Почему IF'ы в тестах - зло

Я часто говорю, что IF’ы в тестах - это зло.

Меня часто спрашивают, почему. Отвечаю.

Тест - это спецификация. Тест проверяет, что программа работает так, как должна. То есть тест точно знает, как должна. Хороший тест должен выглядеть так:

Если эта хрень не зелёная - тест должен упасть. Если та фиговина не красная - тест должен упасть. И точка. Здесь нет места условиям.

Чуть более формально

Давайте чуть конкретизирую. Вот такой IF в тестах разрешён:

IF (что-то) ПРЕКРАТИТЬ ИСПОЛНЕНИЕ

Более того, тест из них и состоит. И тут есть два варианта прекращения - пометить тест как красный (assert) или жёлтый (assume). Но важно то, что тест проверил одну вещь и закончил.

А вот такой IF - строжайше запрещён:

IF (что-то) сделать что-то (кейс А)

или:

IF (что-то) сделать одно (кейс А) ELSE сделать другое (кейс Б)

Это плохо по многим причинам, прежде всего:

  1. Возможно, кейс А так и останется непротестированным
  2. Возможно, кейс Б так и останется непротестированным

Правильное решение - сделать два разных теста: один для кейса А, второй для кейса Б.

Типичный плохой IF

Мой любимый пример - это злосчастный баннер. Когда вы тестируете веб-приложение, в котором в любой момент может открыться рекламный баннер, и тогда его нужно закрыть. Для этого вам приходится добавлять такое условие в тест:

IF (баннер открыт) THEN закрыть баннер

Такой IF - страшное зло по многим причинам.

  1. Он сильно замедляет тест (мой любимый вопрос: “А как подождать, пока элемент не появится, но если не появится, то не падать, а продолжить тест?”)
  2. Он сильно усложняет тест
  3. Он всё равно не решает проблему (потому, что баннер может появиться через долю секунды ПОСЛЕ того, как ты выполнил свою проверку)
  4. Если, допустим, во время запуска этого теста баннер так ни разу и не появился, то кейс с баннером останется непротестированным.

Единственное правильное решение в такой ситуации - взять тестовую среду под свой контроль. Вам на самом деле нужно два разных теста:

Тест 1

Тест 2

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

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

Какие обычно аргументы в пользу IF?

Люди часто говорят, что IF нужен:

Такой IF допустим, потому что он не меняет логический ход теста, а всего лишь оптимизирует скорость. Проверочный приём такой: от этого ифа можно избавиться - каждый раз закрывать и открывать браузер. И ничего кроме скорости не пострадает, тест будет работать точно так же.

Это уже тот самый запрещённый IF. Тест должен точно знать, в какой конфигурации он запущен и что он проверяет. Если в разных конфигурациях приложение ведёт себя по-разному - значит, это разные тест-кейсы, и для них нужны разные тесты. А если тест не может определить, в какой конфигурации работает приложение, и начинает гадать по видимости элементов - это верный признак того, что тестовая среда не под контролем.

Иногда мне возражают: “ну хорошо, но финальная проверка-то у меня без ифов!” Не помогает. Если путь к финальной проверке зависит от условия, то вы всё равно не знаете, какой из путей был протестирован в этот раз.

Всё это нифига не хорошо. Это симптомы, говорящие о глубинных проблемах:

  1. Налицо нехватка юнит-тестов. UI-тестами нереально покрыть всё то многообразие случаев, которые случаются в 40 странах мира. Поэтому баги постоянно и вылезают в бою.
  2. Острый недостаток автоматического деплоймента и нормальной тестовой среды. Поэтому и приходится ставить изменения сразу в бой.

Эти проблемы надо решать, а не ифы в тестах ставить.

Но ведь это как перевязка раны!

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

Ну да, можно и так. Только нельзя ограничиваться перевязкой, верно?

Глупо было бы хвастаться: смотрите, как мы круто перевязываем раны - нам никакие операции не нужны! И ещё на конференциях об этом рассказывать.

Итого

Каждый IF в тесте - это сигнал, что вы чего-то не контролируете: тестовые данные, тестовую среду, конфигурацию приложения. Не прячьте этот сигнал за условием. Найдите, что именно вы не контролируете, возьмите это под контроль - и напишите два честных теста вместо одного хитрого.

Андрей Солнцев

asolntsev.github.io