宏
@Assert
宏
功能:@Assert
声明 Assert 断言,测试函数内部使用,断言失败停止用例。
@Assert(leftExpr, rightExpr)
,比较leftExpr
和rightExpr
值是否相同。@Assert(condition: Bool)
,比较condition
是否为true
,即@Assert(condition: Bool)
等同于@Assert(condition: Bool, true)
。
@Bench
宏
功能:@Bench
宏用于标记要执行多次的函数并计算该函数的预期执行时间。
此类函数将分批执行,并针对整个批次测量执行时间。这种测量将重复多次以获得结果的统计分布,并将计算该分布的各种统计参数。 当前支持的参数如下:
- 中位数
- 用作误差估计的中位数 99% 置信区间的绝对值
- 中位数 99% 置信区间的相对值
- 平均值
参数化的 DSL 与 @Bench
结合的示例如下,具体语法与规则详见@TestCase
宏章节:
func sortArray<T>(arr: Array<T>): Unit
where T <: Comparable<T> {
if (arr.size < 2) { return }
var minIndex = 0
for (i in 1..arr.size) {
if (arr[i] < arr[minIndex]) {
minIndex = i
}
}
(arr[0], arr[minIndex]) = (arr[minIndex], arr[0])
sortArray(arr[1..])
}
@Test
@Configure[baseline: "test1"]
class ArrayBenchmarks{
@Bench
func test1(): Unit
{
let arr = Array(10) { i: Int64 => i }
sortArray(arr)
}
@Bench[x in 10..20]
func test2(x:Int64): Unit
{
let arr = Array(x) { i: Int64 => i.toString() }
sortArray(arr)
}
}
输出如下, 增加 Args
列,列举不同参数下的测试数据,每个参数值将作为一个性能测试用例输出测试结果,多个参数时将列举全组合场景:
--------------------------------------------------------------------------------------------------
TP: default, time elapsed: 68610430659 ns, Result:
TCS: ArrayBenchmarks, time elapsed: 68610230100 ns, RESULT:
| Case | Args | Median | Err | Err% | Mean |
|:-------|:-------|---------:|----------:|-------:|---------:|
| test1 | - | 4.274 us | ±2.916 ns | ±0.1% | 4.507 us |
| | | | | | |
| test2 | 10 | 6.622 us | ±5.900 ns | ±0.1% | 6.670 us |
| test2 | 11 | 7.863 us | ±5.966 ns | ±0.1% | 8.184 us |
| test2 | 12 | 9.087 us | ±10.74 ns | ±0.1% | 9.918 us |
| test2 | 13 | 10.34 us | ±6.363 ns | ±0.1% | 10.28 us |
| test2 | 14 | 11.63 us | ±9.219 ns | ±0.1% | 11.67 us |
| test2 | 15 | 13.05 us | ±7.520 ns | ±0.1% | 13.24 us |
| test2 | 16 | 14.66 us | ±11.59 ns | ±0.1% | 15.53 us |
| test2 | 17 | 16.21 us | ±8.972 ns | ±0.1% | 16.35 us |
| test2 | 18 | 17.73 us | ±6.288 ns | ±0.0% | 17.88 us |
| test2 | 19 | 19.47 us | ±5.819 ns | ±0.0% | 19.49 us |
Summary: TOTAL: 11
PASSED: 11, SKIPPED: 0, ERROR: 0
FAILED: 0
--------------------------------------------------------------------------------------------------
@Configure
宏
功能:@Configure
宏为测试类或测试函数提供配置参数。它可以放置在测试类或测试函数上。
语法规则为 @Configure[parameter1: <value1>,parameter2: <value2>]
其中 parameter1
是仓颉标识符,value
是任何有效的仓颉表达式。
value
可以是常量或在标有 @Configure
的声明范围内有效的任何仓颉表达式。
如果多个参数具有不同的类型,则它们可以有相同的名称。如果指定了多个具有相同名称和类型的参数,则使用最新的一个。
目前支持的配置参数有:
randomSeed
: 类型为 Int64, 为所有使用随机生成的函数设置起始随机种子。generationSteps
: 类型为 Int64 :参数化测试算法中的生成值的最大步数。reductionSteps
:类型为 Int64: 参数化测试算法中的缩减值的最大步数。
以下参数一般用于被 @Bench
修饰的 Benchmark 测试函数:
explicitGC
:类型为 ExplicitGcType: Benchmark 函数测试期间如何调用 GC。默认值为 ExplicitGcType.Light 。baseline
:类型为 String : 参数值为 Benchmark 函数的名称,作为比较 Benchmark 函数执行结果的基线。该结果值将作为附加列添加到输出中,其中将包含比较结果。batchSize
:类型为 Int64 或者 Range<Int64> : 为 Benchmark 函数配置批次大小。默认值是由框架在预热期间计算得到。minBatches
:类型为 Int64 : 配置 Benchmark 函数测试执行期间将执行多少个批次。默认值为10
。minDuration
:类型为 Duration : 配置重复执行 Benchmark 函数以获得更好结果的时间。默认值为 Duration.second * 5 。warmup
:类型为 Duration 或者 Int64 : 配置在收集结果之前重复执行 Benchmark 函数的时间或次数。默认值为 Duration.second 。当值为 0 时,表示没有 warmup , 此时执行次数按用户输入的batchSize
乘minBatches
计算得到,当batchSize
未指定时将抛出异常。measurement
:类型为 Measurement :描述性能测试需要收集的信息。默认值为 TimeNow() ,它在内部使用 DateTime.now() 进行测量。
用户可以在 @Configure
宏中指定其他配置参数,这些参数将来可能会用到。
如果测试类使用 @Configure
宏指定配置,则该类中的所有测试函数都会继承此配置参数。
如果此类中的测试函数也标有 @Configure
宏,则配置参数将从类和函数合并,其中函数级宏优先。
@Expect
宏
功能: @Expect
声明 Expect 断言,测试函数内部使用,断言失败继续执行用例。
@Expect(leftExpr, rightExpr)
,比较leftExpr
和rightExpr
是否相同。@Expect(condition: Bool)
,比较condition
是否为true
,即@Expect(condition: Bool)
等同于@Expect(condition: Bool, true)
。
@Parallel
宏
功能: @Parallel
宏可以修饰测试类。被 @Parallel
修饰的测试类中的测试用例可并行执行。
- 所有相关的测试用例应该各自独立,不依赖于任何可变的共享的状态值。
beforeAll()
和afterAll()
应该是可重入的,以便可以在不同的进程中多次运行。- 需要并行化的测试用例本身应耗时较长。否则并行化引入的多次
beforeAll()
和afterAll()
可能会超过并行化的收益。 - 不允许与
@Bench
同时使用。由于性能用例对底层资源敏感,用例是否并行执行,将影响性能用例的结果,因此禁止与@Bench
同时使用。
@PowerAssert
宏
功能:@PowerAssert(condition: Bool)
检查传递的表达式是否为真,并显示包含传递表达式的中间值和异常的详细图表。
其打印的详细信息如下:
REASON: `foo(10, y: test + s) == foo(s.size, y: s) + bar(a)` has been evaluated to false
Assert Failed: `(foo(10, y: test + s) == foo(s.size, y: s) + bar(a))`
| | |_|| | |_| | |_|| | |_||
| | "123" | "123" | "123" | 1 |
| |__________|| | |______| | |______|
| "test123" | | 3 | 33 |
|______________________| |_________________| |
0 | 1 |
|__________________________|
34
--------------------------------------------------------------------------------------------------
请注意,现在并非所有 AST 节点都受支持。支持的节点如下:
- 任何二进制表达式
- 算术表达式,如
a + b == p % b
。 - 布尔表达式,如
a || b == a && b
。 - 位表达式,如
a | b == a ^ b
。
- 算术表达式,如
- 成员访问如
a.b.c == foo.bar
。 - 括号化的表达式,如
(foo) == ((bar))
。 - 调用表达式,如
foo(bar()) == Zoo()
。 - 引用表达式,如
x == y
。 - 赋值表达式,如
a = foo
,实际上总是 Unit (表示为()
),请注意,赋值表达式的左值不支持打印。 - 一元表达式,如
!myBool
。 is
表达式,如myExpr is Foo
。as
表达式,如myExpr as Foo
。
如果传递了其他节点,则图中不会打印它们的值。 返回的 Tokens 是初始表达式,但包装到一些内部包装器中,这些包装器允许进一步打印中间值和异常。
@RawBench
宏
功能:与 @Bench
相同,但可以访问更高级的 API 。被 @RawBench
修饰的函数主体将被执行一次,并且应使用 RawBencher
类进行相关配置。 @RawBench
宏不能与 @TestCase
和 @Bench
一起应用在同一个测试用例上。
@Skip
宏
功能:@Skip
修饰已经被 @TestCase
/ @bench
修饰的函数,使该测试用例被跳过。
语法规则为 @Skip[expr]
。
expr
暂只支持true
,表达式为true
时,跳过该测试,其他均为false
。- 默认
expr
为true
即@Skip[true]
==@Skip
。
@Test
宏
功能:@Test
宏应用于顶级函数或顶级类,使该函数或类转换为单元测试类。
如果是顶级函数,则该函数新增一个具有单个测试用例的类提供给框架使用,同时该函数仍旧可被作为普通函数调用。
标有 @Test
的类必须满足以下条件:
- 它必须有一个无参构造函数。
- 不能从其他类继承。
实现说明:
@Test
宏为任何用它标记的类引入了一个新的基类:unittest.TestCases
。unittest.TestCases
的所有公共和受保护成员(请参阅下面的 API 概述)将在标有@Test
的类或函数中变得可用,包括两个字段: 1. 包含此测试的TestContext
实例的ctx
。 2. 包含类的名称的name
。 单元测试框架的用户不应修改这些字段,因为这可能会导致不可预期的错误。
@TestCase
宏
功能:@TestCase
宏用于标记单元测试类内的函数,使这些函数成为单元测试的测试用例。
标有 @TestCase
的函数必须满足以下条件:
- 该类必须用
@Test
标记。 - 该函数返回类型必须是 Unit 。
@Test
class Tests {
@TestCase
func fooTest(): Unit {...}
}
测试用例可能有参数,在这种情况下,开发人员必须使用参数化测试 DSL 指定这些参数的值:
@Test[x in source1, y in source2, z in source3]
func test(x: Int64, y: String, z: Float64): Unit {}
此 DSL 可用于 @Test
、@RawBench
、@Bench
和 @TestCase
宏,其中 @Test
仅在顶级函数上时才可用。如果测试函数中同时存在 @Bench
和 @TestCase
,则只有 @Bench
可以包含 DSL 。
在 DSL 语法中,in
之前的标识符(在上面的示例中为 x
、y
和 z
)必须直接对应于函数的参数,参数源(在上面的示例中为source1
、source2
和 source3
)是任何有效的仓颉表达式(该表达式类型必须实现接口 DataStrategy<T> ,详见下文)。
参数源的元素类型(此类型作为泛型参数 T
提供给接口 DataStrategy<T> )必须与相应函数参数的类型严格相同。
目前,参数化测试最多支持 5 个参数,支持的参数源类型如下:
- Arrays:
x in [1,2,3,4]
。 - Ranges:
x in 0..14
。 - 随机生成的值:
x in random()
。 - 从 json 文件中读取到的值:
x in json("filename.json")
。 - 从 csv 文件中读取到的值:
x in csv("filename.csv")
。
高级用户可以通过定义自己的类型并且实现 DataStrategy<T> 接口来引入自己的参数源类型。
使用 random()
的随机生成函数默认支持以下类型:
若需要新增其他的类型支持
random()
,可以让该类型扩展 unittest.prop_test.Arbitrary 。 在参数有多个值时,beforeEach
/afterEach
不会在不同值下重复执行而仅会执行一次。若确实需要在每个值下做初始化和去初始化,需要在测试主体中写。对于性能测试场景,应使用@RawBench
。暂时不对此类情况提供特殊 API ,因为大多数情况下,此类代码取决于具体参数值。
@Timeout
宏
功能: @Timeout
指示测试应在指定时间后终止。它有助于测试可能运行很长时间或陷入无限循环的复杂算法。
语法规则为 @Timeout[expr]
expr
的类型应为 std.time.Duration 。
其修饰测试类时为每个相应的测试用例提供超时时间。
@Types
宏
功能:@Types
宏为测试类或测试函数提供类型参数。它可以放置在测试类或测试函数上。
语法规则为 @Types[Id1 in <Type1, Type2, Type3>, Id2 in <Type4, Type5> ...]
其中 Id1
、Id2
... 是有效类型参数标识符,Type1
、Type2
、Type3
...是有效的仓颉类型。
@Types
宏有以下限制:
- 必须与
@Test
,@TestCase
或@Bench
宏共同使用。 - 一个声明只能有一个
@Types
宏修饰。 - 该声明必须是具有与
@Types
宏中列出的相同类型参数的泛型类或函数。 - 类型列表中列出的类型不能相互依赖,例如
@Types[A in <Int64, String>, B in <List<A>>]
将无法正确编译。但是,在为该类内的测试函数提供类型时,可以使用为测试类提供的类型。例如:
@Test
@Types[T in <...>]
class TestClass<T> {
@TestCase
@Types[U in <Array<T>>]
func testfunc<U>() {}
}
该机制可以与其他测试框架功能一起使用,例如 @Configure
等。