Русский
Русский
English
Статистика
Реклама

Изучаю Scala Часть 3 Юнит Тесты


Привет, Хабр! Мало написать хороший код. Нужно еще покрыть его хорошими Юнит Тестами. В прошлой статье я сделал простой веб сервер. Теперь попробую написать насколько тестов. Обычных, Property-based и с моками. За подробностями добро пожаловать под кат.

Содержание



Ссылки


Исходники
Образы docker image

И так для юнит тестов нужны 3 либы.

  1. Библиотека для создания тестов
  2. Библиотека которая будет генерировать тестовые данные
  3. Библиотека которая будет создавать моки объектов


Для создания тестов я использовал библиотеку ScalaTest
"org.scalatest" %% "scalatest" % "3.2.0" % Test

Для генерирования тестовых данных для Property-based тестирования я использовал ScalaCheck
"org.scalacheck" %% "scalacheck" % "1.14.3" % Test

и расширение которое совмещает ScalaTest + ScalaCheck ScalaTestPlusScalaCheck
"org.scalatestplus" %% "scalacheck-1-14" % "3.2.0.0" % Test

Для создания моков объектов я использовал ScalaMock
"org.scalamock" %% "scalamock" % "4.4.0" % Test

Простенький класс который представляет собой тип заполненной (не пустой) строки. Его мы сейчас и будем тестировать.
package domain.commonsealed abstract case class FilledStr private(value: String)object FilledStr {  def apply(value: String): Option[FilledStr] = {    val trimmed = value.trim    if (trimmed.nonEmpty) {      Some(new FilledStr(trimmed) {})    } else {      None    }  }}

Создаем класс для наших тестов
class FilledStrTests extends AnyFlatSpec with should.Matchers with ScalaCheckPropertyChecks {}

Создаем метод который будет проверять что при создании нашего класса из одинаковых строк мы будем получать одинаковые данные.
 "equals" should "return true fro equal value" in {    val str = "1234AB"    val a = FilledStr(str).get    val b = FilledStr(str).get    b.equals(a) should be(true)  }

В прошлом тесте мы захрадкодили в ручную созданную строку. Теперь сделаем тест используя сгенерированные данные. Мы будем использовать property-based подход при котором тестируются свойства функции что при вот таких входных данных мы получим вот такие выходные данные.
  "constructor" should "save expected value" in {    forAll { s: String =>//Тут фильтруем тестовые данные. Говорим что мы хотим использовать для теста только заполненные строки.      whenever(s.trim.nonEmpty) {        val a = FilledStr(s).get        a.value should be(s)      }    }  }

Можно явно настроить генератор тестовых данных чтобы использовать только нужные нам данные. Например так:
//Определяем наш набор данныхval evenInts = for (n <- Gen.choose(-1000, 1000)) yield 2 * n//Прогоняем тесты с этим наборомforAll (evenInts) { (n) => n % 2 should equal (0) }

Так же можно не передавать явно наш генератор а определить его implict через Arbitrary чтобы он автоматически передавался в качестве генератора в тесты. Например так:
implicit lazy val myCharArbitrary = Arbitrary(Gen.oneOf('A', 'E', 'I', 'O', 'U'))val validChars: Seq[Char] = List('X')//Это тест будет искать ближайший Arbitrary[Char] и получать данные для теста из него.forAll { c: Char => validChars.contains(c) }

Так же с помощью Arbitrary можно генерировать и сложные объекты.
case class Foo(intValue: Int, charValue: Char)val fooGen = for {  intValue <- Gen.posNum[Int]  charValue <- Gen.alphaChar} yield Foo(intValue, charValue)implicit lazy val myFooArbitrary = Arbitrary(fooGen)forAll { foo: Foo => (foo.intValue < 0) ==  && !foo.charValue.isDigit }

Теперь по пробуем написать тест по серьезней. Будем мокать зависимости для TodosService. Он Использует 2 репозитория и репозиторий в свою очередь использует абстракцию над транзакцией UnitOfWork. Будем тестить его самый простой метод
  def getAll(): F[List[Todo]] =    repo.getAll().commit()

Который просто вызывает репозиторий, начинает в нем транзакцию на чтения списка Todo, завершает ее и возвращает результат. Так же в тесте вместо F[_] поставлена монада Id которая просто возвращает хранящееся в ней значение.
class TodoServiceTests extends AnyFlatSpec with MockFactory with should.Matchers {  "geAll" should "возвращает ожидаемые значения" in {//Создаем моки зависимостей.    implicit val tr = mock[TodosRepositoryContract[Id, Id]]    implicit val ir = mock[InstantsRepositoryContract[Id]]    implicit val uow = mock[UnitOfWorkContract[Id, List[Todo], Id]]//Создаем сервис. Он принимает зависимости в свой конструктор не явно через implicit    val service= new TodosService[Id, Id]()//Создаем Id монаду со списком Todo внутри    val list: Id[List[Todo]] = List(Todo(1, "2", 3, Instant.now()))//Устанавливаем что метод getAll репозитория будет возвращать uow и будет вызван 1 раз    (tr.getAll _).expects().returning(uow).once()//Устанавливаем что метод commit будет возвращать созданную коллекцию и будет вызван 1 раз    (uow.commit _).expects().returning(list).once()//Устанавливаем что результат метода сервиса getAll должен быть равен значению коллекции //которую возвращает наш репозиторий    service.getAll() should be(list)  }}

Тесты писать на Scala оказалось очень даже приятно и ScalaCheck, ScalaTest, ScalaMock оказались очень хорошими библиотеками. Как и библиотека для создания АПИ tapir и библиотека для сервера http4s и библиотека для стримов fs2. Пока что окружение и библотеки для Scala вызывают у меня только положительные эмоции. Надеюсь дальше эта тенденция продолжится.
Источник: habr.com
К списку статей
Опубликовано: 21.08.2020 22:11:51
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Программирование

Tdd

Scala

Ооп

Функциональное программирование

Functional programming

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru