12. Event Handling
12事件处理
12.1总则
Common Test
系统的操作员可以在测试运行期间连续接收事件通知。例如,Common Test
报告测试用例启动和停止的情况,当前成功,失败和跳过的情况等等。这些信息可以用于不同的目的,例如记录进度和以HTML之外的其他格式生成结果,将统计信息保存到数据库以生成报告以及测试系统监督。
Common Test
具有基于OTP事件管理器概念和gen_event
行为的事件处理框架。当Common Test
服务器启动时,它会产生一个事件管理器。在测试执行期间,当发生可能感兴趣的事情时,管理器从服务器获取通知。插入事件管理器的任何事件处理程序都可以匹配感兴趣的事件,采取行动或传递信息。事件处理程序是Common Test
用户根据gen_event
行为实现的Erlang模块(有关详细信息,请参阅系统文档中的OTP设计原则中的模块gen_event
和章节gen_event Behaviour
)。
一个Common Test
服务器始终启动一个事件管理器。服务器还插入一个默认的事件处理程序,其目的只是将通知转发给全局注册的Common Test
主事件管理器(如果Common Test
主服务器正在系统中运行)。该Common Test
主服务器启动时也会生成一个事件管理器。插入此管理器的事件处理程序接收来自所有测试节点的事件,以及来自Common Test
主服务器的信息。
用户特定的事件处理程序可以插入到Common Test
事件管理器中,可以通过告诉Common Test
在测试运行之前安装它们(稍后介绍),也可以通过在测试运行期间动态添加处理程序来使用gen_event:add_handler/3
或gen_event:add_sup_handler/3
。在后一种情况下,Common Test
需要事件管理器的参考。要获得它,请致电ct:get_event_mgr_ref/0
或(在Common Test
主节点上)ct_master:get_event_mgr_ref/0
。
12.2使用
事件处理程序可以通过event_handler
start flag(ct_run
)或option 来安装ct:run_test/1
,其中参数指定一个或多个事件处理程序模块的名称。
例子:
$ ct_run -suite test/my_SUITE -event_handler handlers/my_evh1 handlers/my_evh2 -pa $PWD/handlers
要将启动参数传递给事件处理函数init函数,请使用选项ct_run -event_handler_init
而不是-event_handler
。
注
所有事件处理程序模块必须具有gen_event
行为 这些模块必须进行预编译,并且它们的位置必须显式添加到Erlang代码服务器搜索路径中(如前例)。
参数中的event_handler元组Opts
具有以下定义(请参阅ct:run_test/1
):
{event_handler,EventHandlers}
EventHandlers = EH | [EH]
EH = atom() | {atom(),InitArgs} | {[atom()],InitArgs}
InitArgs = [term()]
在下面的示例中,两个事件处理程序用于my_SUITE
已安装测试:
1> ct:run_test([{suite,"test/my_SUITE"},{event_handler,[my_evh1,{my_evh2,[node()]}]}]).
事件处理程序my_evh1
是从[]
作为init函数的参数。事件处理程序my_evh2
以init参数列表中当前节点的名称启动。
事件处理程序也可以使用下列之一插入test specification
术语:
{event_handler, EventHandlers}
{event_handler, EventHandlers, InitArgs}
{event_handler, NodeRefs, EventHandlers}
{event_handler, NodeRefs, EventHandlers, InitArgs}
EventHandlers
是模块名称的列表。在测试会话开始之前,调用每个插入的事件处理程序的init函数(将InitArgs
列表作为参数或者[]
如果未指定启动参数)。
要插入Common Test
主事件管理器的处理程序,请指定master
为其中的节点NodeRefs
。
为了能够匹配事件,事件处理程序模块必须包含头文件ct_event.hrl
。事件是具有以下定义的记录:
#event{name, node, data}
name
标签(类型)的事件。
node
事件源自的节点名称(仅与Common Test
主事件处理程序相关)。
data
具体的事件。
一般事件
一般性活动如下:
#event{name = start_logging, data = LogDir}
LogDir = string()
,用于测试运行的顶级日志目录。
此事件表示日志记录过程Common Test
已成功启动并准备好接收I / O消息。
#event{name = stop_logging, data = []}
此事件表示Common Test
在测试结束时被关闭了。
#event{name = test_start, data = {StartTime,LogDir}}
StartTime = {date(),time()}
,测试运行开始日期和时间。
LogDir = string()
,用于测试运行的顶级日志目录。
此事件表明Common Test
已经完成了初始准备并开始执行测试用例。
#event{name = test_done, data = EndTime}
EndTime = {date(),time()}
测试运行完成的日期和时间。
此事件表示已执行最后一个测试用例,并且Common Test
正在关闭。
#event{name = start_info, data = {Tests,Suites,Cases}}
Tests = integer()
,测试次数。
Suites = integer()
,suites的总数。
Cases = integer() | unknown
,测试用例总数。
此事件提供了初始测试运行信息,可以将其解释为:“此测试运行将执行Tests
单独的测试,总共包含Cases
多个测试用例,Suites
套件数量”。但是,如果任何测试中存在具有重复属性的测试用例组,则无法计算测试用例的总数(未知)。
#event{name = tc_start, data = {Suite,FuncOrGroup}}
Suite = atom()
测试套件的名称。
FuncOrGroup = Func | {Conf,GroupName,GroupProperties}
Func = atom()
、测试用例名称或配置函数。
Conf = init_per_group | end_per_group
,组配置函数。
GroupName = atom()
,团体名称。
GroupProperties = list()
组的执行属性列表。
此事件通知测试用例或组配置函数的开始。事件也被发送给init_per_suite
和end_per_suite
,但不是为了init_per_testcase
和end_per_testcase
如果启动组配置函数,还将指定组名称和执行属性。
#event{name = tc_logfile, data = {{Suite,Func},LogFileName}}
Suite = atom()
测试套件的名称。
Func = atom()
、测试用例名称或配置函数。
LogFileName = string()
测试用例日志文件的全名。
该事件在每个测试用例开始时发送(除了配置功能外init/end_per_testcase
),并携带有关当前测试用例日志文件的全名(即文件名,包括绝对目录路径)的信息。
#event{name = tc_done, data = {Suite,FuncOrGroup,Result}}
Suite = atom()
套房的名字。
FuncOrGroup = Func | {Conf,GroupName,GroupProperties}
Func = atom()
、测试用例名称或配置函数。
Conf = init_per_group | end_per_group
,组配置函数。
GroupName = unknown | atom()
,组的名称(如果init或end函数超时,则不知道)。
GroupProperties = list()
组的执行属性列表。
Result = ok | {auto_skipped,SkipReason} | {skipped,SkipReason} | {failed,FailReason}
结果。
SkipReason = {require_failed,RequireInfo} | {require_failed_in_suite0,RequireInfo} | {failed,{Suite,init_per_testcase,FailInfo}} | UserTerm
为什么这个案子被跳过了。
FailReason = {error,FailInfo} | {error,{RunTimeError,StackTrace}} | {timetrap_timeout,integer()} | {failed,{Suite,end_per_testcase,FailInfo}}
失败的原因。
RequireInfo = {not_available,atom() | tuple()}
,为什么要求失败。
FailInfo = {timetrap_timeout,integer()} | {RunTimeError,StackTrace} | UserTerm
, 错误详情。
RunTimeError = term()
,运行时错误,例如,badmatch
或undef
...
StackTrace = list()
,运行时错误之前的函数调用列表。
UserTerm = term()
,用户指定的任何数据,或exit/1
信息咨询
此事件通知测试用例或配置函数的结束(tc_start
有关元素的详细信息,请参阅事件FuncOrGroup
)。有了这个事件,问题的功能的最终结果。可以在顶层确定Result
功能是否成功,(由用户)跳过,还是失败。
也可以深入挖掘,例如,针对跳过或失败的各种原因执行模式匹配。注意{'EXIT',Reason}
元组被翻译成了{error,Reason}
。还要注意,如果{failed,{Suite,end_per_testcase,FailInfo}
收到结果,则测试用例成功,但是end_per_testcase
该案例失败。
#event{name = tc_auto_skip, data = {Suite,TestName,Reason}}
Suite = atom()
,套件的名称。
TestName = init_per_suite | end_per_suite | {init_per_group,GroupName} | {end_per_group,GroupName} | {FuncName,GroupName} | FuncName
FuncName = atom()
测试用例或配置函数的名称。
GroupName = atom()
测试用例组的名称。
Reason = {failed,FailReason} | {require_failed_in_suite0,RequireInfo}
,自动跳转的原因Func
...
FailReason = {Suite,ConfigFunc,FailInfo}} | {Suite,FailedCaseInSequence}
失败的原因。
RequireInfo = {not_available,atom() | tuple()}
为什么要求失败。
ConfigFunc = init_per_suite | init_per_group
FailInfo = {timetrap_timeout,integer()} | {RunTimeError,StackTrace} | bad_return | UserTerm
,错误细节。
FailedCaseInSequence = atom()
,在序列中失败的案例的名称。
RunTimeError = term()
,例如,运行时错误。badmatch
或undef
...
StackTrace = list()
,运行时错误前面的函数调用列表。
UserTerm = term()
,用户指定的任何数据,或exit/1
信息咨询
针对每个测试用例或配置函数都会发送此事件,Common Test
因为顺序中的测试用例失败,失败init_per_suite
或init_per_group
失败,已自动跳过该测试用例或配置函数。请注意,由于测试用例由于失败而跳过,因此该事件从未被接收,因为该信息随事件一起传送。如果一个失败的测试用例属于一个测试用例组,则第二个数据元素是一个元组,否则只是函数名称。requiresuite/0init_per_testcasetc_done{FuncName,GroupName}
#event{name = tc_user_skip, data = {Suite,TestName,Comment}}
Suite = atom()
,套件的名称。
TestName = init_per_suite | end_per_suite | {init_per_group,GroupName} | {end_per_group,GroupName} | {FuncName,GroupName} | FuncName
FuncName = atom()
测试用例或配置函数的名称。
GroupName = atom()
测试用例组的名称。
Comment = string()
测试用例被跳过的原因。
此事件指定用户跳过测试用例。只有在测试规范中声明了跳过时才会收到。否则,用户跳过信息将作为{skipped,SkipReason}
结果事件tc_done
测试用例。如果跳过的测试用例属于测试用例组,则第二个数据元素是元组。{FuncName,GroupName}
,否则只有函数名。
#event{name = test_stats, data = {Ok,Failed,Skipped}}
Ok = integer()
,当前成功测试用例的数量。
Failed = integer()
,失败测试用例的当前数量。
Skipped = {UserSkipped,AutoSkipped}
UserSkipped = integer()
当前用户跳过测试用例的数量。
AutoSkipped = integer()
当前自动跳过测试用例的数量。
这是一个统计事件,目前有成功,跳过和失败的测试用例。这个事件是在每个测试用例结束之后,紧接着事件发送的tc_done
。
内部事件
内部活动如下:
#event{name = start_make, data = Dir}
Dir = string()
,在此目录中运行make。
该内部事件表示Common Test
开始编译目录中的模块Dir
。
#event{name = finished_make, data = Dir}
Dir = string()
,在此目录中完成了make的运行。
这个内部事件表示Common Test
完成编译目录中的模块Dir
。
#event{name = start_write_file, data = FullNameFile}
FullNameFile = string(), full name of the file.
属性使用此内部事件。Common Test
主进程来同步特定的文件操作。
#event{name = finished_write_file, data = FullNameFile}
FullNameFile = string(), full name of the file.
属性使用此内部事件。Common Test
主进程来同步特定的文件操作。
注记
这些事件也记录在案ct_event.erl
。该模块可以作为Common Test
事件管理器事件处理程序的样例。
注
为了确保打印输出stdout
(或打印输出)ct:log/2,3
或ct:pal,2,3
写入测试用例日志文件,而不是写入Common Test
框架日志,可以Common Test
通过匹配evvents tc_start
和与服务器同步tc_done
。在这些事件之间的时间段内,所有的I / O都被定向到测试用例日志文件。这些事件是同步发送的,以避免潜在的计时问题(例如,测试用例日志文件在来自外部进程的I / O消息通过之前就已关闭)。了解这一点,您需要小心您的handle_event/2
回调函数不会拖延测试执行,因此可能导致意外行为。