背景信息
Apache APISIX 是 Apache 软件基金会下的云原生 API 网关,它兼具动静、实时、高性能等特点,提供了负载平衡、动静上游、灰度公布(金丝雀公布)、服务熔断、身份认证、可观测性等丰盛的流量治理性能。你能够应用 APISIX 来解决传统的南北向流量,也能够解决服务间的东西向流量。同时,它也反对作为 K8s Ingress Controller 来应用。
通常状况下,想要保障软件失常运行,在软件上线前咱们个别会应用各种技术和办法,通过手动或主动的形式对软件的性能进行查看以确保其运行失常。该操作咱们称之为 QA(测试)。测试个别分为单元测试、E2E 测试以及混沌测试。
单元测试是用来查看繁多模块的正确性(比方查看某个 RPC 的序列化 / 反序列化、数据加解密是否失常),然而该测试不足对系统的全局视角。而 E2E 测试(即端到端测试),能够补足单元测试的有余,该测试将整个零碎和内部依赖服务跑起来,通过实在的软件调用形式查看本零碎与其余零碎的集成状况;混沌测试则通过在零碎各组件间制作突发状况,如 OOM Kill、网络中断等,测试整个零碎谬误谬误的容忍水平与能力。APISIX 的测试更加偏差于 E2E 测试,保障本身性能和与其余系统集成的正确性。
APISIX 测试案例简介
APISIX 作为寰球最沉闷的 API 网关,其稳定性及服务的健壮性须要失去肯定的保障,那么如何防止 APISIX 中潜在的谬误呢?这里就须要通过测试用例来实现了。
测试脚本并不仅仅是一个被测试机器执行的程序文件,对于开发者来讲,能够通过测试脚本实现软件所有性能的测试,包含不同配置、不同输出参数等状况下程序的运行状况。而对于用户来说测试提供了某一个功能模块的具体应用示例,例如:程序能够承受的配置和输出,想要失去怎么的输入后果。用户在参考应用文档时遇到不懂的中央,齐全能够参考现有的测试用例,寻找是否有相似的应用场景。
在 APISIX 我的项目中,通常应用 Github Action 运行 CI 测试,执行下图展现的测试脚本。许多 APISIX 的开发者在编写测试用例时,会遇到各种各样的问题。心愿通过本文,能够缩小你在编写 APISIX 测试案例时呈现的谬误。
编写测试案例
APISIX 的测试用例基于 Test::NGINX
测试框架编写,该测试框架是在 Perl 语言根底上实现的测试环境,能够提供基于脚本的自动化测试能力,为 APISIX 以后如此大规模的测试与质量保证工作提供了反对。当然你不会应用 Perl 也没有关系,因为大部分场景中是不须要编写 Perl 代码的,仅应用 TEST::NGINX
封装的能力即可,如果有非凡需要能够联合 Lua 代码的形式进行加强。
APISIX 的测试用例均寄存在 ./apisix/t
目录上面,接下来将以增加 opa
插件为例为你介绍如何编写测试案例。
- 你须要创立一个
.t
结尾的测试文件,例如./t/plugin/opa.t
。如果你是在已有性能上增加个性,能够间接在对应的测试文件中增加测试用例。并在文件中增加固定格局的 ASF 2.0 协定。 - 该局部次要作用是给
opa.t
这个文件中所有的测试用例主动增加no_error_log
,这样就不须要在每个代码下增加error_log
相干的代码了,你能够间接复制应用这段代码。通过这种形式,能够缩小一些反复的代码。
add_block_preprocessor(sub {my ($block) = @_;
if ((!defined $block->error_log) && (!defined $block->no_error_log)) {$block->set_value("no_error_log", "[error]");
}
if (!defined $block->request) {$block->set_value("request", "GET /t");
}
});
run_tests();
DATA
- 每个测试用例都有固定的结尾,个别格局如下。
=== TEST 1: sanity
===
测试用例起始的固定语法结构,TEST
` `1
则代表是本文件的第一个测试用例。sanity
则为该测试的名称。个别以测试用例的具体目标命名。
- 接下来就是测试用例的注释局部了。在 APISIX 中简直每个插件都会定义一些参数和属性,并且会事后定义 JSON schema,因而咱们须要先查看该插件的输出参数是否可能失常地校验,通过这种形式就能够查看咱们输出的数据是否能够正确地被 JSON schema 的规定去校验。
--- config
location /t {
content_by_lua_block {
local test_cases = {{host = "http://127.0.0.1:8181", policy = "example/allow"},
{host = "http://127.0.0.1:8181"},
{host = 3233, policy = "example/allow"},
}
local plugin = require("apisix.plugins.opa")
for _, case in ipairs(test_cases) do
local ok, err = plugin.check_schema(case)
ngx.say(ok and "done" or err)
end
}
}
--- response_body
done
property "policy" is required
property "host" validation failed: wrong type: expected string, got number
通过浏览 opa
插件的源码,能够看到 opa
插件要求 host
和 policy
必须同时存在,因而此处须要定义三个规定。
- 输出正确的参数,包含
host
和policy
。因而返回后果将是done
; - 仅输出
host
参数。不合乎host
、policy
同时存在的要求,所以该测试预期返回后果将是property "policy" is required
; - 输出类型谬误(整数)的
host
值。因为在源码中设置了host
参数必须是字符串类型,所以返回后果将是property "host" validation failed: wrong type: expected string, got number
。
--- config
location /t {
content_by_lua_block {
...
ngx.say(ok and "done" or err)
end
}
}
--- response_body
done
...
个别状况下,每个测试案例中须要应用 /t
的函数,比如说你须要调用 Lua 代码,定义 location
,你能够应用 content_by_lua_block
的形式调用一些代码辅助测试,最初将响应信息以 ngx.say
的形式打印进去,而后再通过 --- response_body
的形式查看下面程序运行的是否正确。无需手动输出 request
和 error
,因为咱们曾经通过脚本主动增加了。
local plugin = require("apisix.plugins.opa")
for _, case in ipairs(test_cases) do
local ok, err = plugin.check_schema(case)
ngx.say(ok and "done" or err)
以上代码示意导入 APISIX Plugin opa
插件的模块,并且调用 plugin.check_schema
函数。而后通过 for
循环,以此调用参数,并且依据测试状况返回对应的后果。
- 接下来,咱们须要配置一个测试时应用的环境。对于插件来说,就是创立一个路由,而后把插件关联到该路由上。创立实现后,咱们就能够通过发送申请校验插件的外部逻辑实现是否正确。
=== TEST 2: setup route with plugin
--- config
location /t {
content_by_lua_block {local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"opa": {
"host": "http://127.0.0.1:8181",
"policy": "example"
}
},
"upstream": {
"nodes": {"127.0.0.1:1980": 1},
"type": "roundrobin"
},
"uris": ["/hello", "/test"]
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
在以上示例中,应用了 lib_test_admin
导入到 t
函数,创立一个 id
为 1
的路由,而后应用 PUT 的办法,传入这些数据。在该测试中,咱们并没有对数据格式进行测验,因为该测试用例只有保障应用 Admin API 能够失常创立路由就能够了,当然咱们也须要对异样进行判断,如果状态码大于或者等于 300
就会打印出具体的信息。
在 APISIX 的测试案例中,你会发现很多测试中都蕴含了以下代码:
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT)
以上代码示意导入 lib.test_admin
模块并应用其中 test 函数封装来发送申请,它缩小了咱们调用 APISIX Admin API 等 HTTP 接口时的反复代码,只需简略调用并查看返回后果即可。
- 在第三个测试中,咱们不须要再反复创立路由,因为在第二个测试中曾经创立了。
=== TEST 3: hit route (with correct request)
--- request
GET /hello?test=1234&user=none
--- more_headers
test-header: only-for-test
--- response_body
hello world
上述示例定义了 request
。因为咱们在上一个测试中定义了 /hello
和 /
`test 的门路,所以咱们能够通过
GET 的办法向
/`hello
发送申请,并而后它会发送一个 test=1234
和 user=none
的 query
参数。你也能够通过 more_headers
的形式增加响应头,比方给发送到 hello
的申请增加一个叫 test-header
的响应头。
以上测试是为了测试增加正确的申请头是否能够胜利。你也能够在后续的测试示例中增加谬误头,并验证后果。如果你须要应用 POST
申请,也能够把上述代码中的 GET
/hello
批改为 POST
/hello
。
在有些测试中,你可能须要创立多个上游或者路由,此时你就能够定义一个数组,而后在数组中定义这些对应的值,并通过 for
形式循环地调用 t
函数,而后让它把这个货色失常地通过 put 的形式来调用 APISIX 的 Admin API 接口来失常创立路由或者上游。该形式也是测试比拟常见的形式,称为:Table driving test,是一种通过表的形式驱动测试的办法,该办法缩小局部反复呈现的代码,比方 opa2.t。具体介绍,请点击浏览原文参考 APISIX 测试案例疾速入门视频。
运行测试案例
测试案例须要源码装置 APISIX,接下来将重点介绍如何运行测试案例及报错查看。
本地运行
通常状况下,你能够在本地应用以下指令来运行测试案例:
PATH=/usr/local/openresty/nginx/sbin:/usr/bin PERL5LIB=.:$PERL5LIB FLUSH_ETCD=1 prove -Itest-nginx/lib -r t/admin
PATH
指定了openresty/nginx
所在的目录,能够防止局部环境配置谬误时引起的抵触问题,如果环境中的 OpenResty 装置在其余地位,则也能够通过这个命令进行指定。PERL5LIB
指定了应用 Perl 导入到本地。导入本门路中存在的以及局部通过环境变量附加的 PERL 库。FLUSH_ETCD
指定了在每个测试文件执行实现后,则清空所有数据,它须要调用 etcdctl 函数,须要确保在 PATH 中能够找到etcdctl
可执行文件。prove
调用测试程序开始执行测试。-Itest-nginx/lib
示意导入Itest-nginx/lib
这个库。-r
示意主动寻找测试文件。如果指定的是一个门路,则会寻找这个门路下所有的测试文件。
以下为上述命令的失常执行后果。
t/admin
示意指定测试用例搜寻门路,此处也能够指定到惟一一个.t
文件上进行限定。
如果测试失败,则会呈现以下信息:
以上信息则会通知你具体是哪个测试文件中的哪个测试用例执行失败了。
Github 运行
个别状况在 Github 中提交代码时,输入的后果和在本地测试类似。
首先抉择谬误的执行工作流,次要的测试用例均在 build 系列 CI 中。
咱们能够看到,在该示例中,416
行呈现了报错。通过错误信息,咱们能够失去在某个测试文件中的某个测试用例呈现谬误,开发者定向查看修改即可。须要留神的是,CI 中可能存在一些奇怪的报错,它们可能是因为 CI 环境的长期异样导致的,如果未修改过对应模块中的代码,能够疏忽这些谬误。
总结
本文次要为大家介绍了测试的相干流程,以及在 APISIX 测试案例的形成和如何进行测试案例的编写,心愿通过本文你能够对 APISIX 的测试案例有一个大抵的意识。
本文中只提到了 APISIX 测试框架中的一些核心内容,未能笼罩 TEST::NGINX 框架中的全部内容,实际上 TEST::NGINX 中还有很多弱小的能力,咱们能够通过 Test::Nginx::Socket 的文档理解更多用法。如果你想学习更多编写测试案例的常识,能够查看 APISIX 测试案例疾速入门视频。