14. Common Test Hooks
14个通用测试钩
14.1总则
在通用测试钩(星期三)
框架允许的默认行为扩展Common Test
之前,所有测试套件来电之后使用挂钩。CTH允许高级Common Test
用户抽象出多个测试套件共同的行为,而不用乱丢库调用的所有测试套件。这可用于记录,启动和监视外部系统,构建测试所需的C文件等。
简而言之,CTH允许您执行以下操作:
- 在每个套件配置调用之前操作运行时配置。
- 操作所有套件配置调用的返回,在扩展中,操作测试本身的结果。
以下各节介绍如何在运行CTH时使用CTH,以及如何在CTH中处理测试结果。
警告
在CTH内执行时,所有时间限制都会关闭。所以如果你的CTH永远不会返回,整个测试运行就会停止。
14.2安装CTH
在您的测试运行中可以以多种方式安装CTH。您可以针对运行中的所有测试,特定测试套件以及测试套件中的特定组执行此操作。如果您希望在您的测试运行中的所有测试套件中都存在CTH,则有三种方法可以实现,如下所示:
-ct_hooks
作为参数添加到ct_run
。要使用此方法添加多个CTH,请使用关键字将它们附加到彼此and
,即ct_run -ct_hooks cth1 [{debug,true}] and cth2 ...
。
ct_hooks
为你的标签添加标签Test Specification
。
ct_hooks
为您的呼叫添加标签ct:run_test/1
。
CTH也可以在测试套件中添加。这是通过返回完成{ct_hooks,[CTH]}
从配置列表suite/0
,init_per_suite/1
或init_per_group/2
。
在这种情况下,CTH
可以只是CTH
的模块名称,也可以是具有模块名称和初始参数的元组,也可以是CTH
的钩子优先级。例如,以下之一:
{ct_hooks,[my_cth_module]}
{ct_hooks,[{my_cth_module,[{debug,true}]}]}
{ct_hooks,[{my_cth_module,[{debug,true}],500}]}
覆盖CTHS
默认情况下,每次安装CTH都会激活它的新实例。如果您希望覆盖测试规范中的CTH,同时仍将它们放在套件信息函数中,则会导致问题。该id/1
回调存在解决这个问题。通过id
在两个地方返回相同的信息,Common Test
知道这个CTH已经安装并且不会再尝试安装它。
Cth执行令
默认情况下,安装的每个CTH都按照为init调用安装的顺序执行,然后在结束调用时反转。这并不总是需要的,因此Common Test
允许用户为每个钩子指定优先级。优先级可以在CTH函数中指定,也可以在init/2
安装钩子时指定。安装时指定的优先级将覆盖CTH返回的优先级。
14.3 Cth范围
一旦CTH安装到某个测试运行中,它就会一直保持到它的范围过期。CTH的范围取决于它的安装时间,请参阅下表。函数init/2
在范围的开始处terminate/1
被调用,函数在范围结束时被调用。
CTH安装在 | CTH范围开始于之前 | CTH范围之后结束 |
---|---|---|
ct_run | 第一个测试套件将被运行 | 最后的测试套件已经运行 |
CT:RUN_TEST | 第一个测试套件运行 | 最后的测试套件已经运行 |
测试规范 | 第一个测试套件运行 | 最后的测试套件已经运行 |
继/ 0 | pre_init_per_suite / 3被调用 | 已经为该测试套件调用了post_end_per_suite / 4 |
init_per_suite / 1 | post_init_per_suite / 4被调用 | 已经为该测试套件调用了post_end_per_suite / 4 |
init_per_group / 2 | post_init_per_group / 5被调用 | 已经为该组调用了post_end_per_group / 5 |
Cth过程和表
CTH使用与正常测试套件相同的进程范围运行,也就是说,不同的进程执行init_per_suite
钩子,然后执行钩子init_per_group
或per_testcase
钩子。所以如果你想在CTH中产生一个进程,你不能链接到CTH进程,因为它在post钩子结束后退出。此外,如果您出于某种原因需要使用CTH的ETS表格,则必须生成一个处理该表格的流程。
外部配置数据和日志记录
cth中的配置数据值可以通过调用ct:get_config/1,2,3
%28 ARequiring and Reading Configuration Data
29%。所讨论的配置变量必须像往常一样,首先由套件、组或测试用例信息函数或函数所要求。ct:require/1/2
后者也可用于CT钩子功能。
CT钩子函数可以调用ct
接口中的任何日志记录功能来将信息打印到日志文件中,或者在套件概览页面中添加注释。
14.4操纵试验
通过CTH,可以操纵测试和配置功能的结果。使用CTH做到这一点的主要目的是允许从测试套件中抽象出常用模式,并将其应用于多个测试套件而不需要重复任何代码。CTH的所有回调函数都遵循下面描述的通用接口。
Common Test
始终调用所有可用的挂钩函数,甚至在套件中未实现的配置函数的预挂钩和挂钩。例如,pre_init_per_suite(x_SUITE, ...)
和post_init_per_suite(x_SUITE, ...)
被称为测试套件x_SUITE
,即使它不出口init_per_suite/1
。使用此功能,可以将挂钩用作配置回退,并且可以使用挂接功能替换所有配置功能。
预钩
在CTH中,行为可以在以下函数之前被吸引:
init_per_suite
init_per_group
init_per_testcase
end_per_testcase
end_per_group
end_per_suite
这在所谓的CTH函数中完成pre_<name of function>。这些函数的参数SuiteName,Name(组或测试案例名称,如果适用的话),Config和CTHState。CTH函数的返回值总是套件/组/测试结果和更新的结果的组合CTHState。
要让测试套件继续执行,请返回希望测试作为结果使用的配置列表。
所有预先钩,除pre_end_per_testcase/4
,可以跳过或通过与返回的元组测试失败skip
或fail
,和一个原因作为结果。
例子:
pre_init_per_suite(SuiteName, Config, CTHState) ->
case db:connect() of
{error,_Reason} ->
{{fail, "Could not connect to DB"}, CTHState};
{ok, Handle} ->
{[{db_handle, Handle} | Config], CTHState#state{ handle = Handle }}
end.
注
如果您使用多个CTH,则返回元组的第一部分将用作下一个CTH的输入。所以在前面的例子中,下一个CTH可以{fail,Reason}
作为第二个参数。如果你有许多CTH相互作用,不要让每个CTH返回fail
或skip
。相反,返回一个动作是通过Config
列表来实现一个CTH,并最终采取正确的动作。
挂钩
在Cth中,行为可以在以下函数之后连接:
init_per_suite
init_per_group
init_per_testcase
end_per_testcase
end_per_group
end_per_suite
这在所谓的CTH函数中完成post_<name of function>。这些函数的参数SuiteName,Name(组或测试案例名称,如果适用的话), ,Config,Return和CTHState。Config在这种情况下Config与测试用例的调用方式相同。Return是测试用例返回的值。如果测试用例失败,Return是{'EXIT',{{Error,Reason},Stacktrace}}。
CTH函数的返回值总是套件/组/测试结果和更新的结果的组合CTHState
。如果您不希望回调影响测试结果,请将Return
数据返回给CTH。您也可以修改测试结果。通过删除Config
元素返回列表tc_status
,您可以从测试失败中恢复。和所有的预钩子一样,它也可能在post钩子中失败/跳过测试用例。
例子:
post_end_per_testcase(_Suite, _TC, Config, {'EXIT',{_,_}}, CTHState) ->
case db:check_consistency() of
true ->
%% DB is good, pass the test.
{proplists:delete(tc_status, Config), CTHState};
false ->
%% DB is not good, mark as skipped instead of failing
{{skip, "DB is inconsisten!"}, CTHState}
end;
post_end_per_testcase(_Suite, _TC, Config, Return, CTHState) ->
%% Do nothing if tc does not crash.
{Return, CTHState}.
注
请使用CTHS从测试失败中恢复,这是最后的手段。如果使用不当,很难确定哪些测试在测试运行中通过或失败。
跳过和失败挂钩
对所有安装的CTH执行任何post钩子后,on_tc_fail
或者on_tc_skip
在测试用例失败或跳过时调用。此时您无法进一步影响测试的结果。
14.5用通用测试同步外部用户应用程序
CTH可用于使测试运行与外部用户应用程序同步。例如,init函数可以启动和/或与应用程序进行通信,该应用程序的目的是为即将到来的测试运行准备SUT,或者初始化数据库以将测试数据保存到测试运行期间。终止函数可以类似地命令这样的应用程序在测试运行后重置SUT,和/或告诉应用程序完成活动会话并终止。在初始阶段或终止阶段生成的任何系统错误或进度报告将保存在Pre- and Post Test I/O Log
。(对于使用ct:log/2
和进行的任何打印输出也是如此ct:pal/2
)。
为了确保Common Test
在外部应用程序准备好之前不开始执行测试或关闭其日志文件并关闭,Common Test
可以与应用程序同步。在启动和关闭期间Common Test
,只需通过CTH评估receive
init或terminate函数中的表达式即可暂停。宏?CT_HOOK_INIT_PROCESS
(执行钩子初始化函数?CT_HOOK_TERMINATE_PROCESS
的进程)和(执行钩子终止函数的进程)各自指定Common Test
发送消息的正确进程的名称。这样做是为了从中返回receive
。这些宏在中定义ct.hrl
。
14.6示例CTH
以下CTH将有关测试运行的信息记录为file:consult/1
(在Kernel中)可解析的格式:
%%% @doc Common Test Example Common Test Hook module.
-module(example_cth).
%% Callbacks
-export([id/1]).
-export([init/2]).
-export([pre_init_per_suite/3]).
-export([post_init_per_suite/4]).
-export([pre_end_per_suite/3]).
-export([post_end_per_suite/4]).
-export([pre_init_per_group/4]).
-export([post_init_per_group/5]).
-export([pre_end_per_group/4]).
-export([post_end_per_group/5]).
-export([pre_init_per_testcase/4]).
-export([post_init_per_testcase/5]).
-export([pre_end_per_testcase/4]).
-export([post_end_per_testcase/5]).
-export([on_tc_fail/4]).
-export([on_tc_skip/4]).
-export([terminate/1]).
-record(state, { file_handle, total, suite_total, ts, tcs, data }).
%% @doc Return a unique id for this CTH.
id(Opts) ->
proplists:get_value(filename, Opts, "/tmp/file.log").
%% @doc Always called before any other callback function. Use this to initiate
%% any common state.
init(Id, Opts) ->
{ok,D} = file:open(Id,[write]),
{ok, #state{ file_handle = D, total = 0, data = [] }}.
%% @doc Called before init_per_suite is called.
pre_init_per_suite(Suite,Config,State) ->
{Config, State#state{ suite_total = 0, tcs = [] }}.
%% @doc Called after init_per_suite.
post_init_per_suite(Suite,Config,Return,State) ->
{Return, State}.
%% @doc Called before end_per_suite.
pre_end_per_suite(Suite,Config,State) ->
{Config, State}.
%% @doc Called after end_per_suite.
post_end_per_suite(Suite,Config,Return,State) ->
Data = {suites, Suite, State#state.suite_total, lists:reverse(State#state.tcs)},
{Return, State#state{ data = [Data | State#state.data] ,
total = State#state.total + State#state.suite_total } }.
%% @doc Called before each init_per_group.
pre_init_per_group(Suite,Group,Config,State) ->
{Config, State}.
%% @doc Called after each init_per_group.
post_init_per_group(Suite,Group,Config,Return,State) ->
{Return, State}.
%% @doc Called before each end_per_group.
pre_end_per_group(Suite,Group,Config,State) ->
{Config, State}.
%% @doc Called after each end_per_group.
post_end_per_group(Suite,Group,Config,Return,State) ->
{Return, State}.
%% @doc Called before each init_per_testcase.
pre_init_per_testcase(Suite,TC,Config,State) ->
{Config, State#state{ ts = now(), total = State#state.suite_total + 1 } }.
%% Called after each init_per_testcase (immediately before the test case).
post_init_per_testcase(Suite,TC,Config,Return,State) ->
{Return, State}
%% @doc Called before each end_per_testcase (immediately after the test case).
pre_end_per_testcase(Suite,TC,Config,State) ->
{Config, State}.
%% @doc Called after each end_per_testcase.
post_end_per_testcase(Suite,TC,Config,Return,State) ->
TCInfo = {testcase, Suite, TC, Return, timer:now_diff(now(), State#state.ts)},
{Return, State#state{ ts = undefined, tcs = [TCInfo | State#state.tcs] } }.
%% @doc Called after post_init_per_suite, post_end_per_suite, post_init_per_group,
%% post_end_per_group and post_end_per_testcase if the suite, group or test case failed.
on_tc_fail(Suite, TC, Reason, State) ->
State.
%% @doc Called when a test case is skipped by either user action
%% or due to an init function failing.
on_tc_skip(Suite, TC, Reason, State) ->
State.
%% @doc Called when the scope of the CTH is done
terminate(State) ->
io:format(State#state.file_handle, "~p.~n",
[{test_run, State#state.total, State#state.data}]),
file:close(State#state.file_handle),
ok.
14.7内置cths
Common Test
与一些通用CTH一起提供,用户可以使用这些通用CTH来提供通用测试功能。其中一些CTH在common_test
开始运行时默认启用。可以通过在命令行或测试规范中设置enable_builtin_hooks
来禁用它们false
。以下两个CTH随附于Common Test
:
cth_log_redirect
内建
捕获所有error_logger
和SASL
记录事件并将它们打印到当前的测试用例日志中。如果一个事件不能与一个测试用例相关联,它将被打印在Common Test
框架日志中。这发生在并行运行的测试用例和发生在测试用例之间的事件之间。您可以SASL
使用正常的SASL
机制配置事件报告级别。
cth_surefire
未内置
捕获所有测试结果并将它们作为surefire XML输出到文件中。创建的文件默认被调用junit_report.xml
。可以通过设置path
此挂钩的选项来更改文件名,例如:
-ct_hooks cth_surefire [{path,"/tmp/report.xml"}]
如果选项url_base
设置,命名为额外的属性url
添加到每个testsuite
和testcase
XML元素。该值url_base
分别由测试套件或测试用例日志构建而成,并分别与其构成相对路径,例如:
-ct_hooks cth_surefire [{url_base, "http://myserver.com/"}]
提供类似于
"http://myserver.com/ct_run.ct@myhost.2012-12-12_11.19.39/ x86_64-unknown-linux-gnu.my_test.logs/run.2012-12-12_11.19.39/suite.log.html"
例如,Jenfire可以使用Surefire XML来显示测试结果。