Pull to refresh

Comments 23

Про случайные числа не согласен: как минимум существует randomized testing, которое в итоге имеет бОльшее покрытие. Впрочем там не так, чтобы уж и случайные числа.

Можете привести пример, когда рандомное число выявит баг, который был бы пропущен параметризованным тестом из 4-5 кейсов?

В том и фишка, рандомизированного тестирования, что случайные числа показывают проблемы за рамками ранее описанных кейсов:


самый известный пример — unicode символы, для которых как правило тесты пишутся только latin1 — но когда на вход приходит французский (или любой другой не latin1) — то появляются неожиданные результаты.


После, конечно, добавляют этот кейс, но рандомизированное может показать это значительно раньше, чем их встретишь

как правило тесты пишутся только latin1

Плохое правило, если есть поддержка любых языков и символов :) В таком случае и рандомизатор напишут скорее всего для latin1, и ничего он не найдет. А если будут осознанно запихивать в тест рандомизатор любых UTF-символов, то сразу же решат проверить и кейсы с многобайтными символами, и снова приходим к тому, что лучше просто добавить в data provider теста таких сложных символов, а не надеяться на рандом.
Где-то на хабре писали про баг с фамилией сотрудника — когда кол-во букв в фамилии совпадало с номером месяца, человеку неправильно рассчитывалась ЗП. Да, баг редкий, но вот рандом имеет шанс на его выявление, а параметризованный тест — нет (если только с первого раза не выявит).

Я тоже так думал. Но рандомизированные тесты надо уметь писать. Если их написать неправильно, то в лучшем случае они будут падать через раз, а в худшем локально проходить, а билд перед продом падать. Я для себя решил не писать рандомизированные тесты — так проще. И не было случая когда я без них не обходился

Как ни крути, но нельзя получить чистого случайного числа (без использования доп. физических девайсов) — почти все они псевдослучайные, а это значит, что если ты знаешь начальный seed — то вся последующая последовательность уже вычислима. На этом и строятся многие фреймворки рандомизированного тестирования — при падении он так же сообщает тебе и начальный seed и что на CI, что локально есть воспроизводимость.

Во-первых, спасибо за статью и хотелось бы выразить негодование по поводу тегов и добавления статьи в хаб "Kotlin". Единственное упоминание этого языка есть только в самом конце, в блоке с саморекламой автора.


Во-вторых, сам автор, как я понял, слабо знаком с экосистемой этого языка, так как по ссылке предлагается все тот же JUnit, тогда как под Kotlin намного рациональнее использовать Spek / Kotest. По примерам уже видно, что обе эти библиотеки позволяют довольно просто сделать те же параметризованные тесты произвольной вложенности и т.д. Аналогично и про моки — тот же mockk синтаксически удобнее аналогов из Java.


В-третьих, в пункте "Используйте заданные значения вместо случайных" опущен прием с "вшитым seed'ом". То есть, просто класс Random использовать нежелательно, так как тесты будет трудно воспроизвести, тут все верно. Однако, зачастую необходимы разные данные на вход. И в этом случае можно создавать класс Random просто с заранее вшитым seed. Подобная практика очень удобная в интеграционных тестах, когда надо создавать много однотипных объектов в условной базе, однако они не должны повторяться на протяжении тестов.

Спасибо за полезный комментарий и ссылки. Мы в FunCorp дрейфуем от Java в Kotlin как на бэкенде, так и на Android, поэтому я когда кидал ссылку на оригинал во внутренний чат — сделал акцент на отдельные приёмы для Kotlin. А при переводе уже машинально поставил хаб.

С JUnit 5 только не всё так радужно — он требует пятой же версии Spring'а, на которую ещё обновиться надо, а это сил и времени требует. А так статья хорошая, надо будет на неё ссылаться как-нибудь.

Хорошая статья. Только для того, чтобы убрать «лишний» код в виде конструкторов необязательно переходить на Котлин. Достаточно использовать Ломбок.

Мы внутри себя считаем, что Lombok — это плохо, привносит лишнюю магию и плохо отражается на читаемости. Но если вам с ним ок — то да.

Странно такое слышать. Спринг куда более магический, чем Ломбок. Документация небольшая и внятная, может быть просто у вас предвзятое отношение с каких-нибудь старых версий, когда ещё в нём были баги.

Мне не хватает фундаментальных понятий — а что же тестировать? Кто подскажет материал? К примеру, есть обычный интернет магазин. Мое мнение — тестирует следует только соблюдение контракта. В понятие контракта входит описание api(запросы, ответы, ошибки, схема json и т.д.) и все. Да и все… может кто подскажет, где это рассматривают с разных сторон?

Насчет использования Instant напрямую в бизнес логике — всегда использовал и не было проблем с тестированием У AssertJ есть метод проверки что-то типа isBeforeOrEqual. А так же я переводил instant в epochSeconds и опять же у assertj есть метод проверки long(?) и double на примерное совпадение, где можно указать процент допустимой разницы между двумя значениями. Но ваш способ через Clock кажется изящнее. Попробую его

Читаешь про тесты: все всегда одно и то же, так пиши, так не пиши. По итогу все равно стремные тесты получаются. Имхо, всему виной процедурная натура подхода к тестированию а иногда и к написанию кода, в итоге зачастую имеешь портянку всяких Mockito.when, не пойми как сгенеренных данных лишь бы работало и кучу ассертов на все на свете на всякий случай.
Если бы кто то предложил красивый декларативный подход (я видел пару библиотек, но меня не убедили), код был бы разбит настолько гранулярно, что можно было бы отделаться парочкой моков (а ещё лучше без них), ну или если бы девелоперы руководствовались принципом «1 тест — 1 ассерт», то все это было бы не нужно

Спасибо за материал, пользуемся многим из перечисленного, и по субъективным жизнь с подобным становится проще и понятнее.
От себя добавлю что:
— Использование статических импортов для общеизвестных методов и констант существенно упрощает чтение и позволяет не утопать в символах;
— Для легкой навигации по участкам Given, When, Then также добавляю комметарии
@Test
public void findProduct() {
    // given
    ...

    // when
    ...

    // then
    ...
}

Позволяет быстро ориентироваться в тесте, если тот содержит больше пары строк кода в одном из участков

Автор как раз топит за то, чтобы писать тесты, которым эти комментарии не нужны) Но мы тоже так делаем, чего уж)

> Вместо этого напишите новый тест с наглядным названием, из которого сразу будет понятно, какого поведения он ожидает от тестируемого кода.

и вы вставляете такой текст с названиями методов 'filterByDateCreated', 'filterByCategory'. Это плохие имена методов.

Глагол 'filter' не отвечает на вопросы «кто» и «что тестируется». Прибавка «ByDateCreated» не даёт мне нужной информации.

Добавлю, что в ряде случаев, когда объекты запросов / expected ответов большие, удобно их не собирать прямо тут, а всё-таки вынести в какой-нибудь .json в ресурсах, и вспомогательными методами (jackson-ом) читать, когда надо. Это в поддержку того, чтобы не использовать продуктовые мапперы.

А я вот не люблю AssertJ, и предпочитаю Hamcrest. По базовому функционалу у них паритет, но на мой вкус написание собственных проверок у Hamcrest куда проще, и, в отличие от AssertJ, не требуется писать (или по новой моде — кодогенерировать) своих версий assertThat для кастомных типов и прочей обвязки.

За статью большое спасибо. Всё очень четко.

Про параметризированные тесты накину кейз неправильного использования, который достаточно часто меня расстраивает.

Это попытка разработчика загнать ВСЁ в параметризированные кейзы. Точнее в один параметризованный тест. Это приводит к тому, что этот параметризированный тест всё чаще становится всё крупнее и сложнее (к тому же для некоторых кейзов делает что-то лишнее).

Я бы в этом случае подумал о серии мелких простых тестов, или серии параметризированных тестов более простых, чем одна большая enum-ка и монструозный тест. Либо о комбинации. Основные тесты - параметризированы (возможно, несколько параметризированных), а особые случаи, которые не легли в общую схему достаточно легко, выделены в отдельные тесты.

Sign up to leave a comment.

Articles