在线文档教程

5. Writing Test Suites

5 编写测试套件

5.1对测试套件作者的支持

ct模块提供编写测试用例的主界面。这包括例如以下内容:

  • 打印和记录功能

  • 读取配置数据的函数

  • 函数用于终止带有错误原因的测试用例。

  • 函数,用于向HTML概述页面添加注释。

有关这些功能的详细信息,请参阅模块ct

该Common Test应用程序还包括名为的其他模块ct_<component>,它们提供各种支持,主要是RPC,SNMP,FTP,Telnet等通信协议的简化使用。

5.2 测试套件

测试套件是一个包含测试用例的普通Erlang模块。建议模块在表单上有一个名称*_SUITE.erl。否则,目录和自动编译功能Common Test无法找到它(至少不是默认情况下)。

还建议ct.hrl头文件包含在所有测试套件模块中。

每个测试套件模块都必须导出函数all/0,该函数返回要在该模块中执行的所有测试用例组和测试用例的列表。

测试套件实现的回调函数全部列在模块中common_test。在本用户指南的后面还会详细介绍它们。

5.3 每个套件的初始和结束

每个测试套件模块都可以包含可选的配置功能init_per_suite/1end_per_suite/1。如果定义了init函数,那么最终函数必须是。

如果init_per_suite存在,则在测试用例执行前最初调用它。它通常包含套件中所有测试用例通用的初始化,这些初始化只能执行一次。init_per_suite建议用于设置和验证被测系统(SUT)或Common Test主节点或两者的状态和环境,以便套件中的测试用例正确执行。以下是初始配置操作的示例:

  • 打开到SUT的连接

  • 初始化数据库

  • 运行安装脚本

end_per_suite被称为测试套件执行的最后阶段(在最后一个测试用例完成之后)。该功能旨在用于清理之后init_per_suite

init_per_suiteend_per_suite在专用的Erlang进程上执行,就像测试用例一样。然而,这些函数的结果并未包含在成功,失败和跳过案例的测试运行统计中。

参数init_per_suiteConfig,也就是每个测试用例作为输入参数的运行时配置数据的相同键值列表。init_per_suite可以用测试用例需要的信息修改此参数。可能被修改的Config列表是函数的返回值。

如果init_per_suite失败,测试套件中的所有测试用例都会自动跳过(所谓的自动跳过),包括end_per_suite

请注意,如果init_per_suiteend_per_suite没有在suite中存在,Common Test调用虚函数(具有相同名称)来代替,因此通过钩子函数生成的输出可以保存为这些日志文件。有关详情,请参阅Common Test Hooks

5.4 每个测试用例的初始和结束

每个测试套件模块都可以包含可选的配置功能init_per_testcase/2end_per_testcase/2。如果定义了init函数,那么最终函数也必须是这些。

如果init_per_testcase存在,则在套件中的每个测试用例之前调用它。它通常包含必须为每个测试案例完成的初始化(init_per_suite与此套件类似)。

end_per_testcase/2在每个测试用例完成后调用,从而启用后清理init_per_testcase

这些函数的第一个参数是测试用例的名称。该值可以与函数子句或条件表达式中的模式匹配一​​起使用,以针对不同的测试用例选择不同的初始化和清理例程,或者为许多或所有测试用例执行相同的例程。

第二个参数是Config运行时配置数据的键值列表,它与返回的列表具有相同的值init_per_suiteinit_per_testcase/2可以修改此参数或按“原样”返回。返回值init_per_testcase/2作为参数传递Config给测试用例本身。

end_per_testcase/2测试服务器忽略返回值,除了save_configfail元组之外。

end_per_testcase可以检查测试案例是否成功。(这又可以确定如何执行清理)。这是通过读取标记为tc_statusfrom 的值完成的Config。该值为以下值之一:

  • ok

  • {failed,Reason}

其中Reasontimetrap_timeout,来自exit / 1的信息或运行时错误的详细信息

  • {skipped,Reason} 这里Reason是用户特定termFunction end_per_testcase/2如果一个测试用例终止,因为到呼叫甚至被称为ct:abort_current_testcase/1,或之后timetrap超时。然而,end_per_testcase然后执行与测试用例函数不同的进程。在这种情况下,end_per_testcase不能通过返回{fail,Reason}或保存数据来更改测试用例终止的原因{save_config,Data}。在以下两种情况下跳过测试用例:

  • 如果init_per_testcase崩溃(称为自动跳过)。

  • 如果init_per_testcase返回一个元组{skip,Reason}(称为用户跳过)。

测试用例也可以标记为失败,而不通过{fail,Reason}init_per_testcase中返回元组来执行它。

如果init_per_testcase崩溃,或者返回{skip,Reason}或者{fail,Reason},函数end_per_testcase没有被调用。

如果在执行期间确定end_per_testcase成功的测试用例的状态将被更改为失败,则end_per_testcase可以返回元组{fail,Reason}(其中Reason描述测试用例失败的原因)。

作为init_per_testcaseend_per_testcase在与测试用例相同的Erlang进程上执行,来自这些配置函数的打印输出包含在测试用例日志文件中。

5.5 测试用例

测试服务器所关心的最小单位是一个测试用例。每个测试用例都可以测试很多事情,例如,使用不同的参数对同一个接口函数进行多次调用。

作者可以选择在每个测试用例中添加很多或很少的测试。有些事情要记住:

  • 许多小的测试用例往往会导致额外的,可能重复的代码,以及由于初始化和清理的大量开销而导致的测试执行速度变慢。避免重复的代码,例如,通过使用常见的帮助功能。否则,产生的套件变得难以阅读和理解,并且维护成本高。

  • 更大的测试用例使得如果失败的话很难判断出出了什么问题。此外,当出现错误时,很大一部分测试代码可能会被跳过。

  • 当测试用例变得太大而且广泛时,可读性和可维护性会受到影响。不确定的结果日志文件反映了很好的测试数量。

  • stylesheet,见HTML Style Sheets

  • userdata,见Test Case Information Function

  • silent_connections,见Silent Connections

下面是套件信息功能的一个示例:

suite() -> [ {timetrap,{minutes,10}}, {require,global_names}, {userdata,[{info,"This suite tests database transactions."}]}, {silent_connections,[telnet]}, {stylesheet,"db_testing.css"} ].

5.8 测试用例组

测试用例组是一组共享配置功能和执行属性的测试用例。测试用例组由函数定义。groups/0根据以下语法:

groups() -> GroupDefs Types: GroupDefs = [GroupDef] GroupDef = {GroupName,Properties,GroupsAndTestCases} GroupName = atom() GroupsAndTestCases = [GroupDef | {group,GroupName} | TestCase] TestCase = atom()

GroupName是组的名称,并且在测试套件模块中必须是唯一的。 通过在另一个组的GroupAndTestCases列表中包含一个组定义,组可以嵌套。 属性是组的执行属性的列表。 可能的值如下所示:

Properties = [parallel | sequence | Shuffle | {RepeatType,N}] Shuffle = shuffle | {shuffle,Seed} Seed = {integer(),integer(),integer()} RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail N = integer() | forever

解释:

parallel

Common Test 并行执行组中的所有测试用例。

sequence

这些案例按照节中描述的顺序执行。Sequences在测试用例和套件之间的依赖部分。

shuffle

组中的案例按随机顺序执行。

repeat

命令Common Test重复执行组中的案例给定次数,或直到任何或所有案例失败或成功。

例子:

groups() -> [{group1, [parallel], [test1a,test1b]}, {group2, [shuffle,sequence], [test2a,test2b,test2c]}].

要指定要以何种顺序执行组(也针对不属于任何组的测试用例),请将表单{group,GroupName}中的元组添加到all/0列表中。

例子:

all() -> [testcase1, {group,group1}, testcase2, {group,group2}].

在一组元组执行属性all/0{group,GroupName,Properties}也可以指定。这些属性将覆盖组定义中指定的属性(请参阅groups/0前面的内容)。通过这种方式,可以运行同一组测试,但具有不同的属性,而不必复制相关组定义的副本。

如果一个组包含子组,那么它们的执行属性也可以在组元组中指定:{group,GroupName,Properties,SubGroups}其中,SubGroups是元组列表,{GroupName,Properties}或者{GroupName,Properties,SubGroups}表示子组。在组中定义的任何group/0未在SubGroups列表中指定的子组都使用其预定义的属性执行。

例子:

groups() -> {tests1, [], [{tests2, [], [t2a,t2b]}, {tests3, [], [t31,t3b]}]}.

每次执行tests1两次不同属性的组tests2

all() -> [{group, tests1, default, [{tests2, [parallel]}]}, {group, tests1, default, [{tests2, [shuffle,{repeat,10}]}]}].

这相当于以下规范:

all() -> [{group, tests1, default, [{tests2, [parallel]}, {tests3, default}]}, {group, tests1, default, [{tests2, [shuffle,{repeat,10}]}, {tests3, default}]}].

价值default使用预定义属性的状态。

下面的示例演示如何在具有深度嵌套组的场景中重写属性:

groups() -> [{tests1, [], [{group, tests2}]}, {tests2, [], [{group, tests3}]}, {tests3, [{repeat,2}], [t3a,t3b,t3c]}]. all() -> [{group, tests1, default, [{tests2, default, [{tests3, [parallel,{repeat,100}]}]}]}].

所描述的语法也可以在测试规范中用于在执行时更改组属性,而不必编辑测试套件。有关更多信息,请参见Test Specifications在部分运行测试和分析结果。

如图所示,可以组合属性。如果,例如,shufflerepeat_until_any_fail,和sequence都指定,测试用例的组中被重复地执行,并且以随机的顺序,直到一个测试用例失败。然后立即停止执行,并跳过其余案例。

在开始执行组之前,init_per_group(GroupName, Config)会调用配置功能。从该函数返回的元组列表通过参数以通常的方式传递给测试用例Configinit_per_group/2旨在用于组中测试用例通用的初始化。组执行完成后,调用函数end_per_group(GroupName, Config)。此功能旨在用于清理之后init_per_group/2。如果定义了init函数,那么最终函数必须是。

每当执行一组,如果init_per_groupend_per_group没有在suite存在,Common Test调用虚函数(具有相同名称)来代替。钩函数生成的输出保存到这些虚拟文件的日志文件中。有关更多信息,请参见Manipulating Tests常见测试挂钩部分。

init_per_testcase/2end_per_testcase/2总是为每个单独的测试用例调用,不管该用例是否属于一个组。

组的属性始终打印在HTML日志的顶部init_per_group/2。组的总执行时间包含在日志的底部end_per_group/2

可以嵌套测试用例组,这样可以用相同的集合配置组。init_per_group/2end_per_group/2职能。嵌套组可以通过在另一个组的测试用例列表中包含组定义或组名引用来定义。

例子:

groups() -> [{group1, [shuffle], [test1a, {group2, [], [test2a,test2b]}, test1b]}, {group3, [], [{group,group4}, {group,group5}]}, {group4, [parallel], [test4a,test4b]}, {group5, [sequence], [test5a,test5b,test5c]}].

在前面的示例中,如果all/0按顺序返回组名称引用[{group,group1},{group,group3}],则配置函数和测试用例的顺序变为以下(注意init_per_testcase/2end_per_testcase/2:始终被调用,但为了简化,未包括在此示例中):

init_per_group(group1, Config) -> Config1 (*) test1a(Config1) init_per_group(group2, Config1) -> Config2 test2a(Config2), test2b(Config2) end_per_group(group2, Config2) test1b(Config1) end_per_group(group1, Config1) init_per_group(group3, Config) -> Config3 init_per_group(group4, Config3) -> Config4 test4a(Config4), test4b(Config4) (**) end_per_group(group4, Config4) init_per_group(group5, Config3) -> Config5 test5a(Config5), test5b(Config5), test5c(Config5) end_per_group(group5, Config5) end_per_group(group3, Config3)

(*)测试例的顺序test1atest1bgroup2是未定义的,如group1具有一个混洗特性。

(**)这些情况不是按顺序执行的,而是并行执行的。

属性不会从顶级组继承到嵌套子组。例如,在前面的例子中,测试用例group2不是以随机顺序执行的(这是属性group1)。

5.9 并行属性和嵌套组

如果一个组具有并行属性,则它的测试用例同时生成并且并行执行。但是,测试用例不允许并行执行end_per_group/2,这意味着执行并行组的时间等于组中最慢测试用例的执行时间。并行运行测试用例的一个负面影响是,HTML摘要页面不会更新为单个测试用例日志的链接,直到end_per_group/2该组的功能完成。

嵌套在并行组下的组开始与以前的(并行)测试用例并行执行(不管嵌套组具有哪些属性)。然而,随着测试案例从不平行于执行init_per_group/2end_per_group/2相同的基团的,它只有后嵌套组已完成前一组成为保持平行的情况下衍生出。

5.10并行测试用例与I/O

一个并行测试案例有一个私有I / O服务器作为其组长。(有关组长概念的描述,请参阅ERTS)。处理常规测试用例和配置功能输出的中央I / O服务器进程在执行并行组期间不响应I / O消息。这对于避免某些陷阱很重要,如下所示:

P例如,如果一个进程在执行期间产生init_per_suite/1,它会继承该init_per_suite进程的组长。这个组长是上面提到的中央I / O服务器进程。如果在以后的时间,在并行测试案例执行期间,某些事件触发器P会调用进程io:format/1/2,该调用永远不会返回(因为组长处于非响应状态)并导致P挂起。

5.11 重复组

测试用例组可以重复一定次数(由整数指定)或无限期(由指定forever)。重复也可以太早停止,如果任一或所有的情况下失败或成功,也就是说,如果任何属性的repeat_until_any_failrepeat_until_any_okrepeat_until_all_fail,或repeat_until_all_ok使用。如果使用基本repeat属性,则测试用例的状态与重复操作无关。

子组的状态可以返回(okfailed),以影响上述级别组的执行。这是通过在end_per_group/2,仰视的值tc_group_propertiesConfig列表和检查的测试用例组的结果。如果状态failed要从组返回,end_per_group/2则返回该值{return_group_result,failed}Common Test当评估一个组的执行是否被重复时(除非使用基本repeat属性),将考虑子组的状态。

tc_group_properties值是状态元组的列表,每个按键okskippedfailed。状态元组的值是一个列表,其中的测试用例的名称已经以相应的状态作为结果执行。

以下是如何从组返回状态的示例:

end_per_group(_Group, Config) -> Status = ?config(tc_group_result, Config), case proplists:get_value(failed, Status) of [] -> % no failed cases {return_group_result,ok}; _Failed -> % one or more failed {return_group_result,failed} end.

也有可能end_per_group/2检查一个小组的状态(可能确定当前小组返回的状态)。这与前面的例子一样简单,只有组名存储在一个元组中{group_result,GroupName},可以在状态列表中搜索。

例子:

end_per_group(group1, Config) -> Status = ?config(tc_group_result, Config), Failed = proplists:get_value(failed, Status), case lists:member{group_result,group2}, Failed) of true -> {return_group_result,failed}; false -> {return_group_result,ok} end; ...

当测试病例组被重复,配置功能init_per_group/2end_per_group/2也总是与每个重复调用。

5.12 混合测试用例顺序

一般情况下,组中测试用例的执行顺序与组定义中测试用例列表中指定的顺序相同。shuffle但是,通过属性设置,Common Test可以按照随机顺序执行测试用例。

您可以使用shuffle属性提供种子值(三个整数的元组){shuffle,Seed}。这样,每次组执行时都可以创建相同的混洗订单。如果未指定种子值,Common Test则为混洗操作创建一个“随机”种子(使用返回值erlang:timestamp/0)。种子值总是被打印到init_per_group/2日志文件中,以便它可以用于在后续测试运行中重新创建相同的执行顺序。

如果重新组合了一个混洗测试用例组,种子不会在两次轮流之间重置。

如果在具有shuffle属性的组中指定子组,则该子组与组中测试用例(和其他子组)的执行顺序是随机的。 然而,子组中的测试用例的顺序不是随机的(除非子组具有shuffle属性)。

5.13 组信息函数

测试用例组信息功能group(GroupName)与前述的套件和测试用例信息功能具有相同的作用。但是,组信息功能的范围是所讨论的组中的所有测试用例和子组(GroupName)。

例子:

group(connection_tests) -> [{require,login_data}, {timetrap,1000}].

组信息属性会覆盖使用套件信息功能设置的属性,并且可能会被测试案例信息属性覆盖。有关有效信息属性和更多一般信息的列表,请参阅Test Case Information Function

5.14信息函数用于Init和End配置

信息功能也可用于执行多种功能init_per_suiteend_per_suiteinit_per_group,和end_per_group,和他们的工作方式一样用Test Case Information Function。例如,这对于设置时间限制并要求外部配置数据仅与所讨论的配置功能相关(而不影响套件中的组和测试用例的属性)很有用。

信息函数init/end_per_suite()被调用init/end_per_suite(Config),并且信息函数init/end_per_group(GroupName)被调用init/end_per_group(GroupName,Config)。但是,信息函数不能用于init/end_per_testcase(TestCase, Config),因为这些配置函数在测试用例进程上执行,并使用与测试用例相同的属性(即测试用例信息函数设置的属性TestCase())。有关有效信息属性和更多一般信息的列表,请参阅Test Case Information Function

5.15数据和私有目录

在数据目录中,data_dir测试模块具有自己的测试所需的文件。名称data_dir是测试套件的名称后面"_data"。例如,"some_path/foo_SUITE.beam"有数据目录"some_path/foo_SUITE_data/"。使用这个目录来实现可移植性,也就是说,避免在套件中硬编码目录名称。由于数据目录与测试套件存储在同一个目录中,因此即使测试套件目录的路径在测试套件实现和执行之间发生了变化,也可以依赖它在运行时的存在。

priv_dir是测试用例的私人目录。只要测试用例(或配置函数)需要将某些内容写入文件,就可以使用该目录。私人目录的名称由Common Test生成,它也创建目录。

默认情况下,Common Test每个测试运行创建一个中央专用目录,由所有测试用例共享。这并不总是合适的。特别是如果在测试运行期间多次执行相同的测试用例(也就是说,如果它们属于具有属性的测试用例组repeat),并且存在私人目录中的文件被覆盖的风险。在这些情况下,Common Test可以配置为每个测试用例和执行代替创建一个专用私人目录。这是通过标志/选项create_priv_dir(与ct_run程序,ct:run_test/1功能或测试规范术语一起使用)完成的。该选项有三种可能的值,如下所示:

  • auto_per_run

  • auto_per_tc

  • manual_per_tc

第一个值表示默认priv_dir行为,即每个测试运行创建一个私人目录。后两个值表示Common Test为每个测试用例和执行生成一个唯一的测试目录名称。如果使用自动版本,则会自动创建所有私人目录。对于许多测试用例或重复测试运行,或两者兼而有之,这可能会变得非常低效。因此,如果使用手动版本,测试用例必须告诉在需要时Common Test创建priv_dir。它通过调用该函数来完成此操作ct:make_priv_dir/0

不要依赖当前的工作目录来读取和写入数据文件,因为这不是可移植的。所有的临时文件都将被写入,priv_dir并且所有的数据文件都将位于data_dir中。另外,Common Test服务器在每个案例开始时将当前工作目录设置为测试用例日志目录。

5.16 执行环境

每个测试用例都由一个专用的Erlang进程执行。该过程在测试用例开始时产生,当测试用例完成时结束。配置功能init_per_testcaseend_per_testcase执行与测试用例相同的进程。

配置功能init_per_suiteend_per_suite执行,就像测试用例一样,专用于Erlang进程。

5.17定时超时

测试用例的默认时间限制为30分钟,除非timetrap通过suite-,group-或测试用例信息函数指定。所限定的timetrap超时值suite/0是在该套件用于各试验情况下,值(和用于配置功能init_per_suite/1end_per_suite/1init_per_group/2,和end_per_group/2)。通过group(GroupName)覆盖由group定义的timetrapsuite()和用于组中的每个测试用例GroupName及其任何子组。如果时间戳值由group/1子组定义,则会覆盖其较高级别的组。各个测试用例(通过测试用例信息功能)设置的时间戳值会覆盖组和套件级别的时间间隔。

时间戳也可以在执行测试用例或配置函数期间动态地设置或重置。这是通过调用完成的ct:timetrap/1。此功能取消当前的时间间隔并启动一个新的功能(在超时或当前功能结束之前保持有效)。

使用选项启动时指定的乘数值可以延长时间戳值multiply_timetraps。也可以让测试服务器决定自动放大时间间隔超时值。也就是说,如果工具在测试期间运行covertrace正在运行。此功能在默认情况下处于禁用状态,可以通过启动选项启用scale_timetraps

如果一个测试用例需要暂停一段时间,并且该时间也可以被多个multiply_timetraps(也可能在scale_timetraps被启用时也被放大),那么ct:sleep/1可以使用该函数(而不是例如timer:sleep/1)。

函数(fun/0{Mod,Func,Args}(MFA)元组)可以在suite-,group-和测试用例信息函数中指定为timetrap值,并作为函数的参数ct:timetrap/1

例子:

{timetrap,{my_test_utils,timetrap,[?MODULE,system_start]}}

ct:timetrap(fun() -> my_timetrap(TestCaseName, Config) end)

用户时间陷阱函数可用于以下两件事:

  • 充当计时器。函数返回时触发超时。

  • 返回一个时间转换时间值(除函数外)。

在执行时间间隔功能(在并行专用时间间隔进程上执行)之前,Common Test取消任何先前为测试用例或配置功能设置的定时器。当timetrap函数返回时,会触发超时,除非返回值是有效的时间间隔时间,例如整数或{SecMinOrHourTag,Time}元组(有关详细信息,请参阅模块common_test)。如果返回时间值,则会启动一个新的时间戳,以在指定时间后生成超时值。

用户timetrap函数可以在延迟后返回时间值。有效的时间间隔是延迟时间加上返回的时间。

5.18 记录 - 类别和详细程度级别

Common Test 为打印字符串提供以下三个主要功能:

  • ct:log(Category, Importance, Format, FormatArgs, Opts)

  • ct:print(Category, Importance, Format, FormatArgs)

  • ct:pal(Category, Importance, Format, FormatArgs)

log/1,2,3,4,5函数将一个字符串打印到测试用例日志文件中。该print/1,2,3,4功能将字符串打印到屏幕上。该pal/1,2,3,4功能将相同的字符串打印到文件和屏幕上。这些功能在模块中进行了描述ct

可选Category参数可用于对日志打印输出进行分类。类别可用于两件事情如下:

  • 将打印输出的重要性与特定的详细级别进行比较。

  • 根据用户特定的HTML样式表(CSS)格式化打印输出。

参数Importance指定与冗长级别(一般和/或每个类别设置)相比较的重要级别,确定打印输出是否可见。Importance是0到99范围内的任何整数。预定义的常量存在于ct.hrl头文件中。默认重要性级别时,?STD_IMPORTANCE(如果参数中使用Importance未设置),为50。这也是用于标准I的重要性/ O,例如,从与由打印输出io:format/2io:put_chars/1等。

Importanceverbosity开始标志/选项设置的详细级别进行比较。级别可以按类别或一般或两者设置。如果verbosity未由用户设置,则将使用100(?MAX_VERBOSITY=可见的所有打印输出)级别作为默认值。Common Test执行以下测试:

Importance >= (100-VerbosityLevel)

常数?STD_VERBOSITY值为50(见ct.hrl)。在此级别,所有标准I / O都会打印。如果设置较低的详细级别,则忽略标准I / O打印输出。详细程度等级0有效地关闭所有日志(除了由其Common Test本身打印输出)。

一般详细程度级别与任何特定类别无关。此级别设置标准I / O打印输出的阈值,未分类的ct:log/print/pal打印输出和未定义详细级别类别的打印输出。

例子:

在测试用例执行期间的一些打印:

io:format("1. Standard IO, importance = ~w~n", [?STD_IMPORTANCE]), ct:log("2. Uncategorized, importance = ~w", [?STD_IMPORTANCE]), ct:log(info, "3. Categorized info, importance = ~w", [?STD_IMPORTANCE]), ct:log(info, ?LOW_IMPORTANCE, "4. Categorized info, importance = ~w", [?LOW_IMPORTANCE]), ct:log(error, ?HI_IMPORTANCE, "5. Categorized error, importance = ~w", [?HI_IMPORTANCE]), ct:log(error, ?MAX_IMPORTANCE, "6. Categorized error, importance = ~w", [?MAX_IMPORTANCE]),

如果以一般冗长级别50(?STD_VERBOSITY)开始测试:

$ ct_run -verbosity 50

输出如下:

1. Standard IO, importance = 50 2. Uncategorized, importance = 50 3. Categorized info, importance = 50 5. Categorized error, importance = 75 6. Categorized error, importance = 99

如果测试开始时:

$ ct_run -verbosity 1 and info 75

输出如下:

3. Categorized info, importance = 50 4. Categorized info, importance = 25 6. Categorized error, importance = 99

注意,为了只指定打印输出的重要性,不需要类别参数。例子:

ct:pal(?LOW_IMPORTANCE, "Info report: ~p", [Info])

或者与常量结合:

-define(INFO, ?LOW_IMPORTANCE). -define(ERROR, ?HI_IMPORTANCE). ct:log(?INFO, "Info report: ~p", [Info]) ct:pal(?ERROR, "Error report: ~p", [Error])

功能ct:set_verbosity/2ct:get_verbosity/1可用于在测试执行期间修改和读取详细级别。

参数FormatFormatArgs ct:log/print/pal始终传递给STDLIB函数io:format/3(有关详细信息,请参见io手册页)。

ct:pal/4ct:log/5打印到日志文件的字符串添加标题。字符串也被包装在具有CSS类属性的div标签中,以便可以应用样式表格式。要禁用此功能以进行打印输出(即获得类似于使用的结果io:format/2),请调用ct:log/5使用该no_css选项

HTML Style Sheets运行测试和分析结果部分的章节中介绍了如何将类别映射到CSS标签。

通用测试会将打印输出中的特殊HTML字符(<,>和&)转义为使用ct:pal/4和生成的日志文件io:format/2。为了将带有HTML标签的字符串打印到日志中,请使用该ct:log/3,4,5功能。字符转义功能默认为禁用,ct:log/3,4,5但可以使用列表中的esc_chars选项启用Opts,请参阅ct:log/3,4,5。

如果需要禁用字符转义功能(通常出于向后兼容性的原因),请使用ct_run启动标志-no_esc_charsct:run_test/1启动选项{esc_chars,Bool}(此启动选项在测试规范中也受支持)。

有关日志文件的更多信息,请参阅Log Files运行测试和分析结果一节中的部分。

5.19 非法依赖

尽管用Common Test框架编写测试套件效率很高,但主要是由于非法依赖造成的错误。我们在运行Erlang / OTP测试套件方面的经验导致了一些更常见的错误:

  • 取决于当前目录,并写在那里:这是测试套件中的常见错误。假设当前目录与开发测试用例时用作当前目录的作者相同。许多测试用例甚至试图将临时文件写入此目录。相反,data_dirpriv_dir将被用来定位数据和书面从头文件。

  • 取决于执行命令:

在测试套件的开发过程中,不要假设测试用例或套件的执行顺序。例如,一个测试用例不能假定它所依赖的服务器已经由以前的测试用例启动。原因如下:

- 用户/操作员可以随意指定订单,也许不同的执行订单有时更具相关性或效率。 - 如果用户为测试指定了测试套件的整个目录,则套件的执行顺序取决于操作系统列出的文件的方式,因系统而异。 - 如果用户只想运行测试套件的一个子集,则一个测试用例无法成功依赖于另一个测试用例。

  • 取决于Unix:通过运行Unix命令os:cmd可能不适用于非Unix平台。

  • 嵌套测试用例:

从另一个测试用例开始测试不仅会对同一个事物进行两次测试,还会使得难以跟踪正在测试的内容。另外,如果被调用的测试用例由于某种原因失败,调用者也是如此。这样,一个错误会导致出现几个错误报告,这是要避免的。

许多测试用例函数的通用功能可以在常见的帮助函数中实现。如果这些函数对于跨套件的测试用例很有用,那么将帮助函数放入常见的帮助模块中。

  • 出错时无法崩溃或退出:如果稍后测试用例失败,则在未检查返回值是否成功的情况下发出请求即可,但只要将错误消息(在日志文件中)打印并成功返回。这样的测试案例会造成损害,因为它们在概述测试结果时会产生虚假的安全感。

  • 为随后的测试用例无效:

测试用例需要尽可能多地恢复执行环境,以便后续测试用例不会因执行顺序而崩溃。该功能end_per_testcase适用于此。