一如大多数基于 Perl 的测试框架,`Test:Nginx` 依靠命令行工具 `prove` 来运行测试文件。

== Running Tests

Like most Perl-based testing frameworks, Test:Nginx relies on Perl’s prove command-line utility to run the test files. The prove utility is usually shipped with the standard perl distribution so we should already have it when we have perl installed.

一如大多数基于 Perl 的测试框架,Test:Nginx 依靠命令行工具 prove 来运行测试文件。 prove 工具通常随 perl 一同发布,所以在安装了 perl 之后,我们一般也有了 prove

Test::Nginx always invokes a real NGINX server and a real socket client to run the tests. It automatically uses the nginx program found in the system environment PATH. It is your responsibility to specify the right nginx in your PATH environment for the test suite. Usually we just specify the path of the nginx program inside the OpenResty installation tree. For example,

Test::Nginx 总是启动一个真实的 NGINX 服务器和真实的套接字客户端来运行测试。 它默认使用系统的环境变量 PATH 来搜索 nginx 程序。你需要保证在这个环境变量里面能够找到它。 通常我们会指定 OpenResty 安装包里面的 nginx 程序路径。举个例子,

[source,bash]

export PATH=/usr/local/openresty/nginx/sbin:$PATH —-

Here we assume that OpenResty is installed to the default prefix, i.e., /usr/local/openresty/.

这里我们假设 OpenResty 安装在默认路径,即 /usr/local/openresty/

You can always use the which command to verify if the PATH environment is indeed set properly:

你可以使用which 命令验证 PATH 环境变量是否设置正确:

[source,console]

$ which nginx /usr/local/openresty/nginx/sbin/nginx —-

For convenience, we usually wrap such environment settings in a custom shell script so that we do not risk polluting the system-wide or account-wide environment settings nor take on the burden of manually setting the environments manually for every shell session. For example, I usually have a local bash script named go in each project I work on. A typical go script might look like below

为了方便,我们通常把类似这样的环境变量设置封装在一个自定义 shell 脚本中, 在避免污染全局或本账户的环境变量的同时,也甩掉在每个 shell 会话中手工设置环境变量的负担。 举个例子,我通常在参与的每个项目中放置一个名为 go 的本地 bash 脚本。 这个脚本看上去大概是这个样子:

[source,bash]

#!/usr/bin/env bash

export PATH=/usr/local/openresty/nginx/sbin:$PATH

exec prove “$@”

Then we can use this ./go script to substitute the prove utility in any of the subsequent commands involving prove.

然后我们可以在要用到 prove 工具的场景中,用这个 ./go 脚本替换掉它。

Because Test::Nginx makes heavy use of environment variables for the callers to fine tune the testing behaviors (as we shall see in later sections), such shell wrapper scripts also make it easy to manage all these environment variable settings and hard to get things wrong.

由于调整 Test::Nginx 的测试行为重度依赖于对环境变量的使用(接下来我们会看到这一点), 这样的包装脚本也让相关环境变量的管理变得简单,让忙中出错变得困难。

NOTE: Please do not confuse the name of this bash script with Google’s Go programming language. It has nothing to do with the Go language in any way.

NOTE: 请不要把这个 bash 脚本的名字跟 Google 的 Go 编程语言弄混。 它跟 Go 语言风牛马不相及。

=== Running A Single File 运行单个测试

If you want to run a single test file, say, t/foo.t, then all you need to do is just typing the following command in your terminal.

假设你想要运行某个测试文件,比如说,t/foo.t,你所需的只是在终端敲入如下命令。

[source,bash]

prove t/foo.t —-

Here inside t/foo.t we employs the simple test file example presented in the previous section. We repeat the content below for the reader’s convenience.

t/foo.t 里的内容正是上一节展示的测试文件示例。我们将它列在下面,以便读者查看。

[source,test-base] .t/foo.t —- use Test::Nginx::Socket ‘no_plan’;

run_tests();

DATA

=== TEST 1: hello, world This is just a simple demonstration of the echo directive provided by ngx_http_echo_module. ngx_http_echo_module 提供的 echo 指令的一个简单演示。 — config location = /t { echo “hello, world!”; } — request GET /t — response_body hello, world! — error_code: 200 —-

It is worth mentioning that we could run the following command instead if we have a custom wrapper script called ./go for prove (as mentioned earlier in this section):

值得一提的是,假如我们用一个名为 ./go 的脚本包装了 prove(正如本节开头部分提到的),运行命令的方式会是这样:

[source,bash]

./go foo.t —-

When everything goes well, it generates an output like this:

如果一切顺利,会有如下输出:

…. t/foo.t .. ok All tests successful. Files=1, Tests=2, 0 wallclock secs (0.02 usr 0.01 sys + 0.08 cusr 0.03 csys = 0.14 CPU) Result: PASS ….

This is a very concise summary. The first line tells you all tests were passed while the second line gives you a summary of the number of test files (1 in this case), the number of tests (2 in this case), and the wallclock and CPU times used to run all the tests.

这段总结惜字如金。第一行告诉你所有测试都通过了,第二行则给出测试文件数(1)、测试数(2)以及运行全部测试所花费的墙上时间和CPU用时。

It is interesting to see that we have only one test block in the sample test file but in the test summary output by prove we see that the number of tests are 2. Why the difference? We can easily find it out by asking prove to generate a detailed test report for all the individual tests. This is achieved by passing the -v option (meaning “verbose”) to the prove command we used earlier:

有趣的是,虽然测试文件示例中只有一个测试块,但是 prove 输出的测试总结却显示测试数为2。 为什么会这样?让 prove 给所有测试生成一份详细的测试报告来揭示真相。 这需要给之前的 prove 命令添加 -v 选项(v 代表 verbose ):

[source,bash,linenums]

prove -v t/foo.t —-

Now the output shows all the individual tests performed in that test file:

现在可以在输出里看到测试文件中每个测试的运行情况:

…. t/foo.t .. ok 1 - TEST 1: hello, world - status code ok ok 2 - TEST 1: hello, world - response_body - response is expected (req 0) 1..2 ok All tests successful. Files=1, Tests=2, 0 wallclock secs (0.01 usr 0.01 sys + 0.07 cusr 0.03 csys = 0.12 CPU) Result: PASS ….

Obviously, the first test is doing the status code check, which is dictated by the error_code data section in the test block, and the second test is doing the response body check, required by the response_body section. Now the mystery is solved.

显而易见,第一个测试检查状态码,这是 error_code 数据节的命令; 而第二个测试检查响应体,则是 response_body 要求的。原来如此。

It is worth mentioning that the --- error_code: 200 section is automatically assumed when no error_code section is explicitly provided in the test block. So our test block above can be simplified by removing the --- error_code: 200 line without affecting the number of tests. This is because that checking 200 response status code is so common that Test::Nginx makes it the default. If you expect a different status code, like 500, then just add an explicit error_code section.

值得一提的是, 如果测试块中没有指定 error_code,默认会设置--- error_code: 200。 因此我们可以移除多余的 --- error_code: 200,而不影响测试的总数。 毕竟检查响应状态码是否为200是个常见的需求,所以 Test::Nginx 把它设置为默认行为。 如果你期望的是其他状态码,比如500,那么就加上一个 error_code 数据节。

From this example, we can see that one test block can contain multiple tests and the number of tests for any given test block can be determined or predicted by looking at the data sections performing output checks. This is important when we provide a “test plan” ourselves to the test file where a “test plan” is the exact number of tests we expect the current test file to run. If a different number of tests than the plan were actually run, then the test result would be considered malicious even when all the tests are passed successfully. Thus, a test plan adds a strong constraint on the total number of tests expected to be run. For our t/foo.t file here, however, we intentionally avoid providing any test plans by passing the 'no_plan' argument to the use statement that loads the Test::Nginx::Socket module. We will revisit the “test plan” feature and explain how to provide one in a later section.

从上面的例子里,我们可以看到一个测试块可以包含多个测试, 而它的测试数则取决于检查输出的数据节。 当我们需要给测试文件设定一个“测试计划”时,要记住,“测试计划”的值等于我们 期望 运行的测试数。 如果实际运行的测试数不同于计划的测试数,那么即使全部测试都通过了,测试结果也不会被接受。 所以,测试计划给期望运行的测试总数添加了一个强约束。 不过在 t/foo.t 里面,当 use 加载 Test::Nginx::Socket 的时候,我们传递了 'no_plan' 参数,借此避免对测试计划的设定。 我们会在后面继续讨论“测试计划”的概念,并展示如何设定它。

=== Running Multiple Files 运行多个文件

Running multiple test files are straightforward; just specify the file names on the prove command line, as in

运行多个文件的方式跟单个文件的差不多,像这样在 prove 命令中指定它们的名字即可:

[source,bash]

prove -v t/foo.t t/bar.t t/baz.t —-

If you want to run all the test files directly under the t/ directory, then using a shell wildcard can be handy:

如果你打算一口气运行 t/ 文件夹下的所有测试文件,需要借助通配符的力量:

[source,bash]

prove -v t/*.t —-

In case that you have sub-directories under t/, you can specify the -r option to ask prove to recursively traverse the while directory tree rooted at t/ to find test files:

如果在 /t 下有子目录,你可以指定 -r 选项,告诉 prove 从根目录 t/ 递归查找测试文件:

[source,bash]

prove -r t/ —-

This command is also the standard way to run the whole test suite of a project.

这个命令也是运行一个项目的所有测试用例的标准方法。

=== Running Individual Test Blocks 运行单个测试块

Test::Nginx makes it easy to run an individual test block in a given file. Just add the special data section ONLY to that test block you want to run individually and prove will skip all the other test blocks while running that test file. For example,

Test::Nginx 提供了运行给定文件的单个测试块的捷径。 仅需在要单独运行的测试块中添加 ONLY 数据节,prove 就会在运行测试文件时跳过其他测试块。 举个例子:

[source,test-base]

=== TEST 1: hello, world This is just a simple demonstration of the echo directive provided by ngx_http_echo_module. ngx_http_echo_module 提供的 echo 指令的一个简单演示。 — config location = /t { echo “hello, world!”; } — request GET /t — response_body hello, world! — ONLY —-

Now prove won’t run any other test blocks (if any) in the same test file.

现在 prove 不再会运行同一测试文件中的其他测试(如果有的话)。

This is very handy while debugging a particular test block. You can focus on one test case at a time without worrying about other unrelated test cases stepping in your way.

这一特性有助于你调试特定的测试块。你可以仅关注某一测试的运行结果,把其他不相关的用例都抛到九宵云外。

When using the link:http://www.vim.org/[Vim] editor, we can quickly insert a --- ONLY line to the test block we are viewing in the vim file buffer, and then type :!prove % in the command mode of vim without leaving the editor window. This works because vim automatically expands the special % placeholder with the path of the current active file being edited. This workflow is great since you never leave your editor window and you never have to type the title (or other IDs) of your test block nor the path of the containing test file. You can quickly jump between test blocks even across different files. Test-driven development usually demands very frequent interactions and iterations, and Test::Nginx is particularly optimized to speed up this process.

在使用 link:http://www.vim.org/[Vim] 编辑器时,我们可以插入一行 --- ONLY 到位于 vim 的当前文件缓冲区的测试块中, 然后在命令模式下输入 :!prove %,就能在不离开编辑器的同时运行测试。 这是因为 vim 会把 % 占位符展开成当前编辑的文件的路径。 这是个很棒的工作流,因为你既不需要切换编辑器界面,也不需要输入测试块标题(或其他ID)和测试文件的路径。你可以自如地在来自不同文件的测试块间切换。 测试驱动开发通常要求非常频繁地进行交互和迭代,而 Test::Nginx 正是优化了这一过程。

Sometimes you may forget to remove the --- ONLY line from some test files even after debugging, this will incorrectly skip all the other tests in those files. To catch such mistakes, Test::Nginx always reports a warning for files using the ONLY special section, as in

有时候你可能忘记在调试后移除 --- ONLY,导致文件中的其他测试没有被执行。 为了避免这种疏忽,Test::Nginx 总会对使用了 ONLY 数据节的测试文件发出提醒,像这样:

[source,console]

$ prove t/foo.t t/foo.t .. # I found ONLY: maybe you’re debugging? t/foo.t .. ok All tests successful. Files=1, Tests=2, 0 wallclock secs (0.01 usr 0.00 sys + 0.09 cusr 0.03 csys = 0.13 CPU) Result: PASS —-

This way it is much easier to identify any leftover --- ONLY lines.

现在找出遗留的 --- ONLY 行变得简单多了。

Similar to ONLY, Test::Nginx also provides the LAST data section to make the containing test block become the last test block being run in that test file.

类似于ONLYTest::Nginx 也提供了 LAST 数据节,可以让所在的测试块运行在测试文件的最后。

NOTE: The special data sections ONLY and LAST are actually features inherited from the Test::Base module.

NOTE: ONLYLAST 这样的特殊的数据节实际上都是来自于 Test::Base 模块的特性。

=== Skipping Tests 跳过测试

We can specify the special SKIP data section to skip running the containing test block unconditionally. This is handy when we write a test case that is for a future feature or a test case for a known bug that we haven’t had the time to fix right now. For example,

我们可以使用 SKIP 数据节无条件跳过所在的测试块。 当我们为了一个尚未完成的功能或 bug 修复写一个测试用例,就可以用上它。举个例子:

[source,test-base]

=== TEST 1: test for the future — config location /t { some_fancy_directive; } — request GET /t — response_body blah blah blah — SKIP —-

It is also possible to skip a whole test file in the prologue part. Just replace the use statement with the following form.

在序言部分跳过整个测试文件也是可行的。仅需把 use 语句替换成下面的形式。

[source,Perl]

use Test::Nginx::Socket skip_all => “some reasons”; —-

Then running the test file gives something like follows.

然后运行这个测试文件会有如下的输出。

…. t/foo.t .. skipped: some reasons ….

NOTE: It is also possible to conditionally skip a whole test file but it requires a little bit of Perl programming. Interested readers can try using a BEGIN {} before the use statement to calculate the value of the skip_all option on the fly.

NOTE: 有选择地跳过整个测试文件也是可能的,不过这需要一点 Perl 编程的技巧。 感兴趣的读者可以尝试在 use 语句前使用一个 BEGIN {} 即时计算出 skip_all 选项的值。

=== Test Running Order 测试运行的顺序

==== Test File Running Order 测试文件运行的顺序

Test files are usually run by the alphabetical order of their file names. Some people prefer explicitly controlling the running order of their test files by prefixing the test file names with number sequences like 001-, 002-, and etc.

测试文件通常按文件名的字母表顺序依序运行。 有些人会在测试名前面添加数字前缀,比如 001-002- 等等,来控制测试文件运行顺序。

The test suite of the link:https://github.com/openresty/lua-nginx-module#readme[ngx_http_lua] module follows this practice, for example, which has test file names like below

link:https://github.com/openresty/lua-nginx-module#readme[ngx_http_lua] 的测试套件就是这么做的,它的测试文件命名如下:

…. t/000-sanity.t t/001-set.t t/002-content.t t/003-errors.t … t/139-ssl-cert-by.t ….

Although the prove utility supports running test files in multiple parallel jobs via the -jN option, Test::Nginx does not really support this mode since all the test cases share exactly the same test server directory, t/servroot/, and the same listening ports, as we have already seen, while parallel running requires strictly isolated running environments for each individual thread of execution. One can still manually split the test files into different groups and run each group on a different (virtual) machine or an isolated environment like a Linux container.

尽管 prove 支持通过 -jN 选项并行运行多个测试,Test::Nginx 并不支持这种模式, 因为所有测试用例会用到同一个测试服务器目录 t/serroot/,监听同一个端口。 而并行运行测试需要给每个线程隔离的运行环境。 当然你还是可以把测试文件分到不同的组里,每个组在一个不同的虚拟/实体机或者隔离环境(如 Linux 容器)下运行。

==== Test Block Running Order 测试块运行的顺序

By default, the Test::Nginx scaffold shuffles the test blocks in each file and run them in a random order. This behavior encourages writing self-contained and independent test cases and also increases the chance of hitting a bug by actively mutating the relative running order of the test cases. This may, indeed, confuse new comers, coming from a more traditional testing platform.

默认情况下,Test::Nginx 脚手架会 乱序 运行每个文件中的测试块。 这一行为鼓励用户编写互不干扰、独立自主的测试用例,同时也通过变换用例的相对顺序提高找出 bug 的概率。 对于熟悉传统测试框架的新用户来说,这确实有点奇怪。

We can always disable this test block shuffling behavior by calling the Perl function, no_shuffle(), imported by the Test::Nginx::Socket module, before the run_tests() call in the test file prologue. For example,

在调用 run_tests() 之前调用 Perl 函数 no_shuffle(),我们可以关掉乱序运行测试的行为。 这个函数来自于 Test::Nginx::Socket 模块。举个例子,

[source,Perl]

use Test::Nginx::Socket ‘no_plan’;

no_shuffle(); run_tests();

DATA … —-

With the no_shuffle() call in place, the test blocks are run in the exact same order as their appearance in the test file.

由于 no_shuffle() 的调用,测试块将严格按照在测试文件中定义的顺序运行。