使用测试框架

Wolfram 语言提供了一个框架,用于编写和运行代码测试.
定义测试的主要函数是 VerificationTestTestCreate.
VerificationTest 是第一个 Wolfram 语言创建的测试函数. 其行为很直接:测试立即被运算,并返回一个 TestObject.
TestCreate 仅创建测试,而不立即运行它:
如要运行测试,可以使用 TestEvaluate
因为 TestCreate 不对测试进行实际运算,所以创建测试是一个快速操作:
这将运算测试. 请注意,暂停会按顺序进行运算:
你可以考虑使用 ParallelMap 对长时间的测试运行进行加速:
在本节中,您已经了解了 TestCreateVerificationTest 之间的主要区别. 如果可能,请始终使用 TestCreate 定义测试. 将创建和运算分开可以释放某些功能,包括:
编写不可变测试
在使用 TestCreate 创建测试时,不会运算输入表达式中包含的符号. 考虑以下测试:
TestObject 现在包含对符号 a 的引用,并且由于该符号已定义,因此在运行 TestEvaluate 时,测试将起作用:
但是,如果您有意或无意地清除了 a 的定义,测试将会失败:
为了避免全局依赖性,您可以使用例如 With 将值注入到测试中:
您现在可以验证 "Input" 属性不包含对原始符号的引用,这将允许测试在完全隔离的环境中运行:
编写多步操作的测试
在测试库时,单个测试通常不足以测试更复杂的用例.
例如,编写一个测试,确保 Export 创建的文件可由 Import 读取,并且表达式与开始时的表达式相同:
一个将此操作转换为一系列测试的尝试如下所示:
该测试的一个问题是,它将两个可能以不同方式失败的操作,测试数据的 ExportImport,组合在一起. 您可能会想将其拆分在多个测试中:
这种方法的缺点是破坏了测试原子性,因为第二个测试现在与第一个测试纠缠在一起. 测试的 "Input" 属性包含对名为 output 的全局变量的引用:
这意味着,如果您在单独运行最后一个测试时意外更改了 output 的值,或者在另一个测试中使用相同的符号,则测试将开始失败.
更好的替代方法是使用 IntermediateTest 来拆分测试. 单击生成的 TestObject 中的 查看中间测试结果:
该测试现在是独立的,并且始终可以单独运行. 任何中间测试结果都可以单独检查:
如果 IntermediateTest 失败,外部测试也会失败,因此您仍然会获得测试的单个总体结果. 您可以检查外部测试中的 IntermediateTest 结果,以查看外部测试的哪些步骤有效,哪些步骤失败.
使用 TestReport
TestReport 是一个具有多种用途的单一函数.

使用 TestReport 运行测试

TestReport 可用于运行多个测试:
TestReport 仅运行 "NotEvaluated" 测试,这意味着如果您有一个测试列表已运算,它将不会运算那些已运算的测试:

使用 TestReport 合并测试结果

TestReport 能够合并多个 TestReportObject. 创建两个对象:
现在可以合并报告:
TestReport 在合并结果时会自动删除重复项:

使用 TestReport 收集测试而不进行运算

通过将一些测试表达式导出到文件来创建测试文件:
当对文件运行 TestReport 时,默认情况下会执行所有未运算的测试:
可以通过使用 Identity 作为 TestEvaluationFunction 来告诉 TestReport 仅收集测试,而不运算它们. 该操作要快得多,因为没有运行测试:
收集测试可用于重新创建测试表达式并以符号方式操作它们. "TestCreate" 属性将重构原始测试表达式,封装在 HoldForm 中:
现在可以通过释放 HoldForm 来创建测试对象:
调试并记录 TestReport 运行
记录 TestReport 正在执行的操作对于调试代码至关重要. 创建一个运行时间较长的测试套件,该套件可能在运行过程中的某个时刻失败:
运行测试套件将为您提供一个进度指示器,这在大多数情况下就足够了,但它不会实时显示您可能需要的所有信息:
您可以使用 HandlerFunctions 来实时记录所有失败的测试:
TestReport 的文档中提供有完整的事件列表,但可以使用 "UnhandledEvent" 快速查看 TestReport 正在执行的操作:
中断 TestReport 控制流程
可以使用 HandlerFunctions 编写自定义 TestReport,一旦测试失败就立即失败:
编写测试文件
TestReport 编写测试文件通常是一个简单的操作. 只需将任何所需的上下文在测试文件的开头加载即可:
Needs["MyContext`"]

TestCreate[MyContext`AddOne[1], 2, TestID -> "MyContext-AddOne-Test"]
TestCreate[MyContext`AddTwo[1], 3, TestID -> "MyContext-AddTwo-Test"]
所有未包含在 TestCreate 中的代码将在每次加载测试文件时执行,即使您未运行测试. 测试文件可以包含测试列表或以编程方式创建测试的表达式:
Needs["MyContext`"]

Table[
    TestCreate[
        MyContext`AddOne[i],
        i+1,
        TestID-> "MyContext-AddOne-" <> IntegerString[i]
    ],
    {i, 1, 20}
]
为每个测试分配一个有意义的 TestID 始终是一个良好的习惯.