(java || kotlin) && devOps
369 subscribers
6 photos
1 video
6 files
306 links
Полезное про Java и Kotlin - фреймворки, паттерны, тесты, тонкости JVM. Немного архитектуры. И DevOps, куда без него
Download Telegram
Всем привет!

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

Я в первую очередь про модульные (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. Женя Осмаковский, спасибо за подсказку по п.8

#unittests #cleancode
Всем привет!

Продолжим про Kotlin.
Есть ли у него минусы? А точнее какие могут быть косяки в коде Kotlin, вызванные в том числе неправильным использованием языка.
Немного накину по своему опыту.

1) злоупотребление контекстными функциями типа also.
https://kotlinlang.org/docs/scope-functions.html
На примере also - он нужен если в коде используются поля и методы объекта контекста - it. Если можно просто две написать последовательные строчки кода - лучше так и сделать.

obj.doSomething().also { doAnotherImportantBusinessLogic() } - плохо

obj.doSomething()
doAnotherImportantBusinessLogic()
- хорошо

2) злоупотребление Extension functions
https://kotlinlang.org/docs/extensions.html
Extension functions конечно крутая штука, но она ухудшает читаемость кода, т.к. код класса размазывается по нескольким файлам. Т.е. использовать ее нужно только если очень нужно)
И если очень нужно и это extension библиотечного типа - поместить его в класс, а не как top level функцию с пакетной видимостью.

3) использование упрощенного синтаксиса для объявления метода + контекстные функции приводят к тому, что Single expression function превращается в Very Big Expression Function и, соответственно, ухудшается читаемость кода метода.
fun transform(x: String): Result<Type1, Type2> = Result(doSomething()).also {
code
other code
another code
more code
....
}
Собственно похожая проблема будет если злоупотреблять method chaining
obj.prepare(...).recieve().handleSuccees(..).log(...).postHandle(..)...

Хочу обратить внимание на два момента - злоупотребление=применение ради применения и читаемость кода. Я думаю это два хороших критерия для ответа на вопрос - стоит ли применять ту или иную фичу.

#kotlin #cleancode