Как проверить черный ящик программы Go с помощью RSpec

1656518891 kak proverit chernyj yashhik programmy go s pomoshhyu rspec

Дмитрий Луцко

1*3EftyhurldrDfAe0PuBHZQ

Автоматизированное тестирование сегодня модно в веб-разработке и распространяется на всю отрасль. Хорошо написанный тест значительно снижает риск случайного излома программы, когда вы добавляете новые функции или исправляете ошибки. Когда у вас есть сложная система, состоящая из нескольких компонентов, взаимодействующих друг с другом, невероятно трудно проверить, как каждый компонент взаимодействует с другими компонентами.

Давайте посмотрим, как писать хорошие автоматические тесты для разработки компонентов в Go и как это сделать с помощью библиотеки RSpec в Ruby on Rails.

Добавление Go к стеку технологий нашего проекта

Один из проектов, над которым я работаю в своей компании, eTeam можно разделить на панель администратора, панель управления пользователями, генератор отчетов и процессор запросов, обрабатывающий запросы от различных служб, интегрированных в программу.

Часть проекта, которая обрабатывает запросы, важнейшая, поэтому нам нужно было максимизировать ее надежность и доступность.

Как часть монолитной программы существует высокий риск того, что ошибка повлияет на процессор запросов, даже если есть изменения в коде в частях программы, не связанных с ним. Аналогично существует риск сбоя процессора запросов, когда другие компоненты находятся под большой нагрузкой. Количество работников Ngnix для программы ограничено, что может вызвать проблемы с увеличением нагрузки. Например, когда на панели администратора открывается сразу несколько ресурсоемких страниц, процессор замедляет или даже выводит из строя все приложение.

Эти риски, а также зрелость рассматриваемой системы, нам не пришлось вносить серьезные изменения в течение месяцев, сделали эту программу идеальным кандидатом для создания отдельной службы для обработки запросов.

Мы решили написать отдельный сервис в Go, который делился бы доступом к базе данных с приложением Rails, которое оставалось ответственным за изменения в структуре таблицы. Только с двумя приложениями такая схема с общей базой данных работает отлично. Вот как это выглядело:

1*3dV9gtMm5kAMTzojZ2Y3hQ

Мы написали и развернули службу в отдельном экземпляре Rails. Таким образом, не было необходимости беспокоиться о том, что процессор запросов будет повлиять на каждый раз, когда будет развернута программа Rails. Сервис напрямую принимает HTTP-запросы без Ngnix и не использует много памяти. Это можно назвать минималистическим приложением!

Проблема с модульным тестированием в Go

Мы создали модульные тесты для Go, где все запросы базы данных были высмеяны. В дополнение к другим аргументам этого решения, основная программа Rails отвечала за структуру базы данных, поэтому программа Go фактически не имела информации для создания тестовой базы данных. Половина обработки была бизнес-логикой, а другая половина – запросами в базу данных, все из которых высмеивались.

Смешанные объекты гораздо хуже читаются в Go, чем у Ruby. Каждый раз, когда добавлялись новые функции для считывания данных из базы данных, нам приходилось добавлять высмеиваемые объекты во время многих неудачных тестов, ранее работавших. В конце концов, такие модульные тесты оказались не очень эффективными и были очень хрупкими.

Наше решение

Чтобы восполнить эти недостатки, мы решили покрыть сервис функциональными тестами в приложении Rails и протестировать службу в Go как черный ящик. Тестирование белого ящика в любом случае не сработает, поскольку невозможно было использовать Ruby, чтобы попасть внутрь службы и проверить, вызывается ли метод.

Это также означает, что запросы, отправленные через службу тестирования, также нельзя было высмеивать, поэтому нам требовалась другая программа для управления и написания этих тестов. Что-то вроде RequestBin работало бы, но оно должно было работать локально. Мы уже написали сработавшую утилиту, поэтому решили попробовать ее использовать.

В результате получилась настройка:

  1. RSpec компилирует и запускает двоичный файл Go с конфигурацией, в которой указывается доступ к тестовой базе данных вместе с определенным портом для получения HTTP-запросов, то есть 8082.
  2. Он также запускает утилиту, записывающую HTTP-запросы, поступающие на порт 8083.
  3. Мы пишем регулярные тесты в RSpec. Это создает необходимые данные в базе данных и посылает запрос localhost:8082, будто это внешний сервис, такой как HTTParty.
  4. Разбираем ответ, проверяем изменения в базе, получаем список запросов, которые были записаны заменителем RequestBin и проверяем их.

Детали реализации

Вот как мы это воплотили. Для демонстрации вызовем службу тестирования TheService и создадим обертку:

Следует отметить, что файлы автозагрузки должны быть настроены в папке поддержки при использовании RSpec:

Dir[Rails.root.join('spec/support/**/*.rb')].each {|f| require f}

Способ запуска:

  • Читает информацию о конфигурации, необходимой для запуска TheService. Эта информация может отличаться у разных разработчиков и потому исключена из Git. Конфигурация содержит необходимые параметры для запуска программы. Все эти разные конфигурации находятся в одном месте, поэтому вам не придется создавать ненужные файлы.
  • Компилирует и выполняет go run <path to main.go> <path to config>
  • Опрашивает ежесекундно и ждет, пока TheService будет готов принимать запросы.
  • Записывает идентификатор каждого процесса, чтобы не повторяться и иметь возможность остановить процесс.

Сама конфигурация:

Метод «стоп» просто останавливает процесс. Хотя есть проблема! Ruby запускает команду go run, которая компилирует TheService и запускает двоичный файл в дочернем процессе с неизвестным идентификатором. Если мы просто остановим процесс, выполняемый в Ruby, дочерний процесс не остановится автоматически и порт останется в использовании. Таким образом, остановка TheService должна пройти через идентификатор группы процессов:

Далее мы готовим shared_context, где определяем переменные по умолчанию, запускаем TheService, если он еще не был запущен, и временно выключаем видеомагнитофон, поскольку видеомагнитофон видит то, что мы делаем как запрос внешней службы, но мы не хотим Видеомагнитофон для имитации запросов на на этом этапе:

А теперь мы можем посмотреть на написание спецификаций:

Служба может делать HTTP-запросы к внешним службам. Мы можем настроить его на перенаправление запросов в регистрирующую их локальную утилиту. Для этой утилиты также есть обертка для ее запуска и остановки, подобная TheServiceControl, за исключением того, что эту утилиту можно просто запустить как двоичный файл без компиляции.

Дополнительные акценты

Программа Go была написана так, чтобы все журналы и информация о настройке отправлялись в STDOUT. При производстве этот результат отправляется в файл. При запуске из RSpec журнал отображается в консоли, что действительно помогает при отладке.

Если вы специально запустите спецификации, не требующие TheService, он не запустится.

Чтобы не тратить время на запуск TheService всякий раз, когда спецификация меняется, во время процесса разработки вы можете запускать TheService вручную в терминале и просто не выключать его. Если это необходимо, вы можете запустить его в режиме настройки IDE. Затем спецификации готовят все, отправляют запрос в службу, он останавливается и вы можете легко его наладить. Это делает подход TDD очень удобным.

Вывод

Мы используем эту установку уже около года, и никаких сбоев с ней не было. Спецификации получаются гораздо более читабельными, чем модульное тестирование в Go, и они не полагаются на знание внутренней структуры службы. Если нам по каким-либо причинам нужно переписать сервис на другом языке, нам не нужно будет изменять характеристики. Только обёртки, используемые для запуска тестовой службы другой командой, нужно будет переписать.

Добавить комментарий

Ваш адрес email не будет опубликован.