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

Примеры определений:

  • тест покрывает какие-то сценарий (от http-запроса до запросов к БД), запросы к тестовой БД, поднятой в testcontainers, внешние сервисы замокированы
  • тест покрывает какие-то сценарий (от http-запроса до запросов к БД), запросы к БД и внешним сервисам замокированы
  • тестируется один сервис в полноценном тестовом окружении
  • тестируются несколько сервисов одновременно в каком-то тестовом окружении
  • тестируется один метод, который работает с какой-то внешней системой (БД, внешний сервис), внешняя система поднята через testcontainers

Хочу остановиться на 1ом-2ом варианте: это наиболее простой способ поднять показатель покрытия тестами с нуля процентов до 60-70. А вот потом уже каждый процент будет даваться все сложнее и сложнее.

Почему просто? Каждый тест сразу проходит много кода. Покрыв основные положительные и отрицательные сценарии (обычно их мало, иначе они не основные), сразу видим хорошую цифру.

А в чем минусы?

  • Медленно запускается. Все-таки не юнит-тест.
  • Так же легко появляются комбинаторные сценарии: на первом уровне 3 выбора, на втором еще 3, так что нужно писать 9 тестов. В отличие от 6, когда тесты пишутся изолированно.
  • Покрывать неосновные сценарии сложнее, чем основные, т.к. для этого еще ничего не готово: чтобы избежать комбинаторных эффектов переходят от интеграционных тестов к юнит, а там еще ничего не готово: ни сам файл не создан, ни данные.

Какой выход? Тестировать каждую публичную функцию класса отдельно с мокированием всех зависимостей класса:

  • в этом случае не проявляется комбинаторный эффект, так что совершенно очевидно как повышать покрытие и оно линейно по времени
  • технически это обычно чистый unit-тесты, а, значит, быстро работают
  • когда нужно протестировать взаимодействие с внешней системой, то можно использовать testcontainers, а в остальном такой же принцип. В этом случае взаимодействие протестировано, но таких тестов минимальное кол-во, что положительно сказывается на скорости работы тестов
  • подход не накладывает ограничения как именно разрабатывать систему

Некоторые называют это лондонской школой юнит-тестирования.

В итоге, мне ближе последнее определение для интеграционных тестов.

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