Java: Автоматическое тестирование
Теория: Подготовка данных
Большинство тестов на одну и ту же функциональность сильно похожи друг на друга. Особенно в части начальной подготовки данных. В уроке по модульным тестам каждый тест начинался со строчки: Stack<Integer> stack = new Stack<>();. Это ещё не дублирование, но уже шаг в эту сторону. Как правило, реальные тесты сложнее и включают в себя большую подготовительную работу.
Допустим, мы разрабатываем собственную коллекцию, такую же как ArrayList и хотим протестировать методы для обработки коллекций:
- contains
- get
- remove
- и другие (всего их более 20 штук)
Для работы этих методов нужна заранее подготовленная коллекция. Проще всего придумать одну, которая подойдёт для тестирования большинства или даже всех методов:
Теперь представьте, что таких тестов несколько десятков (в реальности их сотни). Код с инициализацией и наполнением коллекции начнёт кочевать из одного места в другое, порождая всё больше и больше копипасты. И если мы ещё можем вынести инициализацию коллекции на уровень класса, т.е. сделать её полем, то с наполнением её данными такой "финт" уже не пройдёт. Нам необходимо вынести эти действия в отдельный метод.
Это простое решение убирает ненужное дублирование, хотя сам метод init() нам по-прежнему нужно вызывать внутри каждого из тестовых методов.
Тесты в JUnit обладают одной очень важной особенностью. Посмотрите на код ниже и подумайте одинаковые или разные значения выведут в консоль первый и второй тесты? Вот сам код:
Подвох тут в том, что переменная startTime инициализируется далеко не один раз. JUnit устроен так, что создаёт новый экземпляр класса для каждого теста (метода, помеченного аннотацией @Test). То есть переменная startTime будет инициализирована при создании экземпляра для теста testFirst(), а также при создании собственного экземпляра класса TestClass для теста testSecond(). Поэтому между нестатичными полями в разных тестах не будет совершенно никакой связи.
Следующий вопрос - какой из тестов запустится раньше? То есть у которого из тестов значение переменной startTime будет меньше? Заранее предсказать ответ на этот вопрос практически невозможно. В этом примере сначала запустился тест testSecond(), а потом запустился тест testFirst(). Попробуйте запустить подобный пример на своём компьютере и посмотреть на результаты.
Почему такое происходит? Именно потому, что мы отдаём бразды правления тестами фреймворку. Он будет определять оптимальный порядок и способ запуска тестов. Пользователю при этом необходимо написать тесты, которые не будут зависеть друг от друга, но об этом мы поговорим в следующем уроке.
Итак, вернёмся к вопросу о правильной инициализации нашей коллекции перед началом каждого теста. Для решения этой проблемы тестовые фреймворки предоставляют хуки — специальные методы, которые запускаются до или после тестов. Ниже пример того, как наполнить нашу коллекцию перед каждым тестом:
Аннотацией @BeforeEach помечаются методы, которые будут выполняться перед стартом каждого из тестовых методов. В этих методах не обязательно создаются переменные. Возможно, инициализация заключается в подготовке файловой системы, например, созданию файлов. Но если метод должен создать данные и сделать их доступными в тестах, то придётся использовать поля класса.
Если нам нужно выполнить код один раз перед всеми тестами, его нужно выполнять внутри метода с аннотацией @BeforeAll. Этот хук запускается ровно один раз перед всеми тестами, расположенными в одном классе. При этом сам метод, помеченный аннотацией @BeforeAll должен быть уже статичным.
Аналогично, существуют аннотации @AfterEach и @AfterAll, которые позволяют выполнить определённые действия после каждого или после всех тестов. Например, вы можете написать метод, который удалит созданный в начале файл.
Почему важно использовать аннотации, а не самостоятельно организовывать порядок и способ выполнения тестов? Самый простой ответ на этот вопрос такой: JUnit должен контролировать происходящие процессы и побочные эффекты в тестах. Все, что вызывается вами вручную, отрабатывается вне JUnit. Это значит, что JUnit никак не может отследить, что происходит, и в какой момент можно запускать тесты.




