Большинство из нас сталкивалось с убеждениями, которые не подвергаются сомнению. Такие идеи могут варьироваться от мелких повседневных дел, таких как покупка продуктов, до чего-то столь серьезного, как религиозные вопросы. В моем случае это было профессиональное убеждение, в частности, относительно того, являются ли интеграционные тесты слишком медленными или нет.
В нашей команде (и в нашем подразделении тоже) было убеждение, что интеграционные тесты проходят медленно, и это убеждение заставило нас не включать эту проблему в нашу техническую задолженность, и мы все приняли это как факт. Это продолжалось до тех пор, пока время сборки в некоторых микросервисах не превысило 10 минут, и товарищи по команде не начали жаловаться на них. Первой реакцией было по возможности избегать интеграционных тестов и вместо этого использовать модульные тесты. Я знаю, что это не мудрое решение, против которого есть много аргументов, но это было решение, которое приняла команда. В этом посте мы увидим, как была решена эта проблема, и время сборки в наших микросервисах сократилось вдвое.
Наконец, в выходные я решил заняться этим вопросом и найти ответ на вопрос: Почему наши интеграционные тесты такие медленные? Я начал с того, что записал все, к чему относился скептически, но мой разум был просто одержим @SpringBootTest .
После первого подозрения
Всякий раз, когда проводились наши тесты, я видел, как несколько раз появлялся логотип Spring. Я думал, что @@Springboost загружается полный контекст приложения для каждого тестового класса. После десятиминутных поисков я понял, что все мои предположения были неверны. Я нашел этот пункт в Spring docs :
Как только платформа TestContext загрузит ApplicationContext (или WebApplicationContext) для теста, этот контекст будет кэширован и повторно использован для всех последующих тестов, которые объявляют ту же уникальную конфигурацию контекста в том же наборе тестов.
Так почему же он все еще загружал контекст для каждого тестового класса? На самом деле этого не произошло. Я получил это, подсчитав загрузку контекста по определенному фрагменту текста в журнале (который повторяется каждый раз, когда Spring загружает контекст приложения). Что-то вроде этого:
mvn clean install > build-log.txt grep "The following profiles are active: test" log.txt| wc -l
Результат составил 16, в то время как у нас было 46 классов интеграционных тестов в кодовой базе. Это означает, что он не загружал контекст для каждого тестового класса, но почему только 16 раз?
Копание в тестовых классах
Получив этот странный результат от подсчета контекстных нагрузок, я проверил все тестовые классы один за другим, чтобы найти подсказку. Я понял, что все классы интеграционных тестов были аннотированы @TestPropertySource для загрузки одного или нескольких определенных файлов свойств. У меня также была еще одна странная находка в этом расследовании: Макет боба и / аннотации в интеграционных тестах. У меня были не только философские проблемы с использованием этих аннотаций в интеграционных тестах, но и кричало ли это о том, что: “ Эй, чувак, здесь пахнет кодом
Я начал второй раунд поиска, и так же, как и в предыдущем, мне удалось найти тонны полезных статей и сообщений в блогах, связанных с контекстом, и некоторые подсказки примерно через десять минут. Я нашел это полезное сообщение в блоге Хосе Карлоса Валеро Санчеса об оптимизации интеграционных тестов Spring boot и это что его автор прошел тот же путь, что и я.
Прочитав эти и те же статьи, я понял, что в интеграционных тестах есть некоторые подводные камни, которые не позволяют Spring повторно использовать загруженный контекст приложения в интеграционных тестах. Вот наиболее важные из них:
- Использование @Mockbeat и @@Соя
- Использование @DirtiesContext
- Неосторожное использование профилей в интеграционных тестах
- Неосторожное использование @TestPropertySource
Я подробно рассмотрю все эти пункты в отдельном посте в блоге. Теперь давайте посмотрим, как я улучшил наши интеграционные тесты с помощью двух незначительных действий.
Оптимизация интеграционных тестов
Как я уже упоминал ранее, в наших классах интеграционных тестов было два подводных камня. Первым из них было аннотирование всех интеграционных тестов с помощью @TestPropertySource . Я быстро взглянул на них и обнаружил, что для каждого класса используются разные комбинации файлов свойств. Например:
@TestPropertySource({ "classpath:x.properties", "classpath:y.properties" }) public class TestOne{ //... } @TestPropertySource({ "classpath:z.properties", "classpath:y.properties" }) public class TestTwo{ //... } @TestPropertySource({ "classpath:x.properties", "classpath:z.properties" }) public class TestThree{ //... } @TestPropertySource({ "classpath:x.properties", "classpath:z.properties" }) public class TestFour{ //... }
В этой ситуации Spring загружает контекст для каждой уникальной комбинации файлов свойств. Например, в предыдущем примере он загружает контекст три раза, потому что x и z повторялись два раза, поэтому Spring может повторно использовать контекст для них обоих.
Поэтому я решил исключить аннотацию TestPropertySource из всех классов интеграционных тестов и объединить их в абстрактный класс, который все они расширяют. Вот новый стиль тестовых классов:
@TestPropertySource({ "classpath:x.properties", "classpath:y.properties", "classpath:z.properties" }) public abstract class AbstractTes{ //... } public class TestX extends AbstractTest{ //... }
Теперь, в этом примере, он загружает контекст только один раз. Я сделал что-то подобное в кодовой базе и снова подсчитал загрузку контекста. Результат был многообещающим: количество контекстных загрузок сократилось до 2 (с 16). Обратите внимание, что, например, если у вас есть свойство с именем database.url оба в x.properties и z.свойства , второй переопределяет первый.
Я прошел оставшиеся тестовые занятия. Знаешь что? Все они содержали либо @Mockbeat или @Соя . Как я уже упоминал ранее, в отдельном сообщении в блоге я расскажу об использовании этих причудливых аннотаций и проблемах, которые они могут вызвать в интеграционных тестах.
Извлеченный урок
Весь процесс занял от 3 до 4 часов. Следовательно, время сборки на Дженкинсе сократилось с 10 до 4 минут. Это означает, что иногда мы могли бы сэкономить много времени, потратив несколько часов на решение таких проблем, которые в долгосрочной перспективе отнимают значительное количество времени.
В ближайшее время я рассмотрю проблемы производительности в интеграционных тестах в отдельном сообщении в блоге. Я был бы признателен, если бы вы поделились своими комментариями.
Оригинал: “https://dev.to/dante0747/sluggish-spring-boot-tests-riddle-4ej6”