Я был очень рад видеть, что Kotlintest , превосходный порт в Kotlin , поддерживает тестирование на основе свойств.
Я познакомился с тестированием на основе свойств благодаря великолепной книге «Функциональное программирование в Scala» .
Идея тестирования на основе свойств проста — поведение программы описывается как свойство, а среда тестирования генерирует случайные данные для проверки свойства. Это лучше всего проиллюстрировать на примере с использованием превосходной библиотеки scalacheck :
1
2
3
4
5
6
7
8
|
import org.scalacheck.Prop.forAll import org.scalacheck.Properties object 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.forAll import io.kotlintest.specs.StringSpec class 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.forAll import 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.Gen import io.kotlintest.properties.forAll import io.kotlintest.specs.StringSpec class 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.forAll import org.scalacheck.Properties object 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.forAll import io.kotlintest.specs.StringSpec class 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 for Y{_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. |