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/3gen_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_handlerstart 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_suiteend_per_suite,但不是为了init_per_testcaseend_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(),运行时错误,例如,badmatchundef...

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(),例如,运行时错误。badmatchundef...

StackTrace = list(),运行时错误前面的函数调用列表。

UserTerm = term(),用户指定的任何数据,或exit/1信息咨询

针对每个测试用例或配置函数都会发送此事件,Common Test因为顺序中的测试用例失败,失败init_per_suiteinit_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,3ct:pal,2,3写入测试用例日志文件,而不是写入Common Test框架日志,可以Common Test通过匹配evvents tc_start和与服务器同步tc_done。在这些事件之间的时间段内,所有的I / O都被定向到测试用例日志文件。这些事件是同步发送的,以避免潜在的计时问题(例如,测试用例日志文件在来自外部进程的I / O消息通过之前就已关闭)。了解这一点,您需要小心您的handle_event/2回调函数不会拖延测试执行,因此可能导致意外行为。