Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Любой автоматизатор тестов рано или поздно сталкивается с задачей либо дополнить, либо расширить тестовый фреймворк. К тому же, у многих есть профессиональная цель написать свой тестовый фреймворк. Чтобы реализовать это, необходимо знать и понимать архитектуру тестовых фреймворков, так как от заложенной архитектуры зависит стабильность, расширяемость и гибкость вашего фреймворка и тестов в целом.
Всем известная организация ISTQB разработала общую схему (архитектуру) компонентов, из которых должен состоять тестовый фреймворк. В этой статье разберем, что это за компоненты и для чего они нужны.
Так как эта схема включает еще и окружение, с которым должен взаимодействовать фреймворк, что выходит за рамки статьи, то ограничимся разбором только архитектуры тестового фреймворка, т.е центральной части схемы.
Дисклеймер
Примеры кода и имен библиотек для наглядности будут приводиться для Python. В целом, все принципы справедливы для любых других языков, так как это общие архитектурные принципы.
Test Adaptation Layer (слой тестовых адаптеров)
Начнём с самого нижнего уровня - Test Adaptation Layer. Слой тестовых адаптеров обеспечивает взаимодействие с тестируемой системой. При анализе системы, принимается решение через какие интерфейсы и обходные пути тесты будут посылать и получать данные, изменять состояние системы и т.д. Далее, все вызовы оборачиваются в адаптеры, чтобы абстрагироваться от тестируемой системы и протоколов взаимодействия с ней. К таким интерфейсам, обычно, относят технические протоколы: HTTP, SSH и т.д., и GUI технологии: Selenium (Playwright), Mobile, Desktop, Image Recognition и т.д.
Что же такое тестовый адаптер? Это такие функции, методы или классы, которые оборачивают вызов базовых библиотек и предоставляют упрощенный интерфейс взаимодействия. Адаптер должен быть:
прост в использовании;
конфигурабелен;
независим;
легко заменяемый.
Если говорить о примерах, то к адаптерам можно отнести класс BaseRequest и более редкий BaseResponse. Это такие классы-обертки над базовым request и объектом response, которые облегчают работу с HTTP, позволяют упростить взаимодействие с библиотекой и использовать логирование в одном общем месте, а не копировать его из теста в тест. Еще один пример адаптера - это PageObject. По сути он оборачивает работу с браузером в класс, который содержит методы с вызовом базовых функций Selenium (Playwright) для работы со страницей.
Отличный пример подхода к реализации адаптера для PageObject и базовых компонентов браузера. Взял за основу идеи из этой статьи и имплементировал их в своем рабочем фреймворке. Респект автору.
https://habr.com/ru/articles/708932/
Если суммировать, то есть некие библиотеки, над которыми делаются обертки. Тем самым, упрощается интерфейс взаимодействия с базовыми библиотеками. Также, реализация адаптеров не должна зависеть от самого фреймворка и не должно быть смешение логик. В результате, адаптеры можно переиспользовать в другом фреймворке.
Test Execution Layer (слой исполнения/запуска тестов)
Рассмотрим второй уровень в типовом фреймворке автотестирования. Слой исполнения/запуска тестов - этот уровень отвечает за нахождение и запуск тестов, сбор отчетности и логов, интеграцию с внешними инструментами. Он содержит в себе три компонента:
Test Execution - исполнение\запуск тестов;
Test Logging - логирование тестов;
Test Reporting - отчетность тестов.
Test Execution
Как понятно из названия, этот компонент отвечает за нахождение и запуск тестов. Обычно в него входят две сущности: Test Engine и Test Runner. Test Engine отвечает за нахождение тестов и их исполнение. Test Runner - отвечает за запуск тестов и управление Test Engine(s). Так же он отвечает за интеграцию с IDE, Build и другими инструментами. Получается, что Test Runner может запустить несколько Test Engine согласно плану исполнения и управлять ими.
Обычно эти два компонента объединены или как минимум не разделены явно. Но все современные тестовые движки, такие как pytest и JUnit, позволяют запускать тесты на нескольких удаленных машинах. Это позволяет запускать тесты на необходимом для них окружении, например, если тесты платформенно зависимые или есть распределенные тестовые сценарии.
Запуск тестов на нескольких удаленных машинах pytest
Настройте ssh конфиг файл ~/.ssh/config
HostName ...
User ...
Port 22
IdentityFile /.../.ssh/windows
Host ubuntu
HostName ...
User ...
Port 22
IdentityFile /.../.ssh/ubuntu
Запуск тестов на нескольких удаленных машинах
pytest -d \
--tx ssh=ubuntu//python=/<remote>/.virtualenvs/advent_of_code/bin/python --rsyncdir /<local>/advent_of_code \
--tx ssh=windows//python=C:\<remote>\.virtualenvs\qa\Scripts\python.exe --rsyncdir /<local>/advent_of_code \
--tx popen//python=/<local>/.virtualenvs/qa/bin/python
Ссылка на источник с примером
Test Logging
Этот модуль отвечает за логирование в тестах.
Есть несколько подходов к логированию. Первый - это простой вывод в консоль\файл. Проблема этого подхода в том, что нет уровней детализации, нет форматирования. Также это может быть узким горлышком при многопоточном выполнении, так как в конечном итоге тесты будут бороться за общий ресурс в виде консоли или файла.
Следующий подход это использование встроенных или общепризнанных библиотек логирования. Там обычно поддерживаются уровни детализации, форматирование. Правда проблема с доступом к общему ресурсу (консоли или файлу) либо остается, либо частично скомпенсирована использованием буфера или другими приемами.
Pytest logging
Еще один вариант - это сохранение логов в Log Managment Tool. Например, это могут быть Graylog или Elastic. Тем самым решается проблема доступа к общему ресурсу, так как специализированные сервисы умеют работать в многопоточном режиме. Также логи не будут утеряны, потому что можно настроить backup и разграничить права доступа. К тому же эти сервисы предоставляют моментальный поиск по логам.
Test reporting
Отчет по тестам. Этот модуль отличается от логирования, так как тут важно видеть не то как прошел тест и причины падения, а какой функционал был протестирован и как. По хорошему, отчет должен помогать как техническим специалистам, содержать логи, причины падения и т.д., так и специалистам со стороны бизнеса, которых интересует что было протестировано, в каком состоянии сейчас продукт и т.д.
Для этого, рекомендую интегрироваться с какой-нибудь Test Management system, например, Allure TestOps или дашборд, например, Report Portal.
Test Definition Layer (Слой определения тестов)
На данном уровне определяются сами тесты и все необходимое для них:
Test Library;
Test Procedures;
Test Cases;
Test Conditions;
Test Data.
Test Library
Тестовые библиотеки - это набор переиспользуемых средств для тестирования. Они предоставляют API к тестируемым функциям.
По определению, это очень похоже на адаптеры, но есть различия. Адаптеры находятся на техническом уровне и по сути не зависят от фреймворка. Они дают возможность оперировать с протоколом, с его терминологией. Если это UI, то они дают возможность взаимодействия с элементом интерфейса, например нажать кнопку или найти лейбл. А тестовые библиотеки работают в терминологии уже вашей тестируемой системы. По сути - это шаги теста, например Login, где передается пользователь и пароль, а метод уже сам знает куда нужно перейти и какой флоу нужно пройти, чтобы залогиниться в тестируемом приложении.
Функции (шаги) в тестовых библиотеках должны обладать следующими свойствами:
тестовый шаг (функция) - одно целостное действие с точки зрения “пользователя”;
независимость друг от друга;
имеют входные параметры и результат.
Test Conditions
Если переводить с терминологии ISTQB, то test conditions - это Assertions, т.е. обычные ассерты, которые используются в каждом современном тестовом фреймворке.
Assertion должен быть:
простым и читабельным;
выдавать понятные сообщения об ошибках;
расширяемым.
Ассерты должны показывать ошибку так, чтобы не нужно было повторно прогонять тест локально, а все можно было понять из отчета о падении.
Test Cases
Тест кейсы - это набор конкретных тестируемых требований. Тест кейс содержит в себе:
условия исполнения (Preconditions);
условия окончания исполнения (Postconditions);
утверждения (Assertions);
тестовые данные;
запуск тестовых шагов.
Когда вначале создается тест кейс в какой-нибудь TMS, то он выглядит довольно структурировано и понятно. Все расписано по шагам, есть ожидаемое поведение и входные данные.
Но когда идет перенос тест кейсов в автотесты, очень часто можно встретить вот такую картину, где все намешано и нет никакой структуры. Из-за того, что низкоуровневые вызовы не обернуты адаптерами и логические блоки не объединены в тестовые библиотеки, такой тест как минимум сложно читать, а как максимум невозможно поддерживать в случае изменений протокола взаимодействия с тестируемой системой. Ведь если нужно поменять, например, xpath до какой-нибудь кнопки, то придется проходить по всем тестам, что очень трудозатратно.
Все шаги кейса должны быть максимально похожи на описание тест кейса при ручном тестировании. Так автотест становится читабельным, все логические блоки обернуты в шаги. В таком случае, при изменении тестируемой системы, нужно будет поменять логику в одном методе.
def test_checkout(page):
login(page, 'joe', 'password')
assert_on_page(page, '/home')
target_item = select_item(page, 123)
add_to_chart(page, target_item)
assert len(page.chart.items) == 1
...
Test Procedures
Тестовая процедура описывает, какие тесты в каком порядке запускать. Пример свойств, которые можно задать:
уровень параллелизма исполнения;
тестовые данные или их источник;
настройки окружения.
В повседневной жизни Test procedures называют как Test suite. Так как управление запуском тестов идет через отдельный внешний модуль, где говорится как и что должно запускаться, следовательно, возникает основное правило при проектировании тестов.
От порядка или количества потоков в тестовой процедуре результат должен оставаться неизменным.
Другими словами, автотесты должны быть независимы друг от друга. Не важно сколько раз и в каком порядке они будут запущены. Поэтому каждый тест должен самостоятельно подготавливать данные или систему перед выполнением и очищать или возвращать систему в исходное состояние после завершения теста.
Test Data
Это модуль, который определяет входные параметры для тестовых сценариев. Естественно, что тестовые данные могут приходить в разных форматах и из разных источников, поэтому у тестового фреймворка должны быть необходимые библиотеки и адаптеры к ним для работы с этими входными данными.
Классический пример такого модуля - это генератор параметров для REST API тестов на основе Swagger приложения. Так как Swagger содержит описание типов полей API, их ограничения и формат, то можно без труда проанализировать его и на основе этого сгенерировать необходимые параметры для тестов.
Test Generation Layer (Слой генерации тестов)
Этот уровень определяет “язык” тестов.
Для написание тестов можно использовать различные “языки”:
Python\Java\C# ..
Robotframework\Gherkin
Excel\HTML\Mardown
Понятно, что в основе любого тестового фреймворка будет лежать какой-то из языков программирования. Нет проблем продолжать писать автотесты на нем. При правильном проектировании Test Library компонента, тестовых шагов, не будет никакой сложности повысить абстракцию описания тест кейсов, использовав Robotframework или что-то другое, если это необходимо. Так как уже существующие шаги можно будет легко “замапить” на Step Definitions, словесное описание шага, которое используется в Robotframework и подобных фреймворках.
Также возможен вариант комбинирования, описание тестов на Robotframework и Python, так как один подход “не закрывает двери” для другого. Можно использовать Robotframework для описания простых тестовых сценариев, для которых он идеально подходит, но при более сложных и нестандартных сценариях, написать тесты с использованием стандартного языка программирования.
Итог
Тестовый фреймворк - это конструктор, который содержит в себе различные модули. Каждый модуль имеет свою область ответственности. Каждый модуль должен быть максимально изолирован друг от друга, чтобы можно было легко изменить или подменить их в случае необходимости. Если немного преобразовать схему ISTQB, то получается, что только Test Steps могут вызывать и обращаться к адаптерам. Test Cases используют Test Steps и Assertions. Test Steps тоже могут использовать Assertions, так как некоторые общие проверки успешности выполнения должны быть внутри самих шагов, а не “разбросаны” по всем тестам, в противном случае, при изменении шага, придется менять проверку по всему проекту. Test Suites управляют запуском и вызывают Test cases. Test Suites и Test cases используют Test Data, так как на основе нее можно управлять скоупом и поведением тестов, и генерировать тест кейсы динамически.