...почему иногда всё-таки можно делать зависимые тестовые методы?
Image source: Urban Games

...почему иногда всё-таки можно делать зависимые тестовые методы?

Сложные тесты и независимость

В предыдущей статье я пытался объяснить, почему тесты должны быть независимыми. И конечно же спровоцировал ожидаемые возражения – а что делать со сложными тестами, которые состоят из целой серии шагов?

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

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

Так что же делать в такой ситуации?

Я могу предложить два варианта реализации таких сложных тестов.

Метод-тест и методы-шаги

Делаем примерно такой тестовый метод и к нему десяток вспомогательных методов, которые реализуют отдельные шаги:

@Test(dataProvider = "tripDataProvider")
public void testCanBuyTicket(TripInfo tripInfo) {
  selectDepartureStation(tripInfo.from);
  selectArrivalStation(tripInfo.to);
  submitDepartureArrivalSelection();
  selectTrain(tripInfo.train);
  selectSeats(tripInfo.travelers);
  enterTravelersInfo(tripInfo.travelers);
  submitOrder();
  performPayment(tripInfo);
  List<Ticket> tickets = verifyTickets(tripInfo.travelers);
  verifySeatsAreBuzy(tickets);
}

Основной “официальный” недостаток этого способа заключается в недостаточной детализированности отчёта. Есть такое мнение, что отчёт должен быть красивым и подробным. Мало просто написать, что тест buyTicket выполнился успешно, нужно это про каждый шаг отдельно написать.

Лично я это мнение не разделяю. Если тест прошёл успешно – читать отчёт нет смысла, какой бы красивый он ни был. А если тест упал – стектрейс содержит достаточно информации, чтобы узнать, на каком шаге он сломался.

Но спрос, как известно, рождает предложение.

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

Используя эту библиотеку, можно вспомогательные методы пометить особым образом, и они будут протоколироваться как шаги основного сценария.

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

Класс-тест и методы-шаги

Не знаю, обратили ли вы внимание, что в предыдущей статье, которая агитировала за независимость тестов, ни разу не встречалось слово “метод”.

Да, я говорил, что “тесты должны быть независими”.

Но кто сказал, что “тест” это именно “тестовый метод”?

Тестовые фреймворки поначалу создавались для выполнения модульных (unit) тестов. То есть мелких и низкоуровневых. Поэтому вполне логично, что каждый “тест” оформлялся в виде отдельного метода.

Но при создании сложных тестов можно использовать другой подход – считать “тестом” не отдельный метод, а целый класс, а его методы будут шагами этого сложного теста.

В этом случае вполне логично выстроить их в цепочку и связать зависимостями, потому что выполнение следующего шага не имеет смысла, если предыдущий завершился неуспешно:

@Test
public class TestCanBuyTicket {
  @Test
  public void selectDepartureStation() {
   ...
  }

  @Test(dependsOnMethods = "selectDepartureStation")
  public void selectArrivalStation() {
   ...
  }

  @Test(dependsOnMethods = "selectArrivalStation")
  public void submitDepartureArrivalSelection() {
   ...
  }
  ...
}

При этом в отчёт даже без всякого Allure будет попадать информация и о результатах выполнения “тестов” (то есть классов), и об отдельных “шагах” (то есть методах).

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

Частично эту проблему можно решить, если тестовый фреймворк позволяет реализовать так называемые “фабрики тестов”, но там возникают другие проблемы. В частности, в тестовом фреймворке TestNG фабрика работает не самым оптимальным способом – она сначала создаёт все экземпляры тестового класса, потом во всех экземплярах выполняет первый метод-шаг, потом опять таки во всех экземплярах выполняет второй шаг, и так жалее. Очевидно, что для тестирования на уровне пользовательского интерфейса такой подход не годится, и приходится прикладывать дополнительные усилия, чтобы изменить это поведение на более подходящее. Эти сложности, конечно, преодолимы, но это уже совсем другая история.

Резюме

Поскольку я очень люблю параметризованные тесты и не очень люблю красивые отчёты – ясно, что для меня недостатки второго из описанных способов являются более критичными.

Поэтому я предпочитаю делать тесты-методы, а не тесты-классы, даже если они получаются сложными.

И тогда эти тестовые методы должны быть независимыми.

Шаги зависят друг от друга. Тесты – нет.


Алексей Баранцев

Автор:

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

Мои тренинги
А ещё есть? Конечно!