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
程序路径。举个例子,
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
环境变量是否设置正确:
$ 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 脚本。
这个脚本看上去大概是这个样子:
#!/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 语言风牛马不相及。 |
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
,你所需的只是在终端敲入如下命令。
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
里的内容正是上一节展示的测试文件示例。我们将它列在下面,以便读者查看。
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
(正如本节开头部分提到的),运行命令的方式会是这样:
./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
):
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 test files are straightforward; just specify the file
names on the prove
command line, as in
运行多个文件的方式跟单个文件的差不多,像这样在 prove
命令中指定它们的名字即可:
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/
文件夹下的所有测试文件,需要借助通配符的力量:
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/
递归查找测试文件:
prove -r t/
This command is also the standard way to run the whole test suite of a project.
这个命令也是运行一个项目的所有测试用例的标准方法。
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
就会在运行测试文件时跳过其他测试块。
举个例子:
=== 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 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.
在使用 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
数据节的测试文件发出提醒,像这样:
$ 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.
类似于`ONLY`,Test::Nginx
也提供了 LAST
数据节,可以让所在的测试块运行在测试文件的最后。
Note
|
The special data sections ONLY and LAST are actually features
inherited from the Test::Base module.
|
Note
|
ONLY 和 LAST 这样的特殊的数据节实际上都是来自于 Test::Base 模块的特性。
|
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 修复写一个测试用例,就可以用上它。举个例子:
=== 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
语句替换成下面的形式。
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 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 ngx_http_lua module follows this practice, for example, which has test file names like below
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 容器)下运行。
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
模块。举个例子,
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()
的调用,测试块将严格按照在测试文件中定义的顺序运行。