15 декабря 2014

Встраиваем тесты в приложение или интеграционное тестирование с хохломой и гимназистками

Преамбула.


Как-то летом один персонаж сказал, что он собирается встроить тесты внутрь самого приложения. Тогда я молча поржал про себя.
Спустя 3 месяца на Agile Testing Days я побывал на докладе, где рассказывалось о том что Runtastic делал так для того чтобы выявить багу на устройствах пользователей.
The developers decided to build a unit test into the app where they could check the result of the device’s calculation for a well-known distance between two coordinates.
(Чуть более подробно можно прочесть тут, но большая часть отводится рекламе)
Собственно с того самого доклада мысль зудела в голове и вылилась в пару часов  пляски с кодом.

Амбула.

Начнем мы конечно же с первого по важности для аудитории этого уютненького бложека вопроса - нахеразачем оно нужно?

Если вам не достаточно кейса Runtastic приведенного выше, то отвечу.
Если у вас все хорошо, production вам подконтролен, конфигурация зафиксирована и проблем нет - оно вам не нужно.
Но не у всех и не всегда оно так.
Примеры когда оно не так:

  1.  Вы разрабатываете что-то что будет ставится в непонятное окружение или контейнер и вам нужно понимать что окружение соответствует.
  2. Окружение в которое вы ставите свое поделие может динамически меняться и об этом тоже было бы неплохо знать.

В общем и целом оба вопроса выше сводятся к вопросу о доверии к reference implementation чего-то что вам дают снаружи ( в случае с Runtastic таким reference implementation-ом было API  по работе с GPS видимо).
В зависимости от того насколько большому куску мы не доверяем может захотеться написать как Unit-тест, так и интеграционный.
Рассмотрим оба случая :).
Естественно я буду рассматривать это все на Java потому что режиссер так видит (с)  мне так нравится, но все показанное ниже также справедливо и реализуемо для всех остальных промышленных языков программирования.

Покажи мне свой код.



Код собственно на гитхабе.
Клонируем, в консоли запускаем mvn exec:java, видим такое.

Дальше идем в браузере идем на урлы:
http://127.0.0.1:8080/runUnitTests - для запуска Unit тестов

Видим такое


http://127.0.0.1:8080/runIntegrationTests - для запуска интеграционных тестов.

Видим такое
Посмотреть в код под капотом (кому и если будет интересно) вы сможете сами.

Мысли вслух.

Unit-тесты встроенные в приложение позволят вам отловить исключительно мелкие вещи. Вреда от того что они встроены в само приложение не должно быть никакого, разве что только если у вас их там миллион и вы все их будете гонять на production-среде.

В случае  с интеграционными тестами все несколько сложнее. Во-первых для того чтобы они были интеграционными вам их надо интегрировать, как бы ни глупо это звучало.
В приведенном примере кода тесты получают тот же самый инстанс Injector-а Guice, что и основное приложение.
Можно конечно сделать отдельный или шарить между инжекторами/контекстами только нужные вещи - но это все детали конкретной реализации.

Второй интересный момент который я нашел именно в такой реализации заключается в том, что подключение реальных кусков бизнес-логики к интеграционным тестам может помочь ловить баги, которые воспроизводятся только в определенных условиях.
Минусы тоже имеются - если в ваши слои бизнес-логики можно вносить изменения в runtime, то запуск таких  интеграционных тестов может положить вам ваш production.
Еще более просто и конкретно - такая штука может положить нафиг весь ваш prod и данные.
И да - подобного рода трюки не предусматривают никакой защиты от этого кроме code review.


Третье - такой подход тянет за собой все зависимости необходимые для тестов в основной продукт - то есть всякие junit, mockito, hamcrest. Ну и код тестов конечно.
Избежать этого можно - в своем примере я развел это через профили сборки maven.
Единственная корявость которая есть - приходится указывать значение свойства по умолчанию для исключаемых при компиляции пакетов.

При обычной сборке (mvn clean package) с такой настройкой содержимое архива будет выглядеть так.
А при релизной (mvn clean package -P release) вот так

По аналогии можно сделать и с зависимостями - их скоуп тоже можно разрулить через профили сборки.


P.S. Как и обещал.
Хохлома.
Гимназистки.