Найти в Дзене
(java || kotlin) && devOps

Хороший тест, плохой тест

Всем привет!

Каким должен быть хороший тест?

Я в первую очередь про модульные (unit), но в принципе правила применимы к любым.

Основные моменты:

1) правило Arrange, Act, Assert https://xp123.com/articles/3a-arrange-act-assert/

Тест делится на три части: подготовка тестовых данных, вызов тестового метода и проверка. Часто забывают про последнюю.

Тест должен что-то проверить: выброшенное исключение, сколько и каких было вызвано методов, состояние объекта.

Тест проверяющий только тот факт, что вызов прошел без исключения, добавляет покрытия, но по сути является недотестом. Его успех показывает, что что-то там выполнилось) Выполнено ли то, что должен делать метод - не ясно. Если API не позволяет проверить результат выполнения кода - это плохое API. Если же код legacy и рефакторить его сложно - можно воспользоваться Mockito.spy или рефлексией.

2) тестовый код - такой же код, как и боевой. К нему должны быть применены все практики написания чистого кода - https://www.litres.ru/robert-s-martin/chistyy-kod-sozdanie-analiz-i-refaktoring-6444478

Я вижу одно исключение - в тестовом коде норм использовать System.out.println

3) тест должен проверять одну операцию с одним или несколькими связанными наборами входных параметров, это удобно делать через параметризацию. При этом допустимо в тесте использовать несколько Assert. Хотя можно их вынести в один assert метод. Или использовать Soft Assert http://joel-costigliola.github.io/assertj/assertj-core-features-highlight.html#soft-assertions. Как по мне - все три варианта норм, дело вкуса.

4) все внешние объекты, требуемые тестируемому методу, должны быть заглушены. Наиболее удобно использовать Mockito.mock или Mockito.spy, но если надо - можно наследоваться от интерфейса и сделать тестовый двойник самому. Если глушить приходится слишком много - повод задуматься про архитектуру кода

5) если стандартного API Mockito или самописных заглушек не хватает, и руки тянутся к включить "секретные" опции Mockito для того, чтобы заглушить private, static или наследоваться от final класса - тоже повод задуматься про архитектуру. Хотя как быстрое решение для legacy допустимо.

6) в поставке для ПРОМа не должно быть тестового кода, боевой код не должен использовать тестовые зависимости и Helpers. В боевом коде не должно быть "ловушек" для успешного выполнения тестов - т.е. выражений типа

if (isTestRun()) {

7) тесты - это документация к коду. Для этого тест должен легко читаться. А чтобы этого добиться - нужно выносить все лишние во вспомогательные методы для Arrange и Assert, передавая как параметры в эти методы только то, что непосредственно влияет на конкретный тест. В этом плане при выборе между @ BeforeEach методом и обычным нужно выбирать обычный - так замысел теста легче читается.

А при возникновении вопроса чтобы бы добавить в тесты, или может уже хватит, стоит в первую очередь думать о закрытии всех основных комбинаций входных параметров, а не о покрытии. Требуемый процент покрытия можно подкрутить, главное как и у тестировщиков - полнота тестовой модели. И читаемость тестов

8) тесты должны быть антихрупкими. Т.е. тест не должен падать при любых правках кода, а тем более - при правках кода, который на первый взгляд никак не связан с тестом. Задача теста - облегчить рефакторинг и доработки, а не усложнить их) Готовых рецептов тут нет, но если данные для assert меняются каждый релиз, значить проверять надо что-то еще. Пример: если речь про API - атрибуты JSON, а не полный JSON

9) тесты не должны зависеть от результата других тестов, от порядка выполнения и от среды

10) из логов запуска упавшего теста должно быть понятно, в чем ошибка. Для этого тест должен выполняться быстро, чтобы запускать его после каждого изменения, тестовые методы должны быть названы осознано, и самое сложное - в assert-ах должны быть сообщения об ошибках. И \ или использовать power asserts, которые подробно выводят в лог что пришло в Assert метод https://github.com/bnorm/kotlin-power-assert

P.S. Кроме JUnit и Mockito есть еще много интересных тестовых фреймворков, расскажу о них в других постах.

#unittests #cleancode