Выбор библиотеки ассертов для проекта на Kotlin
В одном из старых проектов в кучу были навалены ассерты из JUnit, kotlin.test и AssertJ. Это было не единственной его проблемой: его вообще писали как письмо Дяди Федора, а времени остановиться и привести к единому виду не было. И вот это время пришло.
В статье будет мини-исследование про то, какие ассерты лучше по субъективным критериям. Хотел сначала сделать что-то простое: накидать набор тестов, чтобы быстренько копипастом клепать варианты. Потом выделил общие тестовые данные, некоторые проверки автоматизировал, и как поехало все… В результате получился небольшой розеттский камень и эта статья может пригодится вам для того, чтобы выбрать библиотеку ассертов, которая подойдет под ваши реалии.
Сразу оговорюсь, что в статье не будет сравнения фреймворков для тестирования, подходов к тестированию и каких-то хитрых подходов к проверке данных. Речь будет идти про несложные ассерты.
Если вам лень читать занудные рассуждения, историю моих мытарств и прочие подробности, то можете перейти сразу к результатам сравнения.
Немного бэкграунда #
Долгое время моим основным ЯП была Scala, а фреймворком для тестов — ScalaTest. Не то чтобы это был лучший фреймворк, но я как-то к нему привык, поэтому мое мнение может быть искажено из-за этого.
Когда на старой работе начали писать на Kotlin, через какое-то время даже делали свою библиотечку, имитирующую поведение скаловских матчеров, но сейчас она имеет мало смысла после появления Kotest (хотя в ней лучше было реализовано сравнение сложных структур, с рекурсивным выводом более подробного сообщения).
Требования #
Сразу оговорюсь, требования весьма субъективны и предвзяты, а часть — вообще вкусовщина. Требования получились такие:
- Бесшовная интеграция с Kotlin и IntelliJ Idea. Scala-библиотеки по этому принципу отпадают — извращаться с настройкой двух рантаймов нет желания. Несмотря на это, ScalaTest будет присутствовать в сравнении как отправная точка, просто потому что с ним много работал. Под интеграцией с IntelliJ я подразумеваю возможность клика на
<Click to see difference>
, чтобы увидеть сравнение реального значения и ожидаемого. Эта фича, вообще говоря, работает в кишках IntelliJ Idea — но ведь разработчики Kotlin-библиотек наверно про нее все-таки слышали и могут решить эту проблему, да? - Возможность быстро понять проблему. Чтобы было не
1 != 2
и стектрейс, а нормальное сообщение, содержащее в идеале название переменной и разделение на “expected” и “actual”. Чтобы для коллекций было сообщение не просто “два множества на 100 элементов не равны, вот тебе оба в строковом представлении, ищи разницу сам”, а подробности, например “… эти элементы должны быть, а их нет, а вот эти не должны, но они есть”. Можно конечно везде описания самому писать, но зачем тогда мне библиотека? Как выяснилось чуть позже, название переменной — это была влажная мечта, и при недолгих раздумьях будет очевидно, что это не так-то просто сделать. - Адекватность записи.
assertEquals(expected, actual)
— Йоды стиль читать сложно мне, вкусовщина однако это большая. Кроме того, я не хочу задумываться о тонких нюансах библиотеки — в идеале должен быть ограниченный набор ключевых слов/конструкций, и чтобы не надо было вспоминать особенности из серии “это массив, а не коллекция, поэтому для него нужен особый метод” или помнить, что строка неcontains
, аincludes
. Другими словами — это одновременно читаемость и очевидность как при чтении, так и при написании тестов. - Наличие проверки содержания подстроки. Что-то вроде
assertThat("Friendship").contains("end")
. - Проверка исключений. В качестве контр-примера приведу JUnit4, в котором исключение ловится либо в аннотацию, либо в переменную типа
ExpectedException
с аннотацией@Rule
. - Сравнение коллекций и содержание элемента(ов) в них.
- Поддержка отрицаний для всего вышеперечисленного.
- Проверка типов. Если ошибка будет выявлена компилятором — то это гораздо круче, чем если она будет выявлена при запуске теста. Как минимум, типизация не должна мешать: если мы знаем тип ожидаемого значения, то тип реального значения, возвращенного generic-функцией, должен быть выведен. Контр-пример:
assertThat(generic<Boolean>(input)).isEqualTo(true)
.<Boolean>
тут лишний. Третий вариант заключается в игнорировании типов при вызове ассерта. - Сравнение сложных структур, например двух словарей с вложенными контейнерами. И даже если в них вложен массив примитивов. Все же знают про неконсистентность их сравнения? Так вот ассерты — это последнее место, где я хочу об этом задумываться, даже если это отличается от поведения в рантайме. Для сложных структур по-хорошему должен быть рекурсивный обход дерева объектов с полезной информацией, а не тупо вызов equals. Кстати в той недо-библиотеке на старой работе так и было сделано.
Не будем рассматривать сложные поведенческие паттерны или очень сложные проверки, где надо у объекта человек
прям сразу проверить, что он и швец, и жнец, и на дуде играет, и вообще хороший парень в нужном месте и время. Повторюсь, что цель — подобрать ассерты, а не фреймворк для тестов. И ориентир на разработчика, который пишет юнит-тесты, а не на полноценного QA-инженера.
Конкурсанты #
Перед написанием этой статьи я думал, что достаточно будет сравнить штук 5 библиотек, но оказалось, что этих библиотек — пруд пруди.
Я сравнивал следующие библиотеки:
- ScalaTest — как опорная точка для меня.
- JUnit 5 — как опорная точка для сферического Java-разработчика.
- kotlin.test — для любителей multiplatform и официальных решений. Для наших целей — это обертка над JUnit, но есть нюансы.
- AssertJ — довольно популярная библиотека с богатым набором ассертов. Отпочковалась от FestAssert, на сайте которого по-японски торгуют сезамином по всем старым ссылкам на документацию.
- Kotest — он же KotlinTest, не путать с kotlin.test. Разработчики пишут, что их вдохновлял ScalaTest. В кишках есть даже сгенерированные функции и классы для 1-22 аргументов — в лучших традициях scala.
- Truth — библиотека от Гугла. По словам самих создателей, очень напоминает AssertJ.
- Hamсrest — фаворит многих автотестировщиков по мнению Яндекса. Поверх нее еще работает valid4j.
- Strikt — многим обязан AssertJ и по стилю тоже его напоминает.
- Kluent — автор пишет, что это обертка над JUnit (хотя на самом деле — над kotlin.test), по стилю похож на Kotest. Мне понравилась документация — куча примеров по категориям, никаких стен текста.
- Atrium — по словам создателей, черпали вдохновение из AssertJ, но потом встали на свой путь. Оригинальная особенность — локализация сообщений ассертов (на уровне импорта в maven/gradle).
- Expekt — черпали вдохновение из Chai.js. Проект заброшен: последний коммит — 4 года назад.
- AssertK — как AssertJ, только AssertK (но есть нюансы).
- HamKrest — как Hamсrest, только HamKrest (на самом деле от Hamcrest только название и стиль).
Если вы хотите нарушить прекрасное число этих конкурсантов — пишите в комментах, какую достойную сравнения библиотеку я упустил или делайте пулл-реквест.
Эволюция методики оценки #
Когда уже написал процентов 80 статьи и добавлял все менее известные библиотеки, наткнулся на репозиторий, где есть сравнение примерно в том виде, что мне думалось изначально. Возможно кому-то там проще будет читать, но там меньше конкурсантов.
Сначала я создал набор тестов, которые всегда валятся, 1 тест — 1 ассерт. Несмотря на то, что я люблю автоматизировать всякую дичь, писать какую-то сложную лабуду для проверки требований мне было откровенно лень, поэтому я планировал проверять все почти вручную и вставить шутку “то самое место, где вы можете влепить минус за “низкий технический уровень материала””.
Потом решил, что все-таки надо защититься от банальных ошибок, и средствами JUnit сделал валидатор, который проверяет, что все тесты, которые должны были завалиться, завалились, и что нет неизвестных тестов. Когда наткнулся на баг в ScalaTest, решил сделать две вариации: одна, где все тесты проходят, вторая — где ничего не проходит и дополнил валидатор. Внимательный читатель может спросить: а кто стрижет брадобрея и какие ассерты использованы там? Отчасти для объективности, отчасти для переносимости ассертов там вообще нет:). Заодно будет демо/аргумент для тех, кто считает, что ассерты не нужны вообще.
Затем я оказался на распутье: выносить ли или нет константы типа listOf(1,2,3)
? Если да — то это упоротость какая-то, если нет — то при переписывании теста на другие ассерты обязательно ошибусь. Составив список библиотек, которые стоит проверить для претензии на полноту исследования, я плюнул и решил решить эту проблему наследованием: написал общий скелет для всех тестов и сделал интерфейс для ассертов, который нужно переопределить для каждой библиотеки. Выглядит немного страшновато, зато можно использовать как розеттский камень.
Однако есть проблема с параметризованными проверками и type erasure. Reified параметры могут быть только в inline-функциях, а их переопределять нельзя. Поэтому хорошо заиспользовать конструкции типа
assertThrows<T>{...}
в коде не получится, пришлось использовать их дополнения без reified параметра:
assertThrows(expectedClass){...}
Я честно немного поковырялся в этой проблеме и решил на нее забить. В конце концов, в kotlin.test есть похожая проблема с интерфейсом Asserter: ассерт на проверку исключения в него не включен, и является внешней функцией. Чего мне тогда выпендриваться, если у создателей языка та же проблема?:)
Весь получившийся код можно посмотреть в репозитории на GitHub. После накрученных абстракций вариант со ScalaTest я оставил как есть, и положил в отдельную папку отдельным проектом.
Результаты #
Дальше тупо суммируем баллы по требованиям: 0 — если требование не выполнено, 0.5 — если выполнено частично, 1 — если все в целом ок. Максимум — 9 баллов.
По-хорошему, надо расставить коэффициенты, чтобы более важные фичи имели больший вес. А по некоторым пунктам так вообще дробные оценки давать. Но я думаю, что для каждого эти веса будут свои. В то же время, хоть как-то табличку мне надо было отсортировать, чтобы “скорее хорошие” библиотеки были наверху, а “скорее не очень” библиотеки были внизу. Поэтому я оставил тупую сумму.
Библиотека | Интеграция | Описание ошибки | Читабельность | Подстрока | Исключения | Коллекции | Отрицания | Вывод типов | Сложные структуры | Итого |
---|---|---|---|---|---|---|---|---|---|---|
Kotest | ± | ± | + | + | + | + | + | нет | + | 7.0 |
Kluent | ± | ± | + | + | + | + | + | нет | - | 6.0 |
AssertJ | ± | + | ± | + | ± | + | + | нет | ± | 6.0 |
Truth | ± | + | + | + | - | + | + | нет | - | 5.5 |
Strikt | ± | ± | ± | + | + | + | + | нет | - | 5.5 |
ScalaTest | ± | ± | ± | + | + | + | + | нет | - | 5.5 |
HamKrest | ± | - | ± | + | + | ± | + | да | - | 5.5 |
AssertK | ± | ± | ± | + | ± | + | + | нет | - | 5.0 |
Atrium | ± | ± | ± | + | + | ± | + | нет | - | 5.0 |
Hamсrest | ± | ± | ± | + | - | ± | + | да | - | 5.0 |
JUnit | + | + | - | ± | + | - | ± | игнор | - | 4.5 |
kotlin.test | + | ± | - | - | + | - | - | да | - | 3.5 |
Expekt | ± | ± | - | + | - | ± | + | нет | - | 3.5 |
Примечания по каждой библиотеке:
Kotest
- Чтобы подключить только ассерты, надо немного поковыряться. На мой взгляд сходу это понять тяжело.
- Не имеет варианта ловли исключения с явным параметром, а не reified, но это на самом деле не особо и нужно: мало кто будет заниматься такими извращениями.
- Сложные структуры: тест с вложенными массивами не прошел. Я завел на это тикет. UPD: Через месяц его пофиксили, и мой тест прошел. Разработчик даже сделал пулл-реквест в мой репозиторий с тестами.
- Интеграция:
<Click to see difference>
есть только для простых ассертов. - Типизация: иногда при использовании дженериков надо писать им явный тип.
- Описание ошибок: почти идеальное, не хватило только подробностей отличия двух множеств.
Kluent
- Можно писать как
"hello".shouldBeEqualTo("hello")
, так и"hello" `should be equal to` "hello"
. Любителям DSL понравится. - Интересная запись для ловли исключения:
invoking { block() } shouldThrow expectedClass.kotlin withMessage expectedMessage
- Описания ошибок в целом отличные, не нет подробностей отличия двух коллекций, что не так. Еще расстроила ошибка в формате
Expected Iterable to contain none of "[1, 3]"
— непонятно, что на самом деле проверяемый Iterable содержит. - Интеграция:
<Click to see difference>
есть только для простых ассертов. - Сложные структуры: тест с вложенными массивами не прошел.
AssertJ
- Впечатляющее количество методов для сравнения — еще бы запомнить их… Надо знать, что списки надо сравнивать через
containsExactly
, множества — черезhasSameElementsAs
, а словари — через.usingRecursiveComparison().isEqualTo
. - Интеграция:
<Click to see difference>
есть только для простых ассертов. - Исключения: ловится просто какое-то исключение, а не конкретное. Сообщение об ошибке, соответственно, не содержит название класса.
- Сложные структуры: есть
.usingRecursiveComparison()
, который почти хорошо сравнивает. Однако ошибку хотелось бы иметь поподробнее: ассерт определяет, что значения по одному ключу не равны, но не говорит по какому. И несмотря на то, что он корректно определил, что два словаря с массивами равны, отрицание этого ассертасработало некорректно: для одинаковых структур не завалился тест на их неравенство.assertThat(actual) .usingRecursiveComparison() .isNotEqualTo(unexpected)
- Типизация: иногда при использовании дженериков надо писать им явный тип. Видимо, это ограничение DSL.
Truth
- Подробные сообщения об ошибках, иногда даже многословные.
- Исключения: не поддерживаются, пишут, что надо использовать
assertThrows
из JUnit5. Интересно, а если ассерты не через JUnit запускают, то что? - Читаемость: кроме прикола с исключением, странное название для метода, проверяющего наличие всех элементов в коллекции:
containsAtLeastElementsIn
. Но я думаю на общем фоне это незначительно, благо тут можно для сравнения коллекций не задумываясь писатьassertThat(actual).isEqualTo(expected)
. - Интеграция:
<Click to see difference>
только для примитивного ассерта. - Сложные структуры: тест с вложенными массивами не прошел.
- Типизация: тип не выводится из ожидаемого значения, пришлось писать явно.
- Веселый стектрейс с сокращалками ссылок “для повышения читаемости”:
expected: 1 but was : 2 at asserts.truth.TruthAsserts.simpleAssert(TruthAsserts.kt:10) at common.FailedAssertsTestBase.simple assert should have descriptive message(FailedAssertsTestBase.kt:20) at [[Reflective call: 4 frames collapsed (https://goo.gl/aH3UyP)]].(:0) at [[Testing framework: 27 frames collapsed (https://goo.gl/aH3UyP)]].(:0) at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) at [[Testing framework: 9 frames collapsed (https://goo.gl/aH3UyP)]].(:0) at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) at [[Testing framework: 9 frames collapsed (https://goo.gl/aH3UyP)]].(:0) at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) at [[Testing framework: 17 frames collapsed (https://goo.gl/aH3UyP)]].(:0) at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99) ...
Strikt
- Не имеет варианта ловли исключения с явным параметром, а не reified, но это на самом деле не особо и нужно: мало кто будет заниматься такими извращениями.
- Отрицание для содержания подстроки в строке выглядит неконсистентно:
expectThat(haystack).not().contains(needle)
, хотя для коллекций есть нормальныйexpectThat(collection).doesNotContain(items)
. - Читаемость: для массивов надо использовать
contentEquals
. Та же проблема с отрицанием:expectThat(actual).not().contentEquals(unexpected)
. Более того, надо еще думать о типе, потому что дляArray<T>
Strikt почему-то не смог определить нужный ассерт сам. Для списков —containsExactly
, для множеств —containsExactlyInAnyOrder
. - Типизация: иногда при использовании дженериков надо писать им явный тип. Более того, для массивов нужно еще с вариацией правильно тип подобрать. Посмотрите на этот кусочек кода:
Он не скомпилируется, потому что компилятор не сможет определить перегрузку для
val actual: Array<String> = arrayOf("1") val expected: Array<String> = arrayOf("2") expectThat(actual).contentEquals(expected)
contentEquals
. Это происходит потому, что нужныйcontentEquals
определен с ковариантным типом:
Из-за этого надо писатьinfix fun <T> Assertion.Builder<Array<out T>>.contentEquals(other: Array<out T>)
val actual: Array<out String> = arrayOf("1") val expected: Array<String> = arrayOf("2") expectThat(actual).contentEquals(expected)
- Интеграция: нет
<Click to see difference>
. - Описание ошибки: нет подробностей для словаря и массивов, а целом довольно подробно.
- Сложные структуры: тест с вложенными массивами не прошел.
ScalaTest
- Интеграция: при сравнении коллекций нельзя открыть сравнение.
- Описание ошибки: в коллекциях написано, что просто не равны. Для словаря тоже подробностей нет.
- Читабельность: надо помнить об особенностях DSL при отрицании и
contains
, отличииcontains
иinclude
, а также необходимостиtheSameElementsAs
. - Сложные структуры: тест с вложенными массивами не прошел, на это есть тикет.
- Типизация: тип не выводится из ожидаемого значения, пришлось писать явно.
Hamkrest
- Проект, судя по тикетам, в полузаброшенном состоянии. Вдобавок документация весьма жиденькая — пришлось читать исходный код библиотеки, чтобы угадать название нужного матчера.
- Ожидал, что достаточно сменить импорты Hamcrest, но не тут-то было: довольно многое тут по-другому.
- Запись ловли исключений — зубодробительная:
assertThat( { block() }, throws(has(RuntimeException::message, equalTo(expectedMessage))))
- Коллекции: нет проверки наличия нескольких элементов. Пулл-реквест висит 3,5 года. Написал так:
assertThat(collection, allOf(items.map { hasElement(it) }))
. - Поддержки массивов нет.
- Сложные структуры: тест с вложенными массивами не прошел.
- Интеграция: нет
<Click to see difference>
. - Описание ошибки — как-то ни о чем:
expected: a value that not contains 1 or contains 3 but contains 1 or contains 3
AssertK
- Как можно догадаться из названия — почти все совпадает с AssertJ. Однако синтаксис иногда немного отличается (нет некоторых методов, некоторые методы называются по-другому).
- Читаемость: Если в AssertJ написано
assertThat(collection).containsAll(items)
, то в AssertK та же конструкция сработает неправильно, потому что в немcontainsAll
принимаетvararg
. Понятно, что цель наcontainsAll(1,2,3)
, но продумать альтернативный вариант стоило бы. В некоторых других библиотеках есть похожая проблема, но в них она вызывает ошибку компиляции, а тут — нет. Причем разработчикам проблема известна — это один из первых тикетов. Вдобавок, нужно отличатьcontainsOnly
иcontainsExactly
. - Интеграция: нет
<Click to see difference>
. - Исключения: ловится просто какое-то исключение, а не конкретное, потом его тип надо отдельно проверять.
- Сложные структуры: аналога
.usingRecursiveComparison()
нет. - Типизация: иногда при использовании дженериков надо писать им явный тип.
- Описания ошибок — подробности есть (хоть и не везде), но местами странные:
Вот почему тут на первый индекс два сообщения?expected to contain exactly:<[3, 4, 5]> but was:<[1, 2, 3]> at index:0 unexpected:<1> at index:1 unexpected:<2> at index:1 expected:<4> at index:2 expected:<5>
Atrium
- Поставляется в двух вариантах стиля: fluent и infix. Я ожидал отличий вида
assertThat(x).isEqualTo(y)
противx shouldBe y
, но нет, этоexpect(x).toBe(y)
противexpect(x) toBe y
. На мой взгляд весьма сомнительная разница, с учетом того, что инфиксный метод можно вызвать без “инфиксности”. Однако для инфиксной записи иногда нужно использовать объект-заполнительo
:expect(x) contains o atLeast 1 butAtMost 2 value "hello"
. Вроде объяснено, зачем так, но выглядит странно. Хотя в среднем по больнице мне нравится infix-ассерты (вертолеты из-за скаловского прошлого), для Atrium я писал во fluent-стиле. - Читабельность: странные отрицания:
notToBe
, ноcontainsNot
. Но это не критично. Пришлось думать, как сделать проверку наличия нескольких элементов в коллекции:contains
принимаетvararg
, аcontainsElementsOf
не может вывести тип, сделал тупой каст. Понятно, что цель наcontains(1,2,3)
, но продумать альтернативный вариант стоило бы. Отрицание наличия нескольких элементов записывается какexpect(collection).containsNot.elementsOf(items)
. - Поддержки работы с массивами нет, рекомендуют преобразовывать через
toList
. - Не имеет варианта ловли исключения с явным параметром, а не reified, но это на самом деле не особо и нужно: мало кто будет заниматься такими извращениями.
- Сложные структуры: тест с вложенными массивами не прошел.
- Интеграция: нет
<Click to see difference>
. - Описание ошибки: местами нет подробностей (при сравнении словарей, например), местами описание довольно запутанное:
expected that subject: [4, 2, 1] (java.util.Arrays.ArrayList <938196491>) ◆ does not contain: ⚬ an entry which is: 1 (kotlin.Int <211381230>) ✘ ▶ number of such entries: 1 ◾ is: 0 (kotlin.Int <1934798916>) ✔ ▶ has at least one element: true ◾ is: true
- Типизация: иногда при использовании дженериков надо писать им явный тип.
Hamcrest
- Читабельность: странный синтаксис для отрицаний (либо
assertThat(actual, `is`(not(unexpected)))
либо
assertThat(actual, not(unexpected))
Надо знать нюанс containsString
vs contains
vs hasItem
vs hasItems
. Пришлось думать, как сделать проверку наличия нескольких элементов в коллекции: hasItems
принимает vararg
, а Set<T>
без знания T
просто так не преобразуешь в массив. Понятно, что цель на hasItems(1,2,3)
, но продумать альтернативный вариант стоило бы. Получилось в итоге
assertThat(collection, allOf(items.map { hasItem(it) }))
С отрицанием еще веселее:
assertThat(collection, not(anyOf(items.map { hasItem(it) })))
- В продолжение этой вакханалии с
hasItems
, я поставил ± в графу “коллекции”, потому что лучше б не было ассертов, чем такие. - Исключения: отдельной проверки нет.
- Интеграция: нет
<Click to see difference>
. - Описание ошибки: для коллекций нет подробностей.
- Сложные структуры: тест с вложенными массивами не прошел.
JUnit
- Читабельность: Йода-стиль
assertEquals(expected, actual)
, надо помнить нюансы и отличия методов: что массивы надо сравнивать черезassertArrayEquals
, коллекции черезassertIterableEquals
и т.п. - Описание ошибок: для тех случаев, когда у JUnit все-таки были методы, оно было вполне нормальным.
- Подстрока: через
assertLinesMatch(listOf(".*$needle.*"), listOf(haystack))
конечно можно, но выглядит это не очень. - Отрицания: нет отрицания для
assertLinesMatch
, что логично, нет отрицания дляassertIterableEquals
. - Коллекции: нет проверки содержания элемента,
assertIterableEquals
дляMap
иSet
не подходит совсем, потому что ему важен порядок. - Сложные структуры: тупо нет.
kotlin.test
- Очень бедно. Вроде как это должна быть обертка над JUnit, но методов там еще меньше. Очевидно, что это расплата за кроссплатформенность.
- Проблемы те же, что и у JUnit, и плюс к этому:
- Нет проверки подстроки.
- Нет даже намека на сравнения коллекций в лице
assertIterableEquals
, нет сравнения массивов. - Типизация: JUnit’у пофиг на типы в
assertEquals
, а kotlin.test ругнулся, что не может вывести тип. - Описание ошибок: не по чему оценивать.
Expekt
- Можно писать в двух стилях
expect(x).equal(y)
иx.should.equal(y)
, причем второй вариант не инфиксный. Разница тут ничтожна, выбрал второй. - Читабельность:
contains(item)
противshould.have.elements(items)
иshould.contain.elements(items)
. Причем есть приватный методcontainsAll
. Пришлось думать, как сделать проверку наличия нескольких элементов в коллекции:should.have.elements
принимаетvararg
. Понятно, что цель наshould.have.elements(1,2,3)
, но продумать альтернативный вариант стоило бы. Для отрицания нужно еще вспомнить проany
:.should.not.contain.any.elements
. - Типизация: тип не выводится из ожидаемого значения, пришлось писать явно.
- Поддержки исключений нет.
- Поддержки массивов нет.
- Сложные структуры: тест с вложенными массивами не прошел.
- Описание ошибки: просто разный текст для разных ассертов без подробностей.
- Интеграция: нет
<Click to see difference>
.
Заключение #
Лично мне из всего этого разнообразия понравились Kotest, Kluent и AssertJ. В целом я в очередной раз опечалился тому, как фигово работать с массивами в Kotlin и весьма удивился, что нигде кроме AssertJ нет нормального рекурсивного сравнения словарей и коллекций (да и там отрицание этого не работает). До написания статьи я думал, что в библиотеках ассертов эти моменты должны быть продуманы.
Что в итоге выбрать — еще предстоит решить команде, но насколько мне известно, большинство пока склоняется к AssertJ. Надеюсь, что и вам эта статья пригодилась и поможет вам выбрать библиотеку для ассертов, подходящую под ваши нужды.
Опросы:
- А какая библиотека больше всего нравится вам?
- Какой библиотекой пользуетесь?
UPD #
Статья удостоилась репоста.
Разработчик Kotest пофиксил проблему со сложными структурами и даже сделал пулл-реквест в мой репозиторий с тестами. Обновил статью.