---
title: "Методы тестирования `test::nginx`: конфигурация, отправка запросов и обработка ответов - API7.ai"
description: "В этой статье описывается, как использовать test::nginx для отправки запросов и обработки ответов в тестовом сценарии."
url: "https://api7.ai/ru/learning-center/openresty/all-inclusive-testing-methods-test-nginx"
---

# Методы тестирования \`test::nginx\`: конфигурация, отправка запросов и обработка ответов

API7.ai

November 18, 2022

[OpenResty (NGINX + Lua)](https://api7.ai/learning-center/openresty)

В предыдущей статье мы уже получили первое представление о `test::nginx` и запустили самый простой пример. Однако в реальных проектах с открытым исходным кодом тестовые случаи, написанные на `test::nginx`, гораздо сложнее и труднее для освоения, чем примеры кода. Иначе это не называлось бы препятствием.

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

Тестирование `test::nginx` по сути генерирует `nginx.conf` и запускает процесс NGINX на основе конфигурации каждого тестового случая. Затем оно имитирует клиентский запрос с указанным телом запроса и заголовками. Далее код Lua в тестовом случае обрабатывает запрос и формирует ответ. В этот момент `test::nginx` анализирует критическую информацию, такую как тело ответа, заголовки ответа и логи ошибок, и сравнивает их с тестовой конфигурацией. Если есть расхождение, тест завершается с ошибкой; в противном случае он считается успешным.

`test::nginx` предоставляет множество примитивов DSL (Domain-specific language). Я сделал простую классификацию в соответствии с настройкой NGINX, отправкой запросов, обработкой ответов и проверкой логов. Эти 20% функциональности покрывают 80% сценариев применения, поэтому мы должны твердо их освоить. Что касается других более продвинутых примитивов и их использования, мы представим их в следующей статье.

## Конфигурация NGINX

Давайте сначала рассмотрим конфигурацию NGINX. Примитив `test::nginx` с ключевым словом "config" связан с конфигурацией NGINX, например, `config`, `stream_config`, `http_config` и т.д.

Их функции одинаковы: вставить указанную конфигурацию NGINX в различные контексты NGINX. Эти конфигурации могут быть либо командами NGINX, либо кодом Lua, заключенным в `content_by_lua_block`.

При выполнении модульных тестов `config` является наиболее часто используемым примитивом, в котором мы загружаем библиотеки Lua и вызываем функции для тестирования "белого ящика". Вот фрагмент тестового кода, который не может быть полностью запущен. Он взят из реального проекта с открытым исходным кодом, поэтому, если вам интересно, вы можете перейти по [ссылке](https://github.com/apache/apisix/blob/master/t/plugin/key-auth.t#L11), чтобы увидеть полный тест, или попробовать запустить его локально.

```
=== TEST 1: sanity
--- config
    location /t {
        content_by_lua_block {
            local plugin = require("apisix.plugins.key-auth")
            local ok, err = plugin.check_schema({key = 'test-key'})
            if not ok then
                ngx.say(err)
            end
            ngx.say("done")
        }
    }
```

Цель этого тестового случая — проверить, правильно ли работает функция `check_schema` в файле кода `plugins.key-auth`. В нем используется команда NGINX `content_by_lua_block` в `location /t`, чтобы загрузить тестируемый модуль и напрямую вызвать функцию, которую нужно проверить.

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

## Отправка запросов

Имитация отправки запроса клиентом включает множество деталей, поэтому начнем с самой простой — отправки одного запроса.

### `request`

Продолжая с вышеуказанным тестовым случаем, если мы хотим, чтобы код модульного теста был выполнен, то мы должны инициировать HTTP-запрос на адрес `/t`, указанный в конфигурации, как показано в следующем тестовом коде:

```
--- request
GET /t
```

Этот код отправляет `GET`-запрос на `/t` в примитиве запроса. Здесь мы не указываем IP-адрес, доменное имя или порт доступа, а также не указываем, используется ли `HTTP 1.0` или `HTTP 1.1`. Все эти детали скрыты `test::nginx`, поэтому нам не нужно о них заботиться. Это одно из преимуществ DSL — нам нужно сосредоточиться только на бизнес-логике, не отвлекаясь на все детали.

Кроме того, это обеспечивает частичную гибкость. Например, по умолчанию используется протокол `HTTP 1.1`, или если мы хотим протестировать `HTTP 1.0`, мы можем указать это отдельно:

```
--- request
GET /t  HTTP/1.0
```

Помимо метода `GET`, также необходимо поддерживать метод `POST`. В следующем примере мы можем отправить строку `hello world` на указанный адрес.

```
--- request
POST /t  
hello world
```

Опять же, `test::nginx` автоматически вычисляет длину тела запроса и добавляет заголовки запроса `host` и `connection`, чтобы гарантировать, что это нормальный запрос.

Конечно, мы можем добавить комментарии, чтобы сделать код более читаемым. Строки, начинающиеся с `#`, будут распознаны как комментарии к коду.

```
--- request
# post request
POST /t  
hello world
```

Запрос также поддерживает более сложный и гибкий режим, который использует `eval` в качестве фильтра для непосредственного встраивания кода Perl, поскольку `test::nginx` написан на Perl. Если текущий язык DSL не удовлетворяет вашим потребностям, `eval` — это "последний аргумент" для выполнения кода Perl напрямую.

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

```
--- request eval
"POST /t
hello\x00\x01\x02
world\x03\x04\xff"
```

В первом примере мы используем `eval` для указания непечатаемых символов, что является одним из его применений. Содержимое между двойными кавычками будет обработано как строка Perl и затем передано в `request` в качестве аргумента.

Вот более интересный пример:

```
--- request eval
"POST /t\n" . "a" x 1024
```

Однако, чтобы понять этот пример, нам нужно знать кое-что о строках в Perl, поэтому я кратко упомяну два момента.

- В Perl мы используем точку для обозначения конкатенации строк. Не напоминает ли это вам две точки в `Lua`?
- Строчная буква `x` указывает количество повторений символа. Например, `"a" x 1024` выше означает, что символ "a" повторяется 1024 раза.

Таким образом, второй пример означает, что метод `POST` отправляет запрос, содержащий `1024` символа `a`, на адрес `/t`.

### `pipelined_requests`

После понимания того, как отправить один запрос, давайте рассмотрим, как отправить несколько запросов. В `test::nginx` мы можем использовать примитив `pipelined_requests` для отправки нескольких запросов последовательно в рамках одного соединения `keep-alive`:

```
--- pipelined_requests eval
["GET /hello", "GET /world", "GET /foo", "GET /bar"]
```

Например, этот пример будет последовательно обращаться к этим четырем API в одном соединении. Это имеет два преимущества:

- Первое — можно устранить множество повторяющегося тестового кода, и четыре тестовых случая можно сжать в один.
- Второе и самое важное — мы можем использовать pipelined-запросы для обнаружения, будет ли логика кода вызывать исключения в случае множественных обращений.

Вы можете задаться вопросом: если я напишу несколько тестовых случаев последовательно, то код также будет выполнен несколько раз на этапе выполнения. Разве это не покрывает вторую проблему выше?

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

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

### `repeat_each`

Мы только что упомянули случай тестирования нескольких запросов, так как же выполнить один и тот же тест несколько раз?

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

Естественно, мы можем установить ее перед функцией `run_test()`, например, изменив аргумент на `2`.

```
repeat_each(2);
run_tests();
```

Тогда каждый тестовый случай будет выполнен дважды, и так далее.

### `more_headers`

После обсуждения тела запроса давайте рассмотрим заголовки запроса. Как мы упоминали выше, `test::nginx` по умолчанию отправляет запрос с заголовками `host` и `connection`. А как насчет других заголовков запроса?

`more_headers` специально предназначен для этого.

```
--- more_headers
X-Foo: blah
```

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

```
--- more_headers
X-Foo: 3
User-Agent: openresty
```

## Обработка ответов

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

### `response_body`

Аналогом примитива запроса является `response_body`, и ниже приведен пример их двух конфигураций в использовании:

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

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

```
--- response_body_like
^he\w+$
```

Это позволяет вам быть очень гибкими с телом ответа. Кроме того, `test::nginx` также поддерживает операции `unlike`:

```
--- response_body_unlike
^he\w+$
```

В этом случае, если тело ответа — `hello`, тест не будет пройден.

По аналогии, после понимания обнаружения одного запроса, давайте рассмотрим обнаружение нескольких запросов. Вот пример использования с `pipelined_requests`:

```
--- pipelined_requests eval
["GET /hello", "GET /world", "GET /foo", "GET /bar"]
--- response_body eval
["hello", "world", "oo", "bar"]
```

Конечно, важно отметить, что сколько запросов вы отправляете, столько ответов вам нужно для соответствия.

### `response_headers`

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

```
--- response_headers
X-RateLimit-Limit: 2
X-RateLimit-Remaining: 1
```

Как и обнаружение тела ответа, заголовки ответа также поддерживают регулярные выражения и операции `unlike`, такие как `response_headers_like`, `raw_response_headers_like` и `raw_response_headers_unlike`.

### `error_code`

Третий — это код ответа. Обнаружение кода ответа поддерживает прямое сравнение и также поддерживает операции `like`, как в следующих двух примерах:

```
--- error_code: 302
```

```
--- error_code_like: ^(?:500)?$
```

В случае нескольких запросов `error_code` нужно проверять несколько раз:

```
--- pipelined_requests eval
["GET /hello", "GET /hello", "GET /hello", "GET /hello"]
--- error_code eval
[200, 200, 503, 503]
```

### `error_log`

Последний тестовый элемент — это лог ошибок. В большинстве тестовых случаев лог ошибок не генерируется. Мы можем использовать `no_error_log` для обнаружения:

```
--- no_error_log
[error]
```

В приведенном выше примере, если строка `[error]` появляется в `error.log` NGINX, тест завершится неудачей. Это очень распространенная функция, и рекомендуется добавлять обнаружение лога ошибок во все ваши нормальные тесты.

```
--- error_log
hello world
```

Приведенная выше конфигурация обнаруживает наличие `hello world` в `error.log`. Конечно, вы можете использовать `eval`, встроенный в код Perl, для реализации обнаружения с помощью регулярных выражений, как в следующем примере:

```
--- error_log eval
qr/\[notice\] .*?  \d+ hello world/
```

## Итог

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

Наконец, вот вопрос для размышления: Каковы преимущества и недостатки абстрактного DSL `test::nginx`? Не стесняйтесь оставлять комментарии и обсуждать со мной, а также вы можете поделиться этой статьей, чтобы общаться и думать вместе.

Share article link

Share

Related Posts

[![OpenResty FAQ | Сетевая структура для тестирования, функции, связанные с SSL, DSL, инструмент \`ab\`](https://static.api7.ai/2022/12/02/63896c177fde1.png)](https://api7.ai/learning-center/openresty/faq-3)

## [OpenResty FAQ | Сетевая структура для тестирования, функции, связанные с SSL, DSL, инструмент \`ab\`](https://api7.ai/learning-center/openresty/faq-3)

[![Малоизвестные возможности использования \`test::nginx\`](https://static.api7.ai/2022/11/25/63802a7fb4697.png)](https://api7.ai/learning-center/openresty/little-known-usage-of-test-nginx)

## [Малоизвестные возможности использования \`test::nginx\`](https://api7.ai/learning-center/openresty/little-known-usage-of-test-nginx)

Table of Contents

- [Конфигурация NGINX](#%D0%BA%D0%BE%D0%BD%D1%84%D0%B8%D0%B3%D1%83%D1%80%D0%B0%D1%86%D0%B8%D1%8F-nginx)

- [Отправка запросов](#%D0%BE%D1%82%D0%BF%D1%80%D0%B0%D0%B2%D0%BA%D0%B0-%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%BE%D0%B2)

  - [`request`](#request)
  - [`pipelined_requests`](#pipelined_requests)
  - [`repeat_each`](#repeat_each)
  - [`more_headers`](#more_headers)

- [Обработка ответов](#%D0%BE%D0%B1%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0-%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D0%BE%D0%B2)

  - [`response_body`](#response_body)
  - [`response_headers`](#response_headers)
  - [`error_code`](#error_code)
  - [`error_log`](#error_log)

- [Итог](#%D0%B8%D1%82%D0%BE%D0%B3)
