[Программирование, TDD, Scala, ООП, Функциональное программирование] Изучаю Scala: Часть 3 — Юнит Тесты
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Привет, Хабр! Мало написать хороший код. Нужно еще покрыть его хорошими Юнит Тестами. В прошлой статье я сделал простой веб сервер. Теперь попробую написать насколько тестов. Обычных, Property-based и с моками. За подробностями добро пожаловать под кат.
Содержание
- Изучаю Scala: Часть 1 — Игра змейка
- Изучаю Scala: Часть 2 — Todo лист с возможностью загрузки картинок
- Изучаю Scala: Часть 3 — Юнит Тесты
Ссылки
Исходники
Образы docker image
И так для юнит тестов нужны 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.common
sealed 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
===========
Похожие новости:
- [Python, Программирование, Изучение языков] Почему Python — плохой выбор для первого языка программирования?
- [Программирование, Java] Что нового в Spring Data (Klara Dan von) Neumann (перевод)
- [Программирование, .NET, C#] Битва C# JSON сериализаторов для .NET Core 3 (перевод)
- [Разработка веб-сайтов, PHP, Программирование, Проектирование и рефакторинг] Модернизация старого PHP-приложения (перевод)
- [Go, Программирование] Конвертация целых чисел в interface{} в Go 1.15
- [Информационная безопасность, Реверс-инжиниринг, Программирование микроконтроллеров, Производство и разработка электроники] Реверс embedded: трассировка кода через SPI-flash
- [PostgreSQL, Программирование, SQL, Администрирование баз данных] PostgreSQL Antipatterns: уникальные идентификаторы
- [Open source, Программирование, Rust] Закладывая фундамент будущего Rust (перевод)
- [Разработка веб-сайтов, PHP, Программирование, Проектирование и рефакторинг] Мёртвый код: найти и обезвредить
- [Совершенный код, ООП] Почему автоматическая регистрация зависимостей — зло
Теги для поиска: #_programmirovanie (Программирование), #_tdd, #_scala, #_oop (ООП), #_funktsionalnoe_programmirovanie (Функциональное программирование), #_scala, #_functional_programming, #_funktsionalnoe_programmirovanie (функциональное программирование), #_oop (ооп), #_tdd, #_programmirovanie (
Программирование
), #_tdd, #_scala, #_oop (
ООП
), #_funktsionalnoe_programmirovanie (
Функциональное программирование
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 01:00
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Привет, Хабр! Мало написать хороший код. Нужно еще покрыть его хорошими Юнит Тестами. В прошлой статье я сделал простой веб сервер. Теперь попробую написать насколько тестов. Обычных, Property-based и с моками. За подробностями добро пожаловать под кат. Содержание
Ссылки Исходники Образы docker image И так для юнит тестов нужны 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.common
sealed 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 =========== Похожие новости:
Программирование ), #_tdd, #_scala, #_oop ( ООП ), #_funktsionalnoe_programmirovanie ( Функциональное программирование ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 01:00
Часовой пояс: UTC + 5