Привет! Меня зовут Степан Лущий, я – тимлид команды тестирования компании «БАРС Груп». Мы занимаемся созданием автоматизированных тестов (автотест), инструментов для их написания, а также развитием инфраструктуры автотестирования. В этой статье я расскажу, как мы разработали инструмент, с помощью которого у нас получилось значительно сократить время реализации автотеста.
Behave-тестирование
В продуктах нашего отдела используется подход behave-тестирования, при котором сценарий автотеста описывает поведение системы с точки зрения пользователя. В шагах сценариев используются специальные ключевые слова «Дано», «Когда», «То»:
Шаги «Дано» описывают то, что нужно сделать, чтобы подготовить систему к проверке функционала;
Шаги «Когда» описывают выполнение проверяемых действий в системе;
Шаги «То» описывают действия по проверке состояния системы.
Ключевые слова – это синтаксис языка Gherkin. Gherkin также является синтаксическим анализатором, который позволяет связывать описание шага с его программной реализацией.
Вот так выглядит сценарий, описанный в feature-файле:
Так как мы используем в проекте язык программирования Python, программная реализация шагов написана в виде функций с декораторами step:
В функциях реализации шагов выполняется отправка запросов в приложение, соответствующих каждому шагу, а также при необходимости выполняется проверка ответов на запросы. Таким образом, наши behave-автотесты проверяют работу только API приложения. Для запуска автотестов используется библиотека behave для Python, адаптированная под работу с фреймворком Django.
Проблематика
В начале применения behave-тестирования в продуктах этапы создания автотестов выглядели так:
Аналитики описывают перечень функционала, который необходимо покрыть автотестами;
Тестировщики составляют сценарии, консультируясь с аналитиками, и описывают шаги, используя тестовые данные;
Тестировщики автоматизируют шаги сценариев. В итоге получаются автотесты.
При такой схеме большую часть работы выполняют тестировщики. Во-первых, при описании сценария со сложным функционалом приложения им требуется консультация аналитика. Во-вторых, процесс автоматизации достаточно рутинный. В зависимости от сложности сценария он может занимать много времени. Чтобы избежать затрат времени на составление сценариев тестировщиками, прежняя схема работы была изменена. Задача по описанию сценариев автотестов теперь перешла к аналитикам, для этого мы настроили несколько стендов приложений с тестовыми данными.
Этапы создания автотестов стали выглядеть следующим образом:
Аналитики описывают функционал для покрытия автотестами;
Аналитики составляют сценарии, описывают шаги, используя тестовые данные (и делают это намного быстрее тестировщиков);
Тестировщики автоматизируют шаги сценариев. В итоге получаются автотесты.
После изменения схемы работы самым продолжительным по времени остался третий этап – автоматизация сценариев.
Мы стали думать, как можно ускорить этот процесс. Стало понятно, что аналитик, описывающий сценарий при помощи приложения с тестовыми данными, выполняет действия, которые должны осуществляться уже в автотесте.
Было принято решение создать инструмент, позволяющий записывать действия пользователя в приложении. В нашем случае под действиями подразумеваются http-запросы с параметрами, отправляемые в API приложения. Записанные действия в автотесте будут формировать состояние приложения, необходимое для поверки.
Начальное состояние приложения ⇒ Действия (запросы к API) ⇒ Проверочное состояние приложения
При этом автоматизацию шагов по проверке состояния системы (шаги с ключевым словом «То»), должен был выполнять тестировщик, как и раньше.
В процессе реализации функционала записи действий выяснилось, что действия по проверке состояния системы тоже нужно записывать для использования их при доработке автотеста. Но можем ли мы обойтись вообще без доработки автотеста? Можем! В процессе записи шагов проверок мы сохраняем ответы от сервера, которые нужны для генерации автоматических проверок в автотесте.
Итак, после применения реализованного инструмента записи, этапы создания автотестов сократятся и будут выглядеть так:
Аналитики описывают функционал для покрытия автотестами.
Аналитики составляют и параллельно записывают сценарии на приложения при помощи инструмента записи. В итоге получаются автотесты.
Сейчас инструмент находится в стадии доработки. Но уже настроено несколько стендов приложений с подключенным инструментом записи теста, которым пользуются аналитики: составляют сценарии автотестов и выполняют их запись. Также есть возможность использовать инструмент записи локально на машине разработчика или тестировщика.
Рассмотрим процесс работы с инструментом на примере записи сценария «Создание документа в реестре Ввода остатков Основных Средств». Работа с инструментом записи в нашем случае предполагает наличие задачи в Jira. Для примера создана задача в Jira с номером BOBUH-19123.
В приложении выбираем нужный пункт меню, открывается окно параметров записи. Заполняем их и сохраняем:
С этого момента запись уже осуществляется, выполним на приложении действия, относящиеся к первому шагу.
2. Далее вызовем пункт меню перехода к записи следующего шага. Откроется окно, в котором укажем его описание и выберем «Начать запись следующего шага». Выполним на приложении действия, относящиеся ко второму шагу:
3. Повторим последовательность из пункта 2 для всех шагов сценария. Когда все шаги будут записаны, нажмем «Завершить запись и сохранить»:
После завершения записи выполняется автоматическое создание ветки с изменениями в репозитории, для которого осуществлялась запись сценария автотеста. Данная ветка отправляется в удаленный репозиторий и теперь отображается в нашей задаче Jira:
Коммит с изменениями в репозитории выглядит следующим образом:
Этот автотест сразу можно выполнить, результат выглядит так:
При этом, если внести правки в работу приложения, например, повлиять на сохранение значения поля «Примечание» у документа,
то при запуске автотеста, мы увидим ошибку на выполнении шага проверки «То»:
Архитектура инструмента записи
Основным в инструменте является класс рекордера BehaveTestRecorder, реализованный с помощью паттерна Singleton. Объект класса BehaveTestRecorder хранит в себе стартовые параметры и состояние записи. С помощью этого объекта происходит управление записью:
Старт записи (метод BehaveTestRecorder.start):
Запускается запись данных запросов в файлы и сохранение ответов от сервера;
Переход к записи следующего шага (метод BehaveTestRecorder.next_step):
Экспорт данных записанного шага в файл;
Завершение записи (метод BehaveTestRecorder.end):
Экспорт записанных данных в файлы;
Коммит изменений, создание ветки, отправка в репозиторий.
На приложении реализован интерфейс управления рекордером.
Доступ к запросам и ответам на приложении происходит с помощью механизма Django Middleware. Для этого в инструменте создан middleware - класс TestRecordingMiddleware:
class TestRecordingMiddleware(MiddlewareMixin):
"""
Middleware для записи каждого запроса в файл в случае если
выполняется запись сценария.
Так же сохраняется результат запроса для последующего создания проверок.
"""
@handle_app_not_available
def process_request(self, request):
recorder = BehaveTestRecorder()
recorder.write_request(
request=request,
)
@handle_app_not_available
def process_response(self, request, response):
recorder = BehaveTestRecorder()
recorder.save_response_data(
request=request,
response=response,
)
return response
Входящий в приложение запрос перехватывается в методе process_request, в нём происходит вызов метода записи данных запроса BehaveTestRecorder.write_request:
def write_request(
self,
request,
):
"""Запись данных запроса.
Args:
request: Объект Django-запроса.
"""
if self.check_request(request):
self._processed_requests.add(id(request))
try:
self._write_request(
request=request,
)
except Exception as e:
self.error_end()
raise e
if self._is_check_step:
# Если это шаг проверки - включаем запись контекста рендера
template_rendered_connect(request)
Приложение возвращает ответ, который перехватывается в методе process_response, в котором осуществляется вызов метода сохранения данных ответа на запрос BehaveTestRecorder.save_response_data. Сохранение происходит только в том случае, когда текущий записываемый шаг – шаг проверки. В зависимости от content-type ответа сохранение происходит в словарь _response_data. Если в ответе пришёл json – сохраняется «сырой ответ». Иначе, если получен объект контекста для рендеринга окна, сохраняется сам объект контекста для последующего преобразования в json.
def save_response_data(
self,
request,
response,
):
"""Сохранение результата запроса для последующего использования проверок в шагах.
В случае ответа сгенерированного с помощью рендеринга шаблона (не json), если доступен контекст, сохраняем его.
Args:
request: Объект Django-запроса.
response: Объект Django-ответа на запрос.
"""
request_id = id(request)
if request_id in self._processed_requests:
self._processed_requests.remove(request_id)
if (
self.check_request(request) and
self._is_check_step
):
template_rendered_disconnect(request)
context = response_context_storage.pop(request_id, None)
if response['content-type'] == 'application/json':
self._response_data[request_id] = response.content
elif context:
if 'window' in context:
self._response_data[request_id] = context['window']
elif 'component' in context:
self._response_data[request_id] = context['component']
Для выгрузки данных записи в файлы реализовано 4 класса-экспортера:
PyRequestExporter – экспортер файлов с параметрами запросов отправляемых в приложение. Он срабатывает при вызове метода записи запроса BehaveTestRecorder.write_request, экспортирует записываемый запрос в отдельный файл в виде функции отправки запроса:
from behave_test_recorder.requests import (
AppRequest,
)
def request_loader(context):
response = AppRequest(
path='/core/enterprise_forwork/select',
method='POST',
context=context,
parameters={
},
).execute()
setattr(context, 'response_551070_select', response)
Пример директории с набором файлов отправки запросов:
StepRequestExporter – экспортер функций реализации шагов. Он запускается при переходе к записи следующего шага или завершении записи – экспортирует функции реализации шага в общий файл steps.py:
StepChecksExporter – экспортер проверок ответов на запросы. Он начинает работать в случае записи шага проверки: в момент перехода к записи следующего шага или при завершении записи. Экспортирует ответы от сервера в отдельный файл в виде функции с проверками соответствия Json-объектов.
Как упоминалось ранее, рекордер сохраняет 2 типа ответов от сервера: в виде сырого json и объекта контекста. В случае json-ответа, генерируется простая проверка сопоставления ответа от сервера с полученным только что json’ом. В случае ответа в виде объекта контекста, генерируется проверка объектов контекста преобразованных в json через специальную функцию.
FeatureExporter – экспортер сценария автотеста. Он срабатывает при завершении записи, экспортирует записанный сценарий в feature-файл.
Итог
Текущая реализация инструмента записи сценариев уже помогает создавать автотесты аналитикам и тестировщикам в нашем отделе, несмотря на то, что инструмент еще находится в стадии разработки. Записанные автотесты, благодаря настроенному CI, могут сразу попадать в кодовую базу (после автоматической проверки). Но случается, что записанные автотесты работают неправильно, например, «падают» с ошибкой в шаге проверки, потому что некорректно сравниваются наборы данных. Чаще всего это зависит от особенностей работы приложения. На данный момент мы дорабатываем инструмент для учета этих особенностей. Также всегда есть возможность вручную исправить записанный автотест.
Основной проблемой текущей реализации инструмента, на мой взгляд, является то, что при записи сценариев с повторяющимися шагами, могут генерироваться наборы одинаковых файлов с отправкой запросов, то есть отсутствует проверка, которая может показать, что запрос с данными параметрами уже записан (для того, чтобы его использовать еще раз). В будущем планируется реализовать возможность в процессе записи сценария автотеста использовать шаги уже созданных автотестов. На самом деле, у нас еще много планов по совершенствованию инструмента.