Малоизвестные возможности использования `test::nginx`

API7.ai

November 24, 2022

OpenResty (NGINX + Lua)

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

Но если вы заинтересованы в том, чтобы стать участником разработки OpenResty, или если вы используете test::nginx для написания тестовых случаев в своих проектах, то вам нужно изучить более продвинутое и сложное использование.

Сегодняшняя статья, вероятно, будет самой "непопулярной" частью серии, потому что это то, о чем никто раньше не делился. Возьмем, к примеру, lua-nginx-module, основной модуль в OpenResty, который имеет более 70 участников по всему миру, но не каждый участник писал тестовый случай. Так что, если вы прочитаете сегодняшнюю статью, ваше понимание test::nginx войдет в топ-100 в мире.

Отладка в тестах

Сначала рассмотрим некоторые из самых простых и наиболее часто используемых разделов, которые разработчики используют при обычной отладке. Здесь мы рассмотрим сценарии использования этих разделов, связанных с отладкой.

ONLY

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

Так есть ли способ выполнить только один из указанных вами тестовых случаев? Это легко сделать с помощью раздела ONLY.

=== TEST 1: sanity === TEST 2: get --- ONLY

Вышеприведенный псевдокод показывает, как использовать этот раздел. Поместив --- ONLY в последнюю строку тестового случая, который нужно выполнить отдельно, при использовании prove для запуска файла тестового случая все остальные тестовые случаи будут проигнорированы, и будет выполнен только этот один тест.

Однако это подходит только для отладки. Поэтому, когда команда prove находит раздел ONLY, она также напомнит вам не забыть удалить его при коммите вашего кода.

SKIP

Требование, соответствующее выполнению только одного тестового случая, — это игнорирование определенного тестового случая. Раздел SKIP, который обычно используется для тестирования функциональности, которая еще не реализована:

=== TEST 1: sanity === TEST 2: get --- SKIP

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

LAST

Еще один распространенный раздел — это LAST, который также прост в использовании, так как тестовые случаи перед ним будут выполнены, а те, что после него, будут проигнорированы.

=== TEST 1: sanity === TEST 2: get --- LAST === TEST 3: set

Вы можете задаться вопросом, я понимаю важность ONLY и SKIP, но в чем польза LAST? На самом деле, иногда ваши тестовые случаи имеют зависимости, и вам нужно выполнить первые несколько тестовых случаев, чтобы последующие тесты имели смысл. Поэтому в этом случае LAST очень полезен при продолжении отладки.

plan

Из всех функций test::nginx, plan является одной из самых раздражающих и трудных для понимания. Он происходит из модуля Perl Test::Plan, документация которого не входит в test::nginx, и найти объяснение этого нелегко. Поэтому я расскажу об этом в начале. Я видел несколько участников разработки OpenResty, которые попадали в эту ловушку и не могли выбраться.

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

plan tests => repeat_each() * (3 * blocks());

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

Для этого примера, если значение repeat_each равно 2, а тестовых случаев 10, то значение plan должно быть 2 x 3 x 10 = 60. Единственное, что может вызвать у вас замешательство, — это значение числа 3, которое выглядит как магическое число!

Не волнуйтесь, давайте продолжим рассматривать пример, и вы сможете разобраться в этом через мгновение. Сначала, можете ли вы определить правильное значение plan в следующем тестовом случае?

=== TEST 1: sanity --- config location /t { content_by_lua_block { ngx.say("hello") } } --- request GET /t --- response_body hello

Я уверен, что все бы заключили, что plan = 1, так как тест проверяет только response_body.

Но это не так! Правильный ответ — plan = 2. Почему? Потому что test::nginx имеет неявную проверку, т.е. --- error_code: 200, которая по умолчанию проверяет, является ли HTTP-код ответа 200.

Таким образом, магическое число 3 выше действительно означает, что каждый тест явно проверяется дважды, например, для body и error log, и неявно для response code.

Поскольку это так легко ошибиться, я рекомендую вам отключить plan, используя следующий метод.

use Test::Nginx::Socket 'no_plan';

Если вы не можете отключить его, например, если вы столкнулись с неточным plan в официальном наборе тестов OpenResty, рекомендуется не углубляться в причину, а просто добавить или вычесть числа из выражений plan.

plan tests => repeat_each() * (3 * blocks()) + 2;

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

Препроцессор

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

В этом случае вы можете использовать директиву add_block_preprocessor, чтобы добавить фрагмент кода на Perl, например, следующий:

add_block_preprocessor(sub { my $block = shift; if (!defined $block->config) { $block->set_value("config", <<'_END_'); location = /t { echo $arg_a; } _END_ } });

Этот препроцессор добавляет раздел config ко всем тестовым случаям, и содержимое — это location /t, так что в ваших последующих тестовых случаях вы можете опустить config и получить доступ напрямую.

=== TEST 1: --- request GET /t?a=3 --- response_body 3 === TEST 2: --- request GET /t?a=blah --- response_body blah

Пользовательские функции

В дополнение к добавлению кода на Perl в препроцессор, вы также можете произвольно добавлять функции на Perl, или пользовательские функции, как мы их называем, перед функцией run_tests.

Вот пример, который добавляет функцию, читающую файл, и сочетает ее с директивой eval, чтобы реализовать POST файла:

sub read_file { my $infile = shift; open my $in, $infile or die "cannot open $infile for reading: $!"; my $content = do { local $/; <$in> }; close $in; $content; } our $CONTENT = read_file("t/test.jpg"); run_tests; __DATA__ === TEST 1: sanity --- request eval "POST /\n$::CONTENT"

Перемешивание

В дополнение к вышесказанному, test::nginx имеет малоизвестную ловушку: по умолчанию он выполняет тестовые случаи в случайном порядке, а не в порядке и нумерации тестовых случаев.

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

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

Поэтому мой совет: пожалуйста, отключите эту функцию. Вы можете отключить ее с помощью следующих двух строк кода:

no_shuffle(); run_tests;

В частности, no_shuffle используется для отключения рандомизации и позволяет тестам выполняться строго в порядке тестовых случаев.

reindex

Набор тестовых случаев OpenResty имеет строгие требования к форматированию. Каждый тестовый случай должен быть разделен тремя новыми строками, и нумерация тестовых случаев должна строго возрастать.

К счастью, у нас есть автоматический инструмент, reindex, чтобы делать эту утомительную работу, который скрыт в проекте openresty-devel-utils. Так как документации о нем нет, только несколько человек знают о нем.

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

Итог

На этом заканчивается введение в test::nginx. Конечно, есть еще больше функций, мы только рассказали о самых основных и важных. "Дай человеку рыбу, и ты накормишь его на день; научи его ловить рыбу, и ты накормишь его на всю жизнь." Я научил вас основным методам и предосторожностям для изучения тестирования, тогда вы можете углубиться в официальный набор тестовых случаев для лучшего понимания.

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