查看源代码 事件处理
概述
Common Test
系统的操作员可以在测试运行期间持续接收事件通知。例如,Common Test
会报告测试用例何时开始和停止,当前成功、失败和跳过的用例计数等等。这些信息可以用于不同的目的,例如以 HTML 以外的格式记录进度和结果,将统计信息保存到数据库以生成报告以及测试系统监控。
Common Test
具有一个基于 OTP 事件管理器概念和 gen_event
行为的事件处理框架。当 Common Test
服务器启动时,它会生成一个事件管理器。在测试执行期间,当发生一些潜在的有趣的事情时,管理器会收到来自服务器的通知。任何插入到事件管理器中的事件处理程序都可以匹配感兴趣的事件,采取行动或传递信息。事件处理程序是 Erlang 模块,由 Common Test
用户根据 gen_event
行为实现(有关详细信息,请参阅模块 gen_event
和系统文档中 OTP 设计原则中的 gen_event 行为
部分)。
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
。
使用
可以通过 event_handler
启动标志 (ct_run
) 或选项 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 代码服务器搜索路径中(如上例所示)。
参数 Opts
中的 event_handler 元组具有以下定义(请参阅 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 参数列表启动。
还可以使用以下 测试规范 项之一插入事件处理程序
{event_handler, EventHandlers}
{event_handler, EventHandlers, InitArgs}
{event_handler, NodeRefs, EventHandlers}
{event_handler, NodeRefs, EventHandlers, InitArgs}
EventHandlers
是模块名称的列表。在测试会话开始之前,会调用每个插入的事件处理程序的 init 函数(以 InitArgs
列表作为参数,如果未指定启动参数,则为 []
)。
要将处理程序插入 Common Test
主事件管理器,请在 NodeRefs
中指定 master
作为节点。
为了能够匹配事件,事件处理程序模块必须包含头文件 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()
,套件总数。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()}
,为什么 require 失败。FailInfo = {timetrap_timeout,integer()} | {RunTimeError,StackTrace} | UserTerm
,错误详细信息。RunTimeError = term()
,运行时错误,例如badmatch
或undef
。StackTrace = list()
,运行时错误之前的函数调用列表。UserTerm = term()
,用户指定的任何数据,或exit/1
信息。此事件通知测试用例或配置函数的结束(有关元素
FuncOrGroup
的详细信息,请参阅事件tc_start
)。此事件带有相关函数的最终结果。可以在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()}
,为什么 require 失败。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
失败,suite/0
中的require
失败,或序列中的测试用例失败。请注意,由于init_per_testcase
失败而导致测试用例被跳过,绝不会收到此事件,因为该信息会在事件tc_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()
,测试用例被跳过的原因。此事件指定测试用例被用户跳过。仅当在测试规范中声明跳过时才会收到此事件。否则,用户跳过信息会作为事件
tc_done
中测试用例的{skipped,SkipReason}
结果接收。如果跳过的测试用例属于测试用例组,则第二个数据元素是元组{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()
,文件的完整名称。此内部事件由
Common Test
主进程用于同步特定的文件操作。#event{name = finished_write_file, data = FullNameFile}
-FullNameFile = string()
,文件的完整名称。此内部事件由
Common Test
主进程用于同步特定的文件操作。
说明
事件也在 ct_event.erl
中进行了文档化。该模块可以作为 Common Test
事件管理器事件处理程序的示例。
注意
为了确保打印到
stdout
的内容(或使用ct:log/2,3
或ct:pal,2,3
进行的打印)被写入到测试用例日志文件,而不是Common Test
框架日志,您可以通过匹配事件tc_start
和tc_done
来与Common Test
服务器进行同步。在这些事件之间的时间段内,所有 I/O 都将定向到测试用例日志文件。这些事件是同步发送的,以避免潜在的时序问题(例如,在外部进程的 I/O 消息通过之前,测试用例日志文件被关闭)。了解这一点后,您需要小心,您的handle_event/2
回调函数不会使测试执行停滞,从而可能导致意外行为。