Как написать идеальный автотест: 25 джедайских принципов
Привет! Меня зовут Дмитрий Трофимов (@angryqa во ВКонтакте или @trofimovdigital на просторах интернета). Я тимлид отдела автоматизации тестирования в VK ID. С командой мы проделали большой путь при внедрении автотестов в наш продукт, и на этом пути мастерски овладели принципами написания идеальных тестов, которыми спешу поделиться с вами.
Автотест — это фича
Что это значит? Автотесты — не просто программный код, а ещё одна фича приложения.
Зачем? Принцип позволяет ответственнее относиться к тестам.
Хорошие практики:
К коду тестов применять те же правила, принципы и паттерны проектирования, что и к коду функциональности;
Не ломать тесты при разработке новых фич;
Думать о тестируемости фичи на этапе её разработки;
Писать тесты на том же языке, что и тестируемое приложение;
Код тестов хранить вместе с кодом функциональности. Это позволит разработчикам самим писать тесты, делиться экспертизой, а также синхронно выкатывать фичи и тесты для них.
Можно ли нарушить? Лучше не нарушать.
Автотест — это тест
Что это значит? При написании тестов нужно использовать техники тест-дизайна.
Зачем? Их применение поможет:
написать полноценный тест, а не просто код, который что-то проверяет;
сократить количество тестов;
не пропускать важные сценарии;
меньшим количеством тестов покрыть больше функциональности.
Хорошие практики: классы эквивалентности, граничные значения, попарное тестирование — самые популярные, но далеко не единственные техники. Разработчику, тестирующему свой код, нужно знать про них, но это тема отдельной статьи.
Можно ли нарушить? Нет.
Атомарность
Что это значит? Один тест — одна проверка (один сценарий).
Зачем? При несоблюдении атомарности, если тест упадёт на одной из пяти условных проверок, то статус других четырёх останется неизвестным. И важно, что не будет выполнена главная проверка, ради которой создавался тест.
Плохие практики: в одном тесте проверять разные сценарии, несвязанные элементы, несвязанную логику. Например, после авторизации вместе с именем пользователя проверять ещё и наличие меню. Одновременно проверять бизнес-логику и аналитику. Подстраиваться под окружение (например, в A/B-экспериментах).
Можно ли нарушить? Можно делать дополнительные проверки, чтобы удостовериться, что тест идет по верному сценарию. Например, перед взаимодействием с элементом, сначала проверить, действительно ли открыта нужная страница, на которой он должен присутствовать. Можно выполнять связанные по смыслу проверки: вместе с именем пользователя проверить аватарку; в ответе API за раз проверить несколько параметров.
Независимость
Что это значит? Тесты должны выполняться в любом порядке и не зависеть от результатов других тестов.
Зачем?
Если один тест упадёт, это не повлияет на прохождение и статус других.
Тесты можно запускать поодиночке, выборочно, в любом порядке.
Плохие практики: авторизуемся в первом тесте, далее последовательно, в том же окружении что-то проверяем во втором, разлогиниваемся в третьем. Баг в первом тесте сделает невозможным прохождение других, а ещё такие тесты нельзя будет запустить по одному.
Можно ли нарушить? Нет. Конец одного теста — не начало другого.
Скорость
Что это значит? Тесты должны быть быстрыми.
Зачем? Высокая скорость обратной связи — одно из главных преимуществ автотестов в сравнении с ручными. Если тесты выполняются долго, то ошибки находят уже после переключения на другие задачи, что замедляет выкатку фичи, снижает пользу и developer experience, раздражает, демотивирует.
Хорошие практики: большинство наших UI-тестов выполняются за пять секунд, а API тесты — ещё быстрее.
Плохие практики:
Использовать «где попало» таймауты;
Тесты в одну минуту. Это медленно, и они чаще падают;
«Страшные истории» про прогоны, длящиеся три дня.
Можно ли нарушить? Нет.
Параллельность
Что это значит? Тесты должны гоняться параллельно.
Зачем? Независимость открывает возможность для параллельного запуска тестов, что делает прогоны ещё быстрее.
Хорошие практики: гонять параллельно и несколько тестовых наборов, и тесты внутри них.
Плохие практики:
Нарушение принципа независимости (тесты не смогут работать параллельно);
Использование одних и тех же тестовых данных (таблица, пользователь) в нескольких тестах. Изменение в одном тесте может дать неожиданный эффект в соседнем, если там используется та же сущность;
Использование общей тестовой среды для тестов. Например, браузерные куки, оставшиеся от одного теста, могут стать причиной падения в другом.
Можно ли нарушить? При очень веских причинах на это. Например, при ограниченном количестве тестовых данных (пользователей, банковских карт), что возможно на начальном этапе автоматизации, когда вы ещё не «разжились» сервисами для их генерации и конфигурирования.
Красота
Что это значит? Туполев говорил, что «хорошо летать могут только красивые самолеты». Так и с тестами: «хорошо работают только красивые».
Зачем? Красивый тест — это понятный тест, что облегчает работу с ним. У теста должен быть внятный облик, соответствующий правилам.
Хорошие практики:
Краткий, но информативный заголовок, с точным описанием тестируемой функции;
Простой для понимания код. В системных и интеграционных тестах низкоуровневый код обёрнут в Steps. Используются паттерны PageObject, PageElement и другие;
Шаги теста описаны в повелительном наклонении (например, «Нажать кнопку»);
Один шаг — одно действие;
Легкочитаемый отчёт о прохождении теста;
Понятно назначение теста, что он проверяет, какое поведение или фичу.
Можно ли нарушить? Нет. Зачем делать что-то непонятным и некрасивым?
Упрощения
Что это значит? Тесты должны быть простыми, насколько это возможно.
Зачем? Чем проще (реализация, понимание, модификация и расширение), тем лучше.
Хорошие практики: не писать сложные структуры и код на будущее. Если тест представляется сложным, дорогим, то нужно найти другое решение или вообще не писать его. Например, как-то мы пытались покрыть авторизацию по QR-коду, и некоторые ребята пытались «научить» устройство сканировать этот самый QR, вместо того, чтобы получить зашитую в него ссылку по API, что гораздо проще.
Можно ли нарушить? Не стоит нарушать. Будет страдать сопровождаемость. Помнишь про KISS (Keep it simple, stupid)?
Тестируй без последствий
Что это значит? После завершения теста среда и сущности должны возвращаться в состояние, в котором находились до запуска.
Зачем? Нарушение принципа может сказаться на стабильности тестов.
Хорошие практики:
Делать каждый тест (если возможно) в новом «чистом» окружении. Например, для UI-тестов в браузере переоткрывать его, начинать новую сессию после каждого теста, разлогиниваться, чистить куки и Local Storage;
Возвращать в исходное состояние тестовых пользователей после использования.
Можно ли нарушить? Когда это не важно для тестов.
Принцип двух ящиков
Что это значит? «Чёрный ящик» и «белый ящик» — методы тестирования программного обеспечения.
Зачем? Для составления оптимального количества тестов. В чёрном ящике генерируются сценарии для тестов без доступа к коду, как будто фичу написал кто-то другой. Это могут быть пользовательские сценарии. А метод белого ящика предполагает написание тестов с доступом к коду, зная, как работает фича.
Хорошие практики: использовать комбинацию чёрного и белого — так называемый серый ящик. Это помогает отбросить лишние тестовые сценарии, потому что мы знаем, какие из них не имеют смысла из-за особенностей реализации фичи.
Можно ли нарушить? Нет.
Хрупкость
Что это значит? Тесты не должны ломаться из-за незначительных изменений в приложении или его окружении.
Зачем? Нарушение принципа скажется на стабильности тестов.
Хорошие практики:
Проверять не внутреннюю реализацию, а конечный результат. Использовать метод чёрного ящика;
В UI-тестах использовать стабильные селекторы элементов вместо поиска их по тексту, который может меняться.
Плохие практики:
Использовать логику в тестах, что увеличивает вероятность ошибки;
Дублировать тестовый код;
Проверять в тестах взаимодействие со стабами;
Нарушение принципов «Независимости» и «Тестирования без последствий».
Можно ли нарушить? Нет.
Не повторяйся
Что это значит? Не нужно писать повторяющийся код в тестах.
Зачем? Использование принципа DRY (don«t repeat yourself), тесты будет проще поддерживать.
Можно ли нарушить? Если это сделает код тестов слишком сложным. Простота важнее.
Принцип ААА
Что это значит? «Arrange — Act — Assert» — «Подготовь — Действуй — Проверь». Согласно этому принципу, тест должен состоять из трёх последовательных логических частей: подготовки тестовых данных, выполнения действий и получения однозначного ответа, совпадают ли ожидаемый и фактический результаты.
Зачем? ААА является способом организации и написания тестов, особенно модульных и интеграционных, но применим и к e2e, делая их более читабельными и поддерживаемыми.
Можно ли нарушить? Только если команда использует другие принципы (например, Given — When — Then, если используют BDD).
Используй скрытую силу
Что это значит? Всегда есть возможности для улучшения, хотя они не всегда очевидны.
Зачем? Чтобы сделать тесты лучше (проще, стабильнее, быстрее).
Хорошие практики:
Использовать в тестах API для подготовки тестовой среды, взаимодействия с ней, настройки тестовых пользователей, предварительной авторизации, проверки ожидаемого результата (где это актуально);
Использовать моки в тестах для имитации поведения реальных объектов и компонентов.
Например, в некоторых тестах для проверки нужно сперва авторизоваться. По-честному ходить по экранам в интерфейсе будет медленнее, чем получить авторизационные куки с помощью API, или вообще подменить реальные ответы моками. А ещё мы избавимся от шагов, не связанных непосредственно с тестом.
Можно ли нарушить? Лучше не нарушать.
Нельзя протестировать всё
Что это значит? Невозможно, бессмысленно или необоснованно дорого в плане ресурсов проверить все возможные комбинации или сценарии. При этом даже полное тестирование не является гарантией отсутствия ошибок в системе.
Хорошие практики:
Не можешь покрыть всё — покрой самое важное: то, что больше всего подвержено серьезным рискам, или то, что приносит деньги;
Используй принцип Парето: 20% тестового покрытия должно отражать 80% основного использования продукта.
Можно ли нарушить? Исчерпывающее тестирование может быть обоснованно там, где даже небольшая ошибка может причинить огромный ущерб (жизни, здоровью, финансам). Но даже там используются методы выборочного тестирования.
Из множества важного выбирай то, что проще
Что это значит? Из множества равнозначно важных задач нужно выбирать самые простые и быстрые в реализации.
Зачем? Чтобы добиться максимального результата при минимальных затратах и скорее принести пользу.
Хорошие практики:
Можно вспомнить матрицу критичности, в ней по оси X отложена скорость (простота реализации), а по оси Y — приоритет. Делаем в первую очередь критичные и быстрые. А некритичные и медленные не автоматизируем вообще.
На доработки, необходимые для написания тестов, заводить задачи в ответственные команды.
Можно ли нарушить? Не стоит нарушать.
Тайна пирамиды
Что это значит? Согласно пирамиде тестирования, модульные тесты должны занимать около 40%, интеграционные — 30%, e2e-тесты — 20%, и не более 10% ручных.
Зачем? e2e-тесты — важные, максимально приближенные к пользователю, но при этом это самые сложные, дорогие, медленные и нестабильные тесты в проекте. Хорошее покрытие на нижних уровнях, где тесты самые простые и быстрые, позволяют писать меньше тестов на верхних.
Плохие практики: перевёрнутая пирамида говорит о высокой стоимость обратной связи. Раздутый e2e-слой может означать, что тесты сложно поддерживать.
Хорошие практики: вместо e2e тестов делать больше интеграционных и модульных.
Можно ли нарушить? Лучше не нарушать — будет больно поддерживать.
Тесты должны запускаться автоматически
Что это значит? Если тесты не запускаются автоматически, то у вас нет автотестов.
Зачем? Исключение человеческого фактора — ещё одно из главных преимуществ автотестов. Разработчик может забыть прогнать тесты, не проверить отчёт, катнуть код с багами.
Хорошие практики: тесты приносят пользу только в том случае, если они автоматически запускаются, встроены в CI и блокируют влитие при падениях, иначе про них будут забывать.
Можно ли нарушить? Нет.
Думай иначе
Что это значит? При проектировании тестов важно использовать разные способы мышления.
Зачем? Чтобы охватить больше возможных сценариев.
Хорошие практики:
Ориентир на созидание — позитивные сценарии использования продукта, в которых нужно принимать во внимание пользователей, последовательность действий;
Ориентир на разрушение — разработка таких тестов, которые выявят случаи, мешающие работе пользователя;
Думать не только сценариями, но и маленькими атомарными действиями, что ещё мог бы сделать пользователь?
Можно ли нарушить? Лучше не нарушать.
Устрой дестрой
Что это значит? После написания теста обязательно попробуйте его «сломать».
Зачем? Чтобы убедиться, что тест найдёт баг.
Хорошие практики:
Проверить, как ведёт себя тест с некорректными данными, отсутствующим элементом, ошибками в ответах сервера;
Проверить, как ведёт себя тест на некорректном (изменившемся) сценарии;
Проверить, как ведёт себя тест при недоступности связанных сервисов.
Можно ли нарушить? Нет, чтобы не пропускать ошибки в тестах.
Поделись знанием
Что это значит? Делиться своими знаниями и опытом с командой.
Зачем? У разных команд и отделов свой контекст, свои задачи. У некоторых отдельные репозитории с проектом. Часть полезной информации может теряться. Обмен знаниями сделает работу (не только с тестами) эффективнее.
Хорошие практики:
Рассказывать об интересных задачах на командных встречах;
Фиксировать новые знания в вики;
Задавать вопросы не в личках, а в специализированных чатах. Вероятно, кто-то уже решал подобную проблему, а кто-то узнает новое.
Можно ли нарушить? Лучше не нарушать.
Доверие
Что это значит? Если тестам нет доверия, то они бесполезны.
Зачем? Тестам можно доверять тогда, когда они падают при наличии бага и остаются зелёными при отсутствии проблем — без компромиссов.
Хорошие практики:
Держать тесты зелёными;
Чинить Flaky-тесты;
Оперативно «выключать» падающие.
Иначе доверие и ценность тестов теряется.
Можно ли нарушить? Нет. Представь, что твой тест проверяет работу твоего космического корабля перед полётом, или аппарата, проводящего операцию на твоём сердце, или финансовую систему, в которой находятся твои сбережения. Доверился бы?
Ценность
Что это значит? Не нужно делать то, что не принесёт пользы.
Зачем? Тесты должны приносить пользу, причём неоднократно. Написать хороший тестовый код сложно, но поддерживать бестолковый — ещё сложнее.
Плохие практики:
Игнорировать вышеназванные принципы;
Игнорирование техник тест-дизайна;
Низкое качество тестов, в том числе тестовых данных;
Формальное отношение к тестам: что-то написали, а зачем, что проверяет — непонятно;
Неправильный, неудачный выбор инструментов;
Дублирующиеся тесты. Функциональность, которая перекрывается другими тестами.
Можно ли нарушить? Нет.
Лучше ничего не делать, чем сделать flaky-тест
Что это значит? У Толстого есть книга «Лучше ничего не делать, чем делать ничего», а у нас — этот принцип.
Зачем? Потратишь своё время на создание бесполезного теста и чужое — на его разбор, игнор и починку. Flaky-тесты подрывают принципы «Ценности» и «Доверия». На такие тесты перестают обращать внимания.
Плохие практики:
Запустить новый тест малое количество раз, перед влитием;
Не проверить на CI, в окружении, в котором тест будет гоняться;
Не проверить, как ведёт себя тест при параллельном прогоне;
Не проверить на слабопроизводительном оборудовании;
Перезапуск тестов в прогонах. Правильнее изучить и устранить причины падений.
Хорошие практики:
Соответствие покрытия пирамиде тестирования;
Использовать не настоящие сервисы, а эмуляторы (ОТП, почта и др.);
Чистое окружение перед каждым тестом (сессия браузера, БД).
Можно ли нарушить? Нет.
Тесты не так важны, как действия по результатам этих тестов
Что это значит? Тесты полезны настолько, насколько они ускоряют разработку и улучшают качество продукта.
Плохие практики:
Игнорировать падения тестов;
Игнорировать процессы;
Молчать о «болях», не улучшать свой и командный Developer Experience;
Относиться к тестам формально;
Считать, что качество — это проблема «какого-то» тестировщика.
Можно ли нарушить? Нет.
Если вы постигли описанные практики — поздравляю, вы настоящий джедай в автотестах. Следование принципам позволит писать тесты, которые исправно служат, не требуют больших трудозатрат на поддержку в течение жизненного цикла программного продукта, обеспечивая защиту от регрессионных багов. Как писал Роберт Мартин в своей «Чистой архитектуре»: «создание беспорядка всегда оказывается медленнее, чем неуклонное соблюдение чистоты».