首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

14. Common Test Hooks

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/0init_per_suite/1init_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_groupper_testcase钩子。所以如果你想在CTH中产生一个进程,你不能链接到CTH进程,因为它在post钩子结束后退出。此外,如果您出于某种原因需要使用CTH的ETS表格,则必须生成一个处理该表格的流程。

外部配置数据和日志记录

cth中的配置数据值可以通过调用ct:get_config/1,2,3%28 ARequiring and Reading Configuration Data29%。所讨论的配置变量必须像往常一样,首先由套件、组或测试用例信息函数或函数所要求。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>。这些函数的参数SuiteNameName(组或测试案例名称,如果适用的话),ConfigCTHState。CTH函数的返回值总是套件/组/测试结果和更新的结果的组合CTHState

要让测试套件继续执行,请返回希望测试作为结果使用的配置列表。

所有预先钩,除pre_end_per_testcase/4,可以跳过或通过与返回的元组测试失败skipfail,和一个原因作为结果。

例子:

代码语言:javascript
复制
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返回failskip。相反,返回一个动作是通过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>。这些函数的参数SuiteNameName(组或测试案例名称,如果适用的话), ,ConfigReturnCTHStateConfig在这种情况下Config与测试用例的调用方式相同。Return是测试用例返回的值。如果测试用例失败,Return{'EXIT',{{Error,Reason},Stacktrace}}

CTH函数的返回值总是套件/组/测试结果和更新的结果的组合CTHState。如果您不希望回调影响测试结果,请将Return数据返回给CTH。您也可以修改测试结果。通过删除Config元素返回列表tc_status,您可以从测试失败中恢复。和所有的预钩子一样,它也可能在post钩子中失败/跳过测试用例。

例子:

代码语言:javascript
复制
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评估receiveinit或terminate函数中的表达式即可暂停。宏?CT_HOOK_INIT_PROCESS(执行钩子初始化函数?CT_HOOK_TERMINATE_PROCESS的进程)和(执行钩子终止函数的进程)各自指定Common Test发送消息的正确进程的名称。这样做是为了从中返回receive。这些宏在中定义ct.hrl

14.6示例CTH

以下CTH将有关测试运行的信息记录为file:consult/1(在Kernel中)可解析的格式:

代码语言:javascript
复制
 %%% @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添加到每个testsuitetestcaseXML元素。该值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来显示测试结果。

扫码关注腾讯云开发者

领取腾讯云代金券

http://www.vxiaotou.com