Gonkey тестирует наши микросервисы в Lamoda, и мы подумали, что он может протестировать и ваши, поэтому выложили его в open source. Если функциональность ваших сервисов реализована преимущественно через API, и используется JSON для обмена данными, то почти наверняка Gonkey подойдет и вам.  Ниже я расскажу о нем подробнее и покажу на конкретных примерах, как его использовать. У нас более ста микросервисов, каждый из которых решает какую-то свою задачу. Все сервисы имеют API. Конечно, некоторые из них — еще и пользовательский интерфейс, но, все же, первостепенная роль у них — это быть источником данных для сайта, мобильных приложений или других внутренних сервисов, а значит предоставлять программный интерфейс. Когда мы поняли, что сервисов становится много, а дальше их будет еще больше, то разработали внутренний документ, описывающий стандартный подход к проектированию API, и взяли как инструмент описания Swagger (и даже написали утилиты для генерации кода на основе swagger-спецификации). Если интересно узнать об этом подробнее, посмотрите доклад Андрея с Highload++. Стандартный подход к проектированию API закономерно навел на мысль о стандартном подходе к тестированию. Вот чего хотелось добиться: Так родился Gonkey. Gonkey — библиотека (для проектов на Golang) и консольная утилита (для проектов на любых языках и технологиях), с помощью которой можно проводить функциональное и регрессионное тестирование сервисов, путем обращения к их API по заранее составленному сценарию. Сценарии тестов описываются в YAML-файлах. Попросту говоря, Gonkey умеет: Репозиторий проекта
Docker-образ Чтобы не нагружать вас текстом, я хочу перейти от слов к делу и прямо здесь протестировать какой-нибудь API и по ходу дела рассказать и показать, как пишутся сценарии тестов. Давайте набросаем маленький сервис на Go, который будет имитировать работу светофора. Он хранит цвет текущего сигнала: красный, желтый или зеленый. Получить текущий цвет сигнала или установить новый можно через API. Полностью исходный код main.go здесь. Запустим программу: Набросал очень быстро, за 15 минут! Наверняка где-нибудь ошибся, поэтому напишем тест и проверим. Скачаем и запустим Gonkey: Эта команда запускает образ с gonkey через докер, монтирует директорию tests/cases внутрь контейнера и запускает gonkey с параметрами -tests tests/cases/ -host. Если вам не нравится подход с докером, то альтернативой такой команде было бы написать: Запустили и получили результат: Нет тестов — нечего проверять. Напишем первый тест. Создадим файл tests/cases/light_get.yaml с минимальным содержимым: На первом уровне — список. Это означает, что мы описали один тест-кейс, но в файле их может быть много. Вместе они составляют тестируемый сценарий. Таким образом, один файл — один сценарий. Можно создать сколько угодно файлов со сценариями тестов, если удобно, разложить их по поддиректориям — gonkey считывает все yaml и yml файлы из переданной директории и глубже рекурсивно. Ниже в файле описаны детали запроса, который будет отправлен на сервер: метод, путь. Еще ниже — код ответа (200) и тело ответа, которые мы ожидаем от сервера. Полный формат файла описан в README. Запустим еще раз: Результат: Ошибка! Ожидалась структура с полем currentLight, а вернулась пустая структура. Это плохо. Первая проблема — это то, что результат был интерпретирован как строка, об этом говорит нам то, что в качестве проблемного места gonkey подсветил весь ответ целиком, без деталиции: Причина простая: я забыл написать, чтобы сервис в ответе указывал тип содержимого application/json. Исправляем: Перезапускаем сервис и прогоняем тесты еще раз: Отлично, есть прогресс. Теперь gonkey распознает структуру, но она по-прежнему неверная: ответ пустой. Причина в том, что я в определении типа использовал неэкспортируемое поле currentLight: В Go поле структуры, названное со строчной буквы считается неэкспортируемым, то есть, недоступным из других пакетов. Сериализатор JSON его не видит и не может включить его в ответ. Исправляем: делаем поле с заглавной буквы, что означает, что оно экспортируемое: Перезапускаем сервис. Снова запускаем тесты. Тесты прошли! Напишем еще один сценарий, который проверит метод set. Заполним файл tests/cases/light_set.yaml следующим содержимым: Первый тест задает новое значения для сигнала светофора, а второй проверяет состояние, чтобы убедиться, что оно поменялось. Запустим тесты все той же командой: Результат: Успешный результат, но нам повезло, что сценарии выполнились в нужном нам порядке: сначала light_get, а потом light_set. Что было бы, если бы они выполнились наоборот? Давайте переименуем: И запустим заново: Сначала выполнился set и оставил светофор в состоянии зеленого, поэтому запущенный следом тест get обнаружил ошибку — он ждал красный. Одним из способов избавится от того, что тест зависит от контекста — это в начале сценария (то есть в начале файла) проинициализировать сервис, что мы в общем-то и делаем в тесте set — сначала задаем известное значение, которое должно произвести известный эффект, а потом проверяем, что эффект возымел действие. Другой способ подготовить контекст выполнения, если сервис использует базу данных — это использовать фикстуры с данными, которые загружаются в базу в начале сценария, тем самым формируя предсказуемое состояние сервиса, которое можно проверять. Описание и примеры работы с фикстурами в gonkey я хочу вынести в отдельную статью. Пока же я предлагаю следующее решение. Так как в сценарии set мы фактически тестируем и метод light/set, и light/get, то сценарий light_get, который зависим от контекста, нам попросту не нужен. Я его удаляю, а оставшийся сценарий переименовываю, чтобы название отражало суть. Следующим шагом я хотел бы проверить некоторые негативные сценарии работы с нашим сервисом, например, корректно ли он отработает, если отправить некорректный цвет сигнала? Или не отправить цвет вовсе? Создам новый сценарий tests/cases/light_set_get_negative.yaml: Он проверяет, что: Запустим: Все отлично :) Как вы заметили, мы тестируем API сервиса, совершенно абстрагируясь от языка и технологий, на которых он написан. Таким же образом мы могли бы протестировать любое публичное API, к исходным кодам которого у нас нет доступа — достаточно возможности отправлять запросы и получать ответы. Но для наших собственных приложений, написанных на go, есть более удобный способ запускать gonkey — подключить его к проекту как библиотеку. Это позволит, не компилируя ничего заранее — ни gonkey, ни сам проект — прогонять тест простым запуском go test. При таком подходе мы как будто начинаем писать юнит-тест, а в теле теста делаем следующее: Для этого нашему приложению понадобится небольшой рефакторинг. Ключевой его момент — вынесение создания сервера в отдельную функцию, потому что нам эта функция теперь понадобится в двух местах: собственно при старте сервиса и еще при запуске тестов gonkey. Я выношу следующий код в отдельную функцию: Функция main тогда будет такой: Измененный файл main go полностью. Это развязало нам руки, поэтому приступим к написанию теста. Я создаю файл func_test.go: Вот файл func_test.go полностью. Вот и все! Проверяем: Результат: Тесты прошли. Если у меня будут и юнит-тесты, и тесты gonkey, они запустятся все вместе — довольно удобно. Allure — это формат отчета о тестировании для отображения результатов в наглядном и красивом виде. Gonkey умеет записывать результаты прохождения тестов в таком формате. Активировать Allure очень просто: Отчет будет помещен в поддиректорию allure-results текущей рабочей директории (поэтому я указал -w /tests). При подключении gonkey как библиотеки Allure-отчет активируется установкой дополнительной переменной окружения GONKEY_ALLURE_DIR: Результаты тестов, записанные в файлы, превращаются в интерактивный отчет командами: Как выглядит отчет:
 В следующих статьях я подробнее остановлюсь на использовании фикстур в gonkey и на имитации ответов других сервисов с помощью моков. Приглашаю вас попробовать gonkey в своих проектах, поучаствовать в его разработке (пул-реквесты приветствуются!) или отметить звездочкой на гитхабе, если этот проект может пригодиться вам в будущем. Архитектор в IT