Почему IF'ы в тестах - зло
15 May 2018Я часто говорю, что IF’ы в тестах - это зло.
Меня часто спрашивают, почему. Отвечаю.
Тест - это спецификация. Тест проверяет, что программа работает так, как должна. То есть тест точно знает, как должна. Хороший тест должен выглядеть так:
- Эта хрень должна быть зелёной
- А вот та фиговина должна быть красной
Если эта хрень не зелёная - тест должен упасть. Если та фиговина не красная - тест должен упасть. И точка. Здесь нет места условиям.
Чуть более формально
Давайте чуть конкретизирую. Вот такой IF в тестах разрешён:
IF (что-то) ПРЕКРАТИТЬ ИСПОЛНЕНИЕ
Более того, тест из них и состоит. И тут есть два варианта прекращения - пометить тест как красный (assert) или жёлтый (assume). Но важно то, что тест проверил одну вещь и закончил.
А вот такой IF - строжайше запрещён:
IF (что-то) сделать что-то (кейс А)
или:
IF (что-то) сделать одно (кейс А) ELSE сделать другое (кейс Б)
Это плохо по многим причинам, прежде всего:
- Возможно, кейс А так и останется непротестированным
- Возможно, кейс Б так и останется непротестированным
Правильное решение - сделать два разных теста: один для кейса А, второй для кейса Б.
Типичный плохой IF
Мой любимый пример - это злосчастный баннер. Когда вы тестируете веб-приложение, в котором в любой момент может открыться рекламный баннер, и тогда его нужно закрыть. Для этого вам приходится добавлять такое условие в тест:
IF (баннер открыт) THEN закрыть баннер
Такой IF - страшное зло по многим причинам.
- Он сильно замедляет тест (мой любимый вопрос: “А как подождать, пока элемент не появится, но если не появится, то не падать, а продолжить тест?”)
- Он сильно усложняет тест
- Он всё равно не решает проблему (потому, что баннер может появиться через долю секунды ПОСЛЕ того, как ты выполнил свою проверку)
- Если, допустим, во время запуска этого теста баннер так ни разу и не появился, то кейс с баннером останется непротестированным.
Единственное правильное решение в такой ситуации - взять тестовую среду под свой контроль. Вам на самом деле нужно два разных теста:
Тест 1
- Arrange: Говорим тестовой среде: “Не показывай такому-то клиенту баннер”.
- Act: Заходим этим клиентом
- Assert: проверяем Кейс А: работа без баннера.
Тест 2
- Arrange: Говорим тестовой среде: “Покажи такому-то клиенту баннер на такой-то странице”.
- Act: Заходим этим клиентом
- Assert: проверяем Кейс Б: работа с баннером. Проверяем, что баннер открылся в нужный момент закрываем баннер, проверяем всё остальное.
Для этого нужно уметь управлять тестовой средой. Для многих это оказывается непреодолимым препятствием, они опускают руки и начинают свою обычную волынку в стиле “у нас особенные обстоятельства”, “не всегда всё получается сделать идеально” и т.п.
Ребята, просто скажите себе честно, что вы занимаетесь ерундой.
Вместо того, чтобы решать настоящую проблему, вы расставляете ифы в тестах и ищете себе оправдания.
Это всё равно не решает проблему, но создаёт видимость, что вроде как у вас всё под контролем.
Вы ведёте себя непрофессионально. Вас наняли, чтобы находить проблемы, а вы знаете о проблемах и закрываете на них глаза.
Какие обычно аргументы в пользу IF?
Люди часто говорят, что IF нужен:
- на этапе подготовки теста. Например,
IF (браузер ещё не открыт) THEN открыть браузер
Такой IF допустим, потому что он не меняет логический ход теста, а всего лишь оптимизирует скорость. Проверочный приём такой: от этого ифа можно избавиться - каждый раз закрывать и открывать браузер. И ничего кроме скорости не пострадает, тест будет работать точно так же.
- когда тесты запускаются в разных конфигурациях. Например, IF (конфигурация такая-то) THEN <работать со="" страницей="" так=""> ELSE <работать со="" страницей="" эдак="">работать>работать>
Это уже тот самый запрещённый IF. Тест должен точно знать, в какой конфигурации он запущен и что он проверяет. Если в разных конфигурациях приложение ведёт себя по-разному - значит, это разные тест-кейсы, и для них нужны разные тесты. А если тест не может определить, в какой конфигурации работает приложение, и начинает гадать по видимости элементов - это верный признак того, что тестовая среда не под контролем.
Иногда мне возражают: “ну хорошо, но финальная проверка-то у меня без ифов!” Не помогает. Если путь к финальной проверке зависит от условия, то вы всё равно не знаете, какой из путей был протестирован в этот раз.
- когда тесты гоняются на проде. Мне однажды рассказали такую историю: тесты гонялись прямо на проде, с реальными бронированиями авиабилетов. Тестировать на тестовой среде или стейдже было невозможно, потому что окружение не совпадало с продом. Бизнес работает в 40 странах мира, правила полётов постоянно меняются, и менять нужную часть системы приходится прямо на проде, без тестирования на препродах - иначе огромные штрафы. А на проде, понятное дело, ничего не контролируешь - вот и приходится расставлять ифы.
Всё это нифига не хорошо. Это симптомы, говорящие о глубинных проблемах:
- Налицо нехватка юнит-тестов. UI-тестами нереально покрыть всё то многообразие случаев, которые случаются в 40 странах мира. Поэтому баги постоянно и вылезают в бою.
- Острый недостаток автоматического деплоймента и нормальной тестовой среды. Поэтому и приходится ставить изменения сразу в бой.
Эти проблемы надо решать, а не ифы в тестах ставить.
Но ведь это как перевязка раны!
Мой собеседник тогда привёл хорошую аналогию. IF в тесте - это как перевязка раны. Если не перевяжешь сейчас, пациент потеряет ногу, а то и чего похуже. Сначала оказываем первую помощь, а уже потом везём в больницу и лечим основательно.
Ну да, можно и так. Только нельзя ограничиваться перевязкой, верно?
Глупо было бы хвастаться: смотрите, как мы круто перевязываем раны - нам никакие операции не нужны! И ещё на конференциях об этом рассказывать.
Итого
Каждый IF в тесте - это сигнал, что вы чего-то не контролируете: тестовые данные, тестовую среду, конфигурацию приложения. Не прячьте этот сигнал за условием. Найдите, что именно вы не контролируете, возьмите это под контроль - и напишите два честных теста вместо одного хитрого.
Complexity is the path to the dark side
