Препятствие при внесении кода: `test::nginx`

API7.ai

November 17, 2022

OpenResty (NGINX + Lua)

Тестирование является неотъемлемой частью разработки программного обеспечения. Концепция разработки через тестирование (TDD) стала настолько популярной, что почти каждая компания, занимающаяся разработкой программного обеспечения, имеет команду QA (обеспечение качества) для выполнения тестирования.

Тестирование является краеугольным камнем качества и отличной репутации OpenResty, но это также самая упускаемая из виду часть открытых проектов OpenResty. Многие разработчики ежедневно используют lua-nginx-module и иногда запускают flame graph, но сколько людей запускают тестовые случаи? Даже многие проекты на основе OpenResty не имеют тестовых случаев. Но открытый проект без тестовых случаев и непрерывной интеграции не заслуживает доверия.

Однако, в отличие от коммерческих компаний, в большинстве открытых проектов нет специальных инженеров по тестированию программного обеспечения, так как же они обеспечивают качество своего кода? Ответ прост: "автоматизация тестирования" и "непрерывная интеграция", ключевыми моментами которых являются автоматизация и непрерывность, и OpenResty достиг этого в максимальной степени.

У OpenResty есть 70 открытых проектов, и их модульное тестирование, интеграционное тестирование, тестирование производительности, mock-тестирование, фаззинг-тестирование и другие задачи сложно решить вручную только усилиями участников сообщества. Поэтому OpenResty с самого начала инвестировал больше в автоматизацию тестирования. Это может показаться замедлением проекта в краткосрочной перспективе, но можно сказать, что инвестиции в эту область очень окупаются в долгосрочной перспективе. Поэтому, когда я рассказываю другим инженерам о логике и наборе инструментов для тестирования OpenResty, они удивляются.

Давайте поговорим о философии тестирования OpenResty.

Концепция

test::nginx является ядром архитектуры тестирования OpenResty, который используется самим OpenResty и окружающими библиотеками lua-resty для организации и написания тестовых наборов. Это тестовая среда с очень высоким порогом входа. Причина в том, что, в отличие от обычных тестовых сред, test::nginx не основан на утверждениях и не использует язык Lua, что требует от разработчиков изучать и использовать test::nginx с нуля и переворачивать свои устоявшиеся знания о тестовых средах.

Я знаю нескольких участников OpenResty, которые могут отправлять код на C и Lua в OpenResty, но считают, что написание тестовых случаев с использованием test::nginx сложно. Они либо не знают, как их писать, либо как исправлять, когда сталкиваются с ошибками тестирования. Поэтому я называю test::nginx препятствием для вклада в код.

test::nginx сочетает в себе Perl, управление данными и DSL (язык, специфичный для предметной области). Для одного и того же набора тестов, управляя параметрами и переменными окружения, можно достичь различных эффектов, таких как случайное выполнение, многократное повторение, обнаружение утечек памяти, стресс-тестирование и т.д.

Установка и примеры

Прежде чем мы начнем использовать test::nginx, давайте узнаем, как его установить.

Что касается установки программного обеспечения в системе OpenResty, только официальный метод установки CI является наиболее своевременным и эффективным; другие способы установки всегда сталкиваются с различными проблемами. Поэтому я рекомендую вам использовать официальные методы в качестве справочного материала, где вы также можете найти установку и использование test::nginx. Есть четыре шага.

  1. Сначала установите менеджер пакетов Perl cpanminus.
  2. Затем установите test::nginx через cpanm.
sudo cpanm --notest Test::Nginx IPC::Run > build.log 2>&1 || (cat build.log && exit 1)
  1. Далее клонируйте последний исходный код.
git clone https://github.com/openresty/test-nginx.git
  1. Наконец, загрузите библиотеку test-nginx через команду Perl prove и запустите набор тестовых случаев в каталоге /t.
prove -Itest-nginx/lib -r t

После установки давайте рассмотрим самый простой тестовый случай в test::nginx. Следующий код адаптирован из официальной документации, и я удалил все пользовательские параметры управления.

use Test::Nginx::Socket 'no_plan'; run_tests(); __DATA__ === TEST 1: set Server --- config location /foo { echo hi; more_set_headers 'Server: Foo'; } --- request GET /foo --- response_headers Server: Foo --- response_body hi

Хотя test::nginx написан на Perl и работает как один из модулей, можете ли вы увидеть что-то на Perl или любом другом языке в приведенном выше тесте? Верно. Это потому, что test::nginx — это собственная реализация DSL автора на Perl, абстрагированная специально для тестирования NGINX и OpenResty.

Поэтому, когда мы впервые видим такой тест, мы, скорее всего, не понимаем его. Но не беспокойтесь; давайте проанализируем приведенный выше тестовый случай.

Прежде всего, use Test::Nginx::Socket;, это способ Perl ссылаться на библиотеки, так же как require в Lua. Это также напоминает нам, что test::nginx — это программа на Perl.

Вторая строка, run_tests(); — это функция Perl в test::nginx, входная функция для тестовой среды. Если вы хотите вызвать любые другие функции Perl в test::nginx, они должны быть размещены перед run_tests, чтобы быть действительными.

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

Следующий === TEST 1: set Server, заголовок тестового случая, указывает цель этого теста, и у него есть инструмент, который автоматически нумерует внутри.

--- config — это поле конфигурации NGINX. В приведенном выше случае мы использовали команды NGINX, а не Lua, и если вы хотите добавить код Lua, вы сделаете это здесь с директивой, такой как content_by_lua.

--- request используется для имитации терминала для отправки запроса, за которым следует GET /foo, который указывает метод и URI запроса.

--- response_headers, который используется для проверки заголовков ответа. Следующий Server: Foo указывает header и value, которые должны появиться в заголовках ответа. Если их нет, тест завершится неудачей.

Последний --- response_body, используется для проверки соответствующего тела. Следующий hi — это строка, которая должна появиться в теле ответа; если ее нет, тест завершится неудачей.

Ну вот, самый простой тестовый случай завершен. Таким образом, понимание тестового случая является предпосылкой для выполнения связанной с OpenResty разработки.

Напишите свои тестовые случаи

Далее, пришло время перейти к практическому тестированию. Помните, как мы тестировали сервер Memcached в прошлой статье? Верно; мы использовали resty для отправки запроса вручную, что представлено следующим кодом.

resty -e 'local memcached = require "resty.memcached" local memc, err = memcached:new() memc:set_timeout(1000) -- 1 sec local ok, err = memc:connect("127.0.0.1", 11212) local ok, err = memc:set("dog", 32) if not ok then ngx.say("failed to set dog: ", err) return end local res, flags, err = memc:get("dog") ngx.say("dog: ", res)'

Но разве отправка вручную не достаточно умна? Не беспокойтесь. Мы можем попытаться превратить ручные тесты в автоматизированные после изучения test::nginx. Например:

use Test::Nginx::Socket::Lua::Stream; run_tests(); __DATA__ === TEST 1: basic get and set --- config location /test { content_by_lua_block { local memcached = require "resty.memcached" local memc, err = memcached:new() if not memc then ngx.say("failed to instantiate memc: ", err) return end memc:set_timeout(1000) -- 1 sec local ok, err = memc:connect("127.0.0.1", 11212) local ok, err = memc:set("dog", 32) if not ok then ngx.say("failed to set dog: ", err) return end local res, flags, err = memc:get("dog") ngx.say("dog: ", res) } } --- stream_config lua_shared_dict memcached 100m; --- stream_server_config listen 11212; content_by_lua_block { local m = require("memcached-server") m.go() } --- request GET /test --- response_body dog: 32 --- no_error_log [error]

В этом тестовом случае я добавил --- stream_config, --- stream_server_config, --- no_error_log как элементы конфигурации, но они по сути одинаковы, т.е.

Данные и тестирование тестов упрощены, чтобы сделать читаемость и расширяемость лучше за счет абстрагирования конфигурации.

Это то, чем test::nginx принципиально отличается от других тестовых сред. Этот DSL — это обоюдоострый меч, который делает логику тестирования ясной и легко расширяемой. Однако он повышает стоимость обучения, требуя от вас заново изучать новый синтаксис и конфигурацию, прежде чем вы сможете начать писать тестовые случаи.

Итог

test::nginx мощный, но часто он может не подходить для вашего сценария. Зачем использовать пушку по воробьям? В OpenResty у вас также есть возможность использовать тестовую среду на основе утверждений busted. busted в сочетании с resty становится инструментом командной строки и также может удовлетворить многие потребности в тестировании.

Наконец, я оставлю вам вопрос. Можете ли вы запустить этот тест для Memcached локально? Если вы можете добавить новый тестовый случай, это будет замечательно.