Я был очень рад видеть, что Kotlintest , превосходный порт в Kotlin , поддерживает тестирование на основе свойств.
Я познакомился с тестированием на основе свойств благодаря великолепной книге «Функциональное программирование в Scala» .
Идея тестирования на основе свойств проста — поведение программы описывается как свойство, а среда тестирования генерирует случайные данные для проверки свойства. Это лучше всего проиллюстрировать на примере с использованием превосходной библиотеки scalacheck :
|
1
2
3
4
5
6
7
8
|
import org.scalacheck.Prop.forAllimport org.scalacheck.Propertiesobject ListSpecification extends Properties("List") { property("reversing a list twice should return the list") = forAll { (a: List[Int]) => a.reverse.reverse == a }} |
scalacheck сгенерирует случайный список (целого числа) различных размеров и подтвердит, что это свойство относится к спискам. Подобная спецификация, выраженная через Kotlintest, выглядит следующим образом:
|
01
02
03
04
05
06
07
08
09
10
11
|
import io.kotlintest.properties.forAllimport io.kotlintest.specs.StringSpecclass ListSpecification : StringSpec({ "reversing a list twice should return the list" { forAll{ list: List<Int> -> list.reversed().reversed().toList() == list } }}) |
Если генераторы должны быть немного более ограничены, скажем, если мы хотим проверить это поведение в списках целых чисел в диапазоне от 1 до 1000, тогда явный генератор можно передать следующим образом, снова начиная с scalacheck:
|
1
2
3
4
5
6
7
8
9
|
import org.scalacheck.Prop.forAllimport org.scalacheck.{Gen, Properties}object ListSpecification extends Properties("List") { val intList = Gen.listOf(Gen.choose(1, 1000)) property("reversing a list twice should return the list") = forAll(intList) { (a: List[Int]) => a.reverse.reverse == a }} |
и эквивалентный код котлинтеста:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
import io.kotlintest.properties.Genimport io.kotlintest.properties.forAllimport io.kotlintest.specs.StringSpecclass BehaviorOfListSpecs : StringSpec({ "reversing a list twice should return the list" { val intList = Gen.list(Gen.choose(1, 1000)) forAll(intList) { list -> list.reversed().reversed().toList() == list } }}) |
Учитывая это, позвольте мне теперь перейти к другому примеру с сайта scalacheck , на этот раз, чтобы проиллюстрировать ошибку:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
import org.scalacheck.Prop.forAllimport org.scalacheck.Propertiesobject StringSpecification extends Properties("String") { property("startsWith") = forAll { (a: String, b: String) => (a + b).startsWith(a) } property("concatenate") = forAll { (a: String, b: String) => (a + b).length > a.length && (a + b).length > b.length } property("substring") = forAll { (a: String, b: String, c: String) => (a + b + c).substring(a.length, a.length + b.length) == b }} |
второе свойство, описанное выше, неверно — если две строки соединены вместе, они ВСЕГДА больше, чем каждая из частей, это неверно, если одна из строк пуста. Если бы я должен был запустить этот тест с использованием scalacheck, он правильно улавливает это неправильно указанное поведение:
|
1
2
3
4
5
6
|
+ String.startsWith: OK, passed 100 tests.! String.concatenate: Falsified after 0 passed tests.> ARG_0: ""> ARG_1: ""+ String.substring: OK, passed 100 tests.Found 1 failing properties. |
Эквивалент котлинтеста следующий:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import io.kotlintest.properties.forAllimport io.kotlintest.specs.StringSpecclass StringSpecification : StringSpec({ "startsWith" { forAll { a: String, b: String -> (a + b).startsWith(a) } } "concatenate" { forAll { a: String, b: String -> (a + b).length > a.length && (a + b).length > b.length } } "substring" { forAll { a: String, b: String, c: String -> (a + b + c).substring(a.length, a.length + b.length) == b } }}) |
при запуске корректно обнаруживает проблему с помощью concatenate и выдает следующий результат:
|
1
2
3
4
5
|
java.lang.AssertionError: Property failed forY{_DZ<vGnzLQHf9|3$i|UE,;!%8^SRF;JX%EH+<5d:p`Y7dxAd;I+J5LB/:O) at io.kotlintest.properties.PropertyTestingKt.forAll(PropertyTesting.kt:27) |
Однако здесь есть проблема: scalacheck обнаружил более простой случай сбоя, он делает это с помощью процесса, называемого «Минимизация тестового набора», где в случае сбоя он пытается найти наименьший тестовый случай, который может дать сбой, то, что может узнать Kotlintest. от.
Существуют и другие особенности, в которых Kotlintest отстает по отношению к scalacheck, большая из которых может комбинировать генераторы:
|
1
2
3
4
5
6
7
8
|
case class Person(name: String, age: Int)val genPerson = for { name <- Gen.alphaStr age <- Gen.choose(1, 50)} yield Person(name, age)genPerson.sample |
Однако в целом я считаю, что DSL Kotlintest и его поддержка тестирования, основанного на свойствах, являются хорошим началом и с нетерпением ожидаем развития этой библиотеки с течением времени.
Если вы хотите немного поиграть с этими сэмплами, они доступны в моем репозитории github здесь — https://github.com/bijukunjummen/kotlintest-scalacheck-sample
| Ссылка: | Kotlintest и имущественное тестирование от нашего партнера JCG Biju Kunjummen в блоге all and sundry. |