unittest 包
介绍
unittest 包使用元编程语法来支持单元测试功能,用于编写和运行可重复的测试用例,并进行结构化测试。
仓颉编译器提供 --test
编译选项来自动组织源码中的测试用例以及生成可执行程序的入口函数。
宏功能介绍
@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()
的随机生成函数默认支持以下类型:
Unit
Bool
- 所有内置的 integer 类型(包含有符号和无符号)
- 所有内置的 float 类型
Ordering
- 所有已支持类型的数组类型
- 所有已支持类型的 Option 类型
若需要新增其他的类型支持
random()
,可以让该类型扩展unittest.prop_test.Arbitrary
。有关详细信息,请参阅 “高级特性” 章节。
在参数有多个值时,
beforeEach
/afterEach
不会在不同值下重复执行而仅会执行一次。若确实需要在每个值下做初始化和去初始化,需要在测试主体中写。对于性能测试场景,应使用@RawBench
。暂时不对此类情况提供特殊 API ,因为大多数情况下,此类代码取决于具体参数值。
@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
--------------------------------------------------------------------------------------------------
@RawBench
宏
与 @Bench
相同,但可以访问更高级的 API 。被 @RawBench
修饰的函数主体将被执行一次,并且应使用 RawBencher
类进行相关配置。 @RawBench
宏不能与 @TestCase
和 @Bench
一起应用在同一个测试用例上。
@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
宏,则配置参数将从类和函数合并,其中函数级宏优先。
@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
等。
@Skip
宏
@Skip[expr]
修饰已经被 @TestCase
修饰的函数
expr
暂只支持true
,表达式为true
时,跳过该测试,其他均为false
- 默认
expr
为true
即@Skip[true]
==@Skip
@Timeout
宏
@Timeout[expr]
指示测试应在指定时间后终止。它有助于测试可能运行很长时间或陷入无限循环的复杂算法。 expr
的类型应为 std.time.Duration
。
其修饰测试类时为每个相应的测试用例提供超时时间。
@Parallel
宏
@Parallel
宏可以修饰测试类。被 @Parallel
修饰的测试类中的测试用例,将被分别独立到不同的进程中并行运行。
- 所有相关的测试用例应该各自独立,不依赖于任何可变的共享的状态值。
beforeAll()
和afterAll()
应该是可重入的,以便可以在不同的进程中多次运行。- 需要并行化的测试用例本身应耗时较长。否则并行化引入的多次
beforeAll()
和afterAll()
可能会超过并行化的收益。 - 不允许与
@Bench
同时使用。由于性能用例对底层资源敏感,用例是否并行执行,将影响性能用例的结果,因此禁止与@Bench
同时使用。
@Assert
宏
@Assert
声明 Assert 断言,测试函数内部使用,断言失败停止用例
@Assert(leftExpr, rightExpr)
,比较leftExpr
和rightExpr
值是否相同@Assert(condition: Bool)
,比较condition
是否为true
,即@Assert(condition: Bool)
等同于@Assert(condition: Bool, true)
@PowerAssert
宏
@PowerAssert
与 @Assert
类似, 但是将打印更详细的中间值和异常信息。
打印的详细信息如下:
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
--------------------------------------------------------------------------------------------------
@Expect
宏
@Expect
声明 Expect 断言,测试函数内部使用,断言失败继续执行用例
@Expect(leftExpr, rightExpr)
,比较leftExpr
和rightExpr
是否相同@Expect(condition: Bool)
,比较condition
是否为true
,即@Expect(condition: Bool)
等同于@Expect(condition: Bool, true)
编译选项 --test
介绍
--test
是仓颉编译器 cjc 的内置编译选项
- 测试用例编译,配合 unittest 库使用
- 条件编译,测试代码应放在以 _test.cj 结尾的文件中,如果包编译时未使用
--test
则忽略该文件
使用方法
使用 --test
编译测试文件
例如,有如下一段测试代码:
@Test
public class TestA {
@TestCase
public func case1(): Unit {
print("case1\n")
}
}
cjc test.cj -o test --test && ./test
test.cj
是含有测试用例的仓颉文件test
是编译输出的可执行程序--test
是编译选项,当使用--test
选项编译时,程序入口不再是main
,而是由编译器生成的入口函数。./test
执行测试用例
使用 --test
组织包测试
目录结构:
application
├── pkgc
| ├── a1.cj
| └── a1_test.cj
| └── a2.cj
| └── a2_test.cj
- a1_test.cj 是 a1.cj 的单元测试
- a2_test.cj 是 a2.cj 的单元测试
cjc -p pkgc -o test --test && ./test
-p
整包编译选项pkgc
是含有测试用例的包路径test
是编译输出的可执行程序--test
是编译选项,当使用--test
选项编译时,-程序入口不再是main
,而是由编译器生成的入口函数./test
执行所有测试用例
unittest 没有严格限制只能测试同 package 的内容,但通常情况下由于单元测试需要测试仅包内可见的内容,推荐使用如上形式组织 package 的单元测试。
编译器约束 (仅在使能 --test
时生效)
@Test
宏不能修饰非 TopLevel 的函数。@Test
宏不能修饰泛型类。@Test
宏修饰的类必须包含一个无参数的构造函数。@Testcase
宏只能修饰返回值类型为Unit
的成员函数。@Testcase
宏不能修饰foreign
函数。@Testcase
宏不能修饰泛型函数。
运行选项介绍
使用方法
运行 cjc 编译的可执行文件 test ,添加参数选项
./test --bench --filter MyTest.*Test,-stringTest
--bench
默认情况下,只有被 @TestCase
修饰的函数会被执行。在使用 --bench
的情况下只执行 @Bench
宏修饰的用例。
--filter
如果您希望以测试类和测试用例过滤出测试的子集,可以使用 --filter=测试类名.测试用例名
的形式来筛选匹配的用例,例如:
--filter=*
匹配所有测试类--filter=*.*
匹配所有测试类所有测试用例(结果和*相同)--filter=*.*Test,*.*case*
匹配所有测试类中以 Test 结尾的用例,或者所有测试类中名字中带有 case 的测试用例--filter=MyTest*.*Test,*.*case*,-*.*myTest
匹配所有 MyTest 开头测试类中以 Test 结尾的用例,或者名字中带有 case的用例,或者名字中不带有 myTest 的测试用例
另外,--filter
后有 =
和无 =
均支持。
--timeout-each=timeout
使用 --timeout-each=timeout
选项等同于对所有的测试类使用 @Timeout[timeout]
修饰。若代码中已有 @Timeout[timeout]
,则将被代码中的超时时间覆盖,即选项的超时时间配置优先级低于代码中超时时间配置。
timeout
的值应符合以下语法:
number ('millis' | 's' | 'm' | 'h')
例如: 10s
, 9millis
等。
- millis : 毫秒
- s : 秒
- m : 分钟
- h : 小时
--parallel
打开 --parallel
选项将使测试框架在单独的多个进程中并行执行不同的测试类。
测试类之间应该是相互独立的,不依赖于共享的可变的状态值。
程序静态初始化可能会发生多次。
不允许与 --bench
同时使用。由于性能用例对底层资源敏感,用例是否并行执行,将影响性能用例的结果,因此禁止与 --bench
同时使用。
--parallel=<BOOL>
<BOOL>
可为true
或false
,指定为true
时,测试类可被并行运行,并行进程个数将受运行系统上的CPU核数控制。另外,--parallel
可省略=true
。--parallel=nCores
指定了并行的测试进程个数应该等于可用的 CPU 核数。--parallel=NUMBER
指定了并行的测试进程个数值。该数值应该为正整数。--parallel=NUMBERnCores
指定了并行的测试进程个数值为可用的 CPU 核数的指定数值倍。该数值应该为正数(支持浮点数或整数)。
--option=value
以 --option=value
形式提供的任何非上述的选项均按以下规则处理并转换为配置参数(类似于 @Configure
宏处理的参数),并按顺序应用:
option
与 value
为任意自定义的运行配置选项键值对,option
可以为任意通过 -
连接的英文字符,转换到 @Configure
时将转换为小驼峰格式。value
的格式规则如下:
注:当前未检查 option
与 value
的合法性,并且选项的优先级低于代码中 @Configure
的优先级。
- 如果省略
=value
部分,则该选项被视为Bool
值true
, 例如:--no-color
生成配置条目noColor = true
。 - 如果``value
严格为
true或
false,则该选项被视为具有相应含义的
Bool值:
--no-color=false生成配置条目
noColor = false` 。 - 如果
value
是有效的十进制整数,则该选项被视为Int64
值 , 例如:--random-seed=42
生成配置条目randomSeed = 42
。 - 如果
value
是有效的十进制小数,则该选项被视为Float64
值 , 例如:--angle=42.0
生成配置条目angle = 42
。 - 如果
value
是带引号的字符串文字(被"
符号包围),则该选项将被视为String
类型,并且该值是通过解码"
符号之间的字符串值来生成的,并处理转义符号,例如\n
、\t
、\"
作为对应的字符值。例如,选项--mode="ABC \"2\""
生成配置条目mode = "ABC \"2\""
; - 除上述情况外
value
值将被视为String
类型,该值从所提供的选项中逐字获取。例如,--mode=ABC23[1,2,3]
生成配置条目mode = "ABC23[1,2,3]"
。
package unittest
enum TimeoutInfo
public enum TimeoutInfo {
| NoTimeout
| Timeout(Duration)
指定测试超时的信息。目前,可以通过两种方式提供此超时信息:
- 在执行测试文件时打开
--timeout-each
选项。 - 在测试类或测试用例上使用
@Timeout[expr]
宏。
NoTimeout
没有提供超时信息。
Timeout(Duration)
指定超时时间。
func fromDefaultConfiguration
public static func fromDefaultConfiguration(): TimeoutInfo
功能:返回测试过程的 CLI 参数中提供的超时信息。
返回值:超时信息。
func applyDefault
public func applyDefault(default: TimeoutInfo): TimeoutInfo
功能:返回根据默认值更新的超时信息。
参数:
- default :来自于 CLI 参数或测试类上的超时信息,作为默认值
返回值:超时信息。
interface Measurement
public interface Measurement {
func measure(f: () -> Unit): Float64
func toString(f: Float64): String
}
功能:在性能测试过程中可以收集和分析各种数据的接口。性能测试框架使用的特定实例由 @Configure
宏的 measurement
参数指定。
func measure
func measure(f: () -> Unit): Float64
功能: 返回将用于统计分析的测量数据的表示。
参数:
- f : 是一个 lambda,应该测量它的执行信息。
返回值:测量得到的数据
func toString
func toString(f: Float64): String
功能:将测量数据的浮点表示转换为将在性能测试输出中使用的字符串。
参数:
- f : 测量数据的浮点表示
返回值:按规则转换后的字符串
class RawBench
public class RawBencher<T> {
public init()
public init(initial: T)
public func beforeInvocation(before: () -> T)
public func afterInvocation(after: (T) -> Unit )
public func runBench(bench: (T) -> Unit )
}
extend RawBencher<T> where T <: Unit {
public func afterInvocation(after: () -> Unit )
public func runBench(bench: () -> Unit )
}
提供较低级别的性能测试 API ,以便对性能测试过程进行更细粒度的控制。使用时应小心,因为如果使用不当,可能会产生误导性结果。
init
public init()
功能:默认构造函数。只应在 @RawBench
注释的性能测试用例中调用。
异常:
- IllegalStateException : 如果不是在
@RawBench
注释的性能测试用例中创建的,则抛出IllegalStateException
init
public init(initial: T)
功能:带初始化值的默认构造函数。只应在 @RawBench
注释的性能测试用例中调用。
参数:
- initial : 测试用例会使用的初始化值
异常:
- IllegalStateException : 如果不是在
@RawBench
注释的性能测试用例中创建的,则抛出IllegalStateException
func beforeInvocation
beforeInvocation(before: () -> T)
功能:提供在每次调用 runBench
之前执行并且不统计入性能测试结果的代码。
为了提供可靠的性能测试结果,before
的执行应该尽可能具有确定性,并且它应该比实际的性能测试函数花费更少的时间。如果可能的话,堆分配的数量也应该最小化。
参数:
- before: 带有入参的被注册的函数。
异常:
- IllegalStateException: 当在
runBench
调用后或者其内部调用时将抛出异常。
func afterInvocation
public func afterInvocation(after: (T) -> Unit)
功能:提供在每次调用 runBench
之后执行并且不统计入性能测试结果的代码。
为了提供可靠的性能测试结果,after
的执行应该尽可能具有确定性,并且它应该比实际的性能测试函数花费更少的时间。如果可能的话,堆分配的数量也应该最小化。
特别是,即使它是在 beforeInvocation
回调之后立即执行而不在其间调用 runBench
,它也应该以相同的方式工作。
参数:
- after: 带有入参的被注册的函数。
异常:
- IllegalStateException :当在
runBench
调用后或者其内部调用时将抛出异常。
func afterInvocation
public func afterInvocation(after: () -> Unit)
功能:提供在每次调用 runBench
之后执行并且不统计入性能测试结果的代码。
为了提供可靠的性能测试结果,after
的执行应该尽可能具有确定性,并且它应该比实际的性能测试函数花费更少的时间。如果可能的话,堆分配的数量也应该最小化。
特别是,即使它是在 beforeInvocation
回调之后立即执行而不在其间调用 runBench
,它也应该以相同的方式工作。
参数:
- after: 无入参的被注册的函数。
异常:
- IllegalStateException :当在
runBench
调用后或者其内部调用时将抛出异常。
func runBench
public func runBench(bench: (T) -> Unit)
功能:执行实际的性能测试流程。所有设置都继承自包含测试用例的 @Configure
。
性能测试用例的输入数据来自构造函数或前一个 beforeInvocation
调用。
参数:
- bench: 带有入参的被注册的函数。
func runBench
public func runBench(bench: () -> Unit)
功能:执行实际的性能测试流程。所有设置都继承自包含测试用例的 @Configure
。
性能测试用例的输入数据来自构造函数或前一个 beforeInvocation
调用。
参数:
- bench: 无入参的被注册的函数。
示例
如何对 Array.sort
进行性能测试:
@Test
class BenchSort {
@RawBench[len in [1,100,10000]]
func sort(len: Int64): Unit {
let b = RawBencher<Array<Int64>>()
b.beforeInvocation{ => Array(len,{ i: Int64 => len-i}) }
b.runBench { x =>
x.sort()
}
}
}
请注意,上例每次都会分配新数组,这可能会导致后台进行大量 GC 工作,从而导致结果不太精确。
如何更好得对 Array.sort
进行性能测试,消除过多对象分配:
@Test
class BenchSort {
@RawBench[len in [1,100,10000]]
func sort(len: Int64): Unit {
let b = RawBencher<Array<Int64>>()
let array = Array(len,{ i: Int64 => len-i})
b.beforeInvocation{ => array.clone() }
b.runBench { x =>
x.sort()
}
}
}
请记住,并不总是可以在 beforeInvocation
中将对象恢复到其原始状态的同时满足 beforeInvocation
的所有要求。
例如,如下即为不正确的方式对 ArrayList.append
进行性能测试:
@Test
class BenchAppend {
@RawBench
func append(): Unit {
let b = RawBencher<ArrayList<Int64>>()
let data = ArrayList()
b.beforeInvocation{ =>
data.clear()
data
}
b.runBench { x =>
x.append(1)
}
}
}
上例看起来正确,但是它并不满足 beforeInvocation
的要求。
它的问题是,根据是否调用基准测试,ArrayList.clear()
的工作方式有所不同。
一种正确的,但依然不够好的对 ArrayList.append
的性能测试方案:
@Test
class BenchAppend {
@RawBench
func append(): Unit {
let b = RawBencher<ArrayList<Int64>>()
b.beforeInvocation{ =>
ArrayList()
}
b.runBench { x =>
x.append(1)
}
}
}
这里的问题是 ArrayList
的创建比 ArrayList.append
花费更多的时间,因此 append
的实际时间可能会在测量噪声中丢失。
如下为一种更好的方式对 ArrayList.append
进行测试:
@Test
class BenchAppend {
@RawBench[times in [10,100]]
func append(times: Int64): Unit {
let b = RawBencher<ArrayList<Int64>>()
b.beforeInvocation{ =>
ArrayList()
}
b.runBench { x =>
for i in 0..times {
x.append(1)
}
}
}
}
这仍然不是最好的方法,因为性能测试结果将包括循环本身的执行时间。但目前在很多场景上它都比其他选择更好。
class TimeNow
public struct TimeNow <: Measurement {
public init(unit: ?TimeUnit)
public init()
public func measure(f: () -> Unit): Float64
public func toString(duration: Float64): String
}
功能: Measurement
的实现,用于测量执行一个函数所花费的时间。
init
public init(unit: ?TimeUnit)
功能: unit
参数用于指定打印结果时将使用的时间单位。
参数:
- unit: 指定的时间单位
init
public init()
功能:自动选择输出格式的默认构造函数。
func measure
public func measure(f: () -> Unit): Float64
功能:计算将用于统计分析的测量数据
参数:
- f :被计算时间的执行体
返回值:
计算得到的数据,用于统计分析
func toString
public func toString(duration: Float64): String
功能:按时间单位打印传入的时间值
参数:
- duration: 需要被打印的时间数值
返回值:按指定单位输出的时间数字字符串
enum TimeUnit
public enum TimeUnit {
| Nanos
| Micros
| Millis
| Seconds
}
功能:可以在 TimeNow
类构造函数中使用的时间单位。
Nanos
Nanos
功能: 单位为纳秒
Micros
Micros
功能: 单位为微秒
Millis
Millis
功能: 单位为毫秒
Seconds
Seconds
功能: 单位为秒
enum ExplicitGcType
public enum ExplicitGcType{
Disabled |
Light |
Heavy
}
功能:用于指定 @Configure
宏的 explicitGC
配置参数。表示 GC 执行的三种不同方式。
Disabled
Disabled
功能: GC不会被框架显式调用。
Light
Light
功能: std.runtime.GC(heavy: false)
将在 Benchmark 函数执行期间由框架显式调用。这是默认设置。
Heavy
Heavy
功能: std.runtime.GC(heavy: true)
将在性能测试执行期间由框架显式调用。
class JsonStrategy
public class JsonStrategy<T> <: DataStrategy<T> where T <: Serializable<T> {
public override func provider(configuration: Configuration): SerializableProvider<T>
}
功能:DataStrategy
对 JSON 数据格式的序列化实现
func provider
public override func provider(configuration: Configuration): SerializableProvider<T>
功能:生成序列化数据迭代器
参数:
- configuration: 数据配置信息
返回值:序列化迭代器对象
func json
public func json<T>(fileName: String): JsonStrategy<T> where T <: Serializable<T>
功能:
返回一个 JsonStrategy<T>
对象, T 可被序列化,数据值从 JSON 文件中读取。
参数:
- fileName : JSON 格式的文件地址,可为相对地址
返回值:一个 JsonStrategy<T>
对象, T 可被序列化,数据值从 JSON 文件中读取。
class CsvStrategy
public class CsvStrategy<T> <: DataStrategy<T> where T <: Serializable<T> {
public override func provider(configuration: Configuration): SerializableProvider<T>
}
功能:DataStrategy
对 CSV 数据格式的序列化实现
func provider
public override func provider(configuration: Configuration): SerializableProvider<T>
功能:生成序列化数据迭代器
参数:
- configuration: 数据配置信息
返回值:序列化迭代器对象
func csv
public func csv<T>(
fileName: String,
delimiter!: Char = ',',
quoteChar!: Char = '"',
escapeChar!: Char = '"',
commentChar!: Option<Char> = None,
header!: Option<Array<String>> = None,
skipRows!: Array<UInt64> = [],
skipColumns!: Array<UInt64> = [],
skipEmptyLines!: Bool = false
): CsvStrategy<T> where T <: Serializable<T>
功能:
返回一个 CsvStrategy<T>
对象, T 可被序列化,数据值从 CSV 文件中读取。
参数:
- fileName : CSV 格式的文件地址,可为相对地址,不限制后缀名。
- delimiter :一行中作为元素分隔符的符号。默认值为
,
(逗号)。 - quoteChar :- 括住元素的符号。默认值为
"
(双引号)。 - escapeChar :转义括住元素的符号。默认值为
"
(双引号)。 - commentChar :注释符号,跳过一行。必须在一行的最左侧。默认值是
None
(不存在注释符号)。 - header :提供一种方式覆盖第一行。
- 当 header 被指定时,文件的第一行将被作为数据行,指定的 header 将被使用。
- 当 header 被指定,同时第一行通过指定
skipRows
被跳过时,第一行将被忽略,指定的 header 将被使用。 - 当 header 未被指定时,即值为
None
时,文件的第一行将被作为表头。此为默认值。
- skipRows :指定需被跳过的数据行号,行号从 0 开始。默认值为空数组
[]
。 - skipColumns :指定需被跳过的数据列号,列号从 0 开始。当有数据列被跳过,并且用户指定了自定义的 header 时,该 header 将按照跳过后的实际数据列对应。默认值为空数据
[]
。 - skipEmptyLines :指定是否需要跳过空行。默认值为
false
。
返回值:CsvStrategy<T>
对象, T 可被序列化,数据值从 CSV 文件中读取。
func tsv
public func tsv<T>(
fileName: String,
quoteChar!: Char = '"',
escapeChar!: Char = '"',
commentChar!: Option<Char> = None,
header!: Option<Array<String>> = None,
skipRows!: Array<UInt64> = [],
skipColumns!: Array<UInt64> = [],
skipEmptyLines!: Bool = false
): CsvStrategy<T> where T <: Serializable<T>
功能:
返回一个 CsvStrategy<T>
对象, T 可被序列化,数据值从 TSV 文件中读取。
Parameters:
- fileName : TSV 格式的文件地址,可为相对地址,不限制后缀名。
- quoteChar :- 括住元素的符号。默认值为
"
(双引号)。 - escapeChar :转义括住元素的符号。默认值为
"
(双引号)。 - commentChar :注释符号,跳过一行。必须在一行的最左侧。默认值是
None
(不存在注释符号)。 - header :提供一种方式覆盖第一行。
- 当 header 被指定时,文件的第一行将被作为数据行,指定的 header 将被使用。
- 当 header 被指定,同时第一行通过指定
skipRows
被跳过时,第一行将被忽略,指定的 header 将被使用。 - 当 header 未被指定时,即值为
None
时,文件的第一行(跳过后的实际数据)将被作为表头。此为默认值。
- skipRows :指定需被跳过的数据行号,行号从 0 开始。默认值为空数组
[]
。 - skipColumns :指定需被跳过的数据列号,列号从 0 开始。当有数据列被跳过,并且用户指定了自定义的 header 时,该 header 将按照跳过后的实际数据列对应。默认值为空数据
[]
。 - skipEmptyLines :指定是否需要跳过空行。默认值为
false
。
返回值:CsvStrategy<T>
对象, T 可被序列化,数据值从 TSV 文件中读取。
如何使用 json 文件进行参数化测试
用例示例:
@Test[user in json("users.json")]
func test_user_age(user: User): Unit {
@Expect(user.age, 100)
}
json 文件示例:
[
{
"age": 100
},
{
"age": 100
}
]
创建一种被用作测试函数参数的类,该类实现接口 Serializable
。
class User <: Serializable<User> {
User(let age: Int64) {}
public func serialize(): DataModel {
DataModelStruct()
.add(Field("age", DataModelInt(age)))
}
public static func deserialize(dm: DataModel): User {
if (let Some(dms) <- dm as DataModelStruct) {
if (let Some(age) <- dms.get("age") as DataModelInt) {
return User(age.getValue())
}
}
throw Exception("Can't deserialize user.")
}
}
任何实现 Serializable
的类型都可以用作参数类型,包括默认值:
@Test[user in json("numbers.json")]
func test(value: Int64)
@Test[user in json("names.json")]
func test(name: String)
如何使用 csv/tsv 文件进行参数化测试
在单元测试中,可以通过传入 csv/tsv 文件地址进行参数化测试。
CSV 文件每一行的数据应当被表示成一个 Serializable<T>
对象,它的成员名是文件每一列头的值,成员值是 DataModelString
类型的对应列号上的值。
举例来说,有一个 testdata.csv
文件,具有如下内容:
username,age
Alex Great,21
Donald Sweet,28
有几种方式可以序列化上述数据:
- 将数据表示为
HashMap<String, String>
类型。
具体示例为:
from std import collection.HashMap
from std import unittest.*
from std import unittest.testmacro.*
@Test[user in csv("testdata.csv")]
func testUser(user: HashMap<String, String>) {
@Assert(user["username"] == "Alex Great" || user["username"] == "Donald Sweet")
@Assert(user["age"] == "21" || user["age"] == "28")
}
- 将数据表示为
Serializable<T>
类型数据,其String
类型的数据可被反序列化为DataModelStruct
格式对象。
具体示例为:
from serialization import serialization.*
from std import convert.*
from std import unittest.*
from std import unittest.testmacro.*
public class User <: Serializable<User> {
public User(let name: String, let age: UInt32) {}
public func serialize(): DataModel {
let dms = DataModelStruct()
dms.add(Field("username", DataModelString(name)))
dms.add(Field("age", DataModelString(age.toString())))
return dms
}
static public func deserialize(dm: DataModel): User {
var data: DataModelStruct = match (dm) {
case dms: DataModelStruct => dms
case _ => throw DataModelException("this data is not DataModelStruct")
}
let name = String.deserialize(data.get("username"))
let age = String.deserialize(data.get("age"))
return User(name, UInt32.parse(age))
}
}
@Test[user in csv("testdata.csv")]
func testUser(user: User) {
@Assert(user.name == "Alex Great" || user.name == "Donald Sweet")
@Assert(user.age == 21 || user.age == 28)
}
Re-exported declarations
unittest
重导出了下列类型:
public import unittest.common.checkDataStrategy
public from std import unittest.common.Configuration
public from std import unittest.common.ConfigurationKey
public from std import unittest.common.DataProvider
public from std import unittest.common.DataStrategy
public from std import unittest.common.DataShrinker
public from std import unittest.common.DataFinisher
public import unittest.prop_test.random
public import unittest.prop_test.Arbitrary
public import unittest.prop_test.Shrink
package unittest.testmacro
macro Bench
public macro Bench(input: Tokens): Tokens
功能:@Bench
宏的实现, @Bench
可用于在 @Test
宏内声明的函数。可以使用 @Configure
宏来配置 @Bench
宏的行为。
参数:
- input :
@Bench
修饰的函数的 Tokens
返回值:经过处理后的 Tokens ,该用例将作为性能测试用例
异常 MacroException - 如果 input 不是 FuncDecl ,抛出异常
macro Bench
public macro Bench(dslArguments: Tokens, input: Tokens): Tokens
功能: @Bench
宏的实现, @Bench
可用于在 @Test
宏内声明的函数。可以使用 @Configure
宏来配置 @Bench
宏的行为。
参数:
- input :
@Bench
修饰的 Tokens - dslArguments - 参数的配置 DSL
返回值:经过处理后的 Tokens ,该用例将作为性能测试用例
异常 MacroException : 如果 input 不是 FuncDecl ,抛出异常
macro RawBench
public macro RawBench(input: Tokens): Tokens
功能: @RawBench
宏的实现, @RawBench
可用于在 @Test
宏内声明的函数。可以使用 @Configure
宏来配置 @RawBench
宏的行为。提供比 @Bench
宏更细粒度的 API 。
参数:
- input :
@RawBench
修饰的 Tokens
返回值:经过处理后的 Tokens ,该用例将作为性能测试用例
异常 MacroException : 如果 input 不是 FuncDecl ,抛出异常
macro RawBench
public macro RawBench(dslArguments: Tokens, input: Tokens): Tokens
功能: @RawBench
宏的实现, @RawBench
可用于在 @Test
宏内声明的函数。可以使用 @Configure
宏来配置 @RawBench
宏的行为。提供比 @Bench
宏更细粒度的 API 。
参数:
- input :
@RawBench
修饰的 Tokens - dslArguments - 参数的配置 DSL
返回值:经过处理后的 Tokens ,该用例将作为性能测试用例
异常 MacroException : 如果 input 不是 FuncDecl ,抛出异常
macro Expect
public macro Expect(input: Tokens): Tokens
@Expect
宏的实现, @Testcase
宏声明的函数内部使用, 用于断言。@Expect(expr1, expr2)
接受两个表达式,左边是实际执行的值,右边是期望的值。@Expect(condition: Bool)
比较 condition 是否为 true 。比较左右两边是否相同,与 @Assert
不同的是断言失败继续执行用例。
参数:
- input :
@Expect
修饰的 Tokens
返回值:经过处理后的 Tokens ,用于判断测试中的变量是否符合预期
macro Assert
public macro Assert(input: Tokens): Tokens
@Assert
宏的实现, @Testcase
宏声明的函数内部使用,用于断言。@Assert(expr1, expr2)
接受两个表达式,左边是实际执行的值,右边是期望的值。@Assert(condition: Bool)
比较 condition 是否为 true 。比较左右两边是否相同,与 @Expect
不同的是断言失败停止用例。
参数:
- input :
@Assert
修饰的 Tokens
返回值:经过处理后的 Tokens ,用于判断测试中的变量是否符合预期
macro PowerAssert
public macro PowerAssert(input: Tokens): Tokens
功能:@PowerAssert(condition: Bool)
检查传递的表达式是否为真,并显示包含传递表达式的中间值和异常的详细图表。
请注意,现在并非所有 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 是初始表达式,但包装到一些内部包装器中,这些包装器允许进一步打印中间值和异常。
参数:
- input :
@Assert
修饰的 Tokens
返回值:经过处理后的 Tokens ,初始表达式,但包装到一些内部包装器中,这些包装器允许进一步打印中间值和异常。
macro Skip
public macro Skip(input: Tokens): Tokens
功能:@Skip
宏的实现,只在用于测试类内使用 @Testcase
宏声明的函数,@Skip
修饰使用 @Testcase
宏声明的函数后,执行测试会跳过该用例。
参数:
- input :
@Skip
修饰的 Tokens
返回值:经过处理后的 Tokens,用于跳过该测试用例
异常 MacroException : 如果 input 不是 ClassDecl 或者 FuncDecl ,抛出异常
macro Skip
public macro Skip(attr: Tokens, input: Tokens): Tokens
功能:@Skip
宏的实现,只在用于测试类内使用 @Testcase
宏声明的函数,@Skip
修饰使用 @Testcase
宏声明的函数后,执行测试会跳过该用例。
参数:
- input :
@Skip
修饰的 Tokens - attr -
@Skip[attr]
中的 attr ,只支持为 true ,其他参数用例不跳过。
返回值:经过处理后的 Tokens,用于跳过该测试用例
异常 MacroException : 如果 input 不是 ClassDecl 或者 FuncDecl ,抛出异常
macro Timeout
public macro Timeout(attr: Tokens, input: Tokens): Tokens
@Timeout
宏可以与 @TestCase
和 @Test
。指定超时值。
参数:
- attr : 类型为
std.time.Duration
的表达式,作为用例执行的超时时间 - input :
@Timeout
修饰的 Tokens
返回值:经过处理后的 Token
异常 MacroException : 如果 input 不是 ClassDecl 或者 FuncDecl ,抛出异常
macro Parallel
public macro Parallel(input: Tokens): Tokens
@Parallel
宏可以与 @Test
共同使用,指定该用例类中所有用例可并行执行。
参数:
- input :
@Parallel
修饰的 Tokens
返回值:经过处理后的 Token
macro TestCase
public macro TestCase(input: Tokens): Tokens
功能: @Testcase
宏的实现,只在 @Test
修饰的 class 内部生效, 修饰非测试类成员函数时无效。@Testcase
修饰的函数必须为返回值类型为 Unit
的函数
参数:
- input :
@Testcase
修饰的 Tokens
返回值:经过处理后的 Tokens ,用于将测试类中的函数变为测试用例
异常 MacroException : 如果 input 不是 FuncDecl ,抛出异常
macro TestCase
public macro TestCase(dslArguments: Tokens, input: Tokens): Tokens
功能: @Testcase
宏的实现,只在 @Test
修饰的 class 内部生效, 修饰非测试类成员函数时无效。@Testcase
修饰的函数必须为返回值类型为 Unit
的函数
参数:
- input :
@Testcase
修饰的 Tokens - dslArguments - 参数的配置 DSL
返回值:经过处理后的 Tokens ,用于将测试类中的函数变为测试用例
异常 MacroException : 如果 input 不是 FuncDecl ,抛出异常
macro Test
public macro Test(input: Tokens): Tokens
功能: @Test
宏的实现,修饰 Top-Level 类或者 Top-Level 的函数(此时也会宏展开为测试类)。 @Test
修饰的类不能是泛型类且必须有无参构造函数。 @Test
修饰的 Top-Level 的函数必须为返回值类型为 Unit
的函数
参数:
- input :
@Test
修饰的 Tokens
返回值:经过处理后的 Tokens ,用于将修饰的类和函数变为测试类
异常:
- MacroException : 如果 input 不是 ClassDecl 或 FuncDecl ,抛出异常
- IllegalArgumentException - 如果输入是 ClassDecl ,并且在类体内中找到了 PrimaryCtorDecl ,并且 PrimaryCtorDecl 体内为空,抛出异常
macro Test
public macro Test(dslArguments: Tokens, input: Tokens): Tokens
功能: @Test
宏的实现,修饰 Top-Level 类或者 Top-Level 的函数(此时也会宏展开为测试类)。 @Test
修饰的类不能是泛型类且必须有无参构造函数。 @Test
修饰的 Top-Level 的函数必须为返回值类型为 Unit
的函数
参数:
- input :
@Test
修饰的 Tokens - dslArguments - 参数的配置 DSL
返回值:经过处理后的 Tokens ,用于将修饰的类和函数变为测试类
异常:
- MacroException : 如果 input 不是 ClassDecl 或 FuncDecl ,或者 classDecl 的无携带参数的构造函数,抛出异常
macro Configure
public macro Configure(dslArguments: Tokens, input: Tokens): Tokens
功能: @Configure
与 @Test
或 @TestCase
或 @Bench
宏一起放置在声明上。如果与 @Test
宏一起使用,必须放在 @Test
宏之后。用于设置测试用例的配置参数。
语法:@Configure(parameter1:value1,parameter2:value2)
。
@Configure
宏仅用于设置内部配置参数,在单元测试框架中的使用方式请参阅各组件文档。
参数:
- input :
@Configure
修饰的 Tokens - dslArguments - 参数的配置 DSL
返回值:通常返回 input
相同的值。
异常:
- MacroException : 如果 dslArguments 格式不正确,则抛出异常。
macro Types
public macro Types(dslArguments: Tokens, input: Tokens): Tokens
功能:@Types
与 @Test
或 @TestCase
或 @Bench
宏一起放置在声明上。用于设置带泛型参数的测试用例的类型参数。
语法:@Types[T in <Int64, String, Array<Int64>>, U in <SomeClass, SomeClass2>]
.
更多信息,详见 《带类型参数的参数化测试》章节。
参数:
- input :
@Types
修饰的 Tokens - dslArguments - 类型参数 DSL
返回值:返回使用传入的类型参数替换泛型参数的类或函数声明 Tokens 。
异常:
- MacroException : 如果 dslArguments 格式不正确,则抛出异常。
package unittest.common
class Configuration
public class Configuration <: ToString
public init()
public func get<T>(key: String): ?T
public func set<T>(key: String, value: T): Unit
public func remove<T>(key: String): ?T
public func clone(): Configuration
public func toString(): String
}
存储 @Configure
宏生成的 unittest
配置数据的对象。Configuration
是一个类似 HashMap
的类,但它的键不是键和值类型,而是 String
类型,和任何给定类型的值
init
public init()
功能:构造一个空的实例。
func get
public func get<T>(key: String): ?T
功能:获取 key 对应的值。
参数:
- key : 键名称
- T : 泛型参数,用于在对象中查找对应类型的值
返回值:未找到时返回 None ,找到对应类型及名称的值时返回 Some() 。
func set
public func set<T>(key: String, value: T): Unit
功能:给对应键名称和类型设置值。
参数:
- key :键名称
- T :类型名称
func remove
public func remove<T>(key: String): ?T
功能:删除对应键名称和类型的值。
参数:
- key :键名称
- T :类型名称
返回值:当存在该值时返回该值,当不存在时返回 None 。
func clone
public func clone(): Configuration
功能:拷贝一份对象
返回值:拷贝的对象
func toString
public func toString(): String
功能:该对象的字符化对象,当内部对象未实现 ToString
接口时,输出 '
返回值:字符串
interface DataProvider
public interface DataProvider<T> {
func provide(): Iterable<T>
prop isInfinite: Bool
}
DataStrategy 的组件,用于提供测试数据, T 指定提供者提供的数据类型。
func provide()
func provide(): Iterable<T>
功能:获取数据迭代器。
返回值:数据迭代器
prop isInfinite
prop isInfinite: Bool
功能:是否无法穷尽。
interface DataShrinker
public interface DataShrinker<T> {
func shrink(value: T): Iterable<T>
}
DataStrategy 的组件,用于在测试期间缩减数据,T 指定该收缩器处理的数据类型。
func shrink
func shrink(value: T): Iterable<T>
功能:获取类型 T 的值并生成较小值的集合。什么被认为是“较小”取决于数据的类型。
参数:
- value : 被缩减的值
返回值:较小值的集合,当数据无法再被缩减时返回空集合。
interface DataFinisher
public interface DataFinisher<T> {
func finish(value: T): Unit
}
DataStrategy 的组件,用于在测试完成后对数据进行后处理。
func finish
func finish(value: T): Unit
功能:对值进行后处理步骤
参数:
- value : 被处理的值
interface DataStrategy
public interface DataStrategy<T> {
func provider(configuration: Configuration): DataProvider<T>
func shrinker(configuration: Configuration): DataShrinker<T>
func finisher(configuration: Configuration): DataFinisher<T>
}
为参数化测试提供数据的策略,T 指定该策略操作的数据类型。
func provider
func provider(configuration: Configuration): DataProvider<T>
功能:获取提供测试数据组件。
参数:
- configuration : 配置信息
返回值:提供测试数据的组件对象。
func shrinker
func shrinker(configuration: Configuration): DataShrinker<T>
功能:获取缩减测试数据的组件。
参数:
- configuration : 配置信息
返回值:缩减测试数据的组件对象。
func finisher
func finisher(configuration: Configuration): DataFinisher<T>
功能:获取对测试数据进行测试后操作的组件。
参数:
- configuration : 配置信息
返回值:对测试数据进行测试后操作测试数据的组件对象。
extend Array
extend Array<T> <: DataStrategy<T> & DataProvider<T>
为 Array 实现了 DataStrategy
@Test[x in [1,2,3]]
func test(x: Int64) {}
extend Range
extend Range<T> <: DataStrategy<T> & DataProvider<T>
为 Range 实现了 DataStrategy
@Test[x in (0..5)]
func test(x: Int64) {}
package unittest.prop_test
interface Generator
public interface Generator<T> {
func next(): T
}
生成器生成 T 类型的值。
func next
func next(): T
功能:获取生成出来的 T 类型的值。
返回值:生成的 T 类型的值
interface Arbitrary
public interface Arbitrary<T> {
static func arbitrary(random: Random): Generator<T>
}
生成 T 类型随机值的接口
func arbitrary
static func arbitrary(random: Random): Generator<T>
功能:获取生成 T 类型随机值生成器。
参数:
- random :随机数
返回值:生成 T 类型随机值生成器。
extend Types by Arbitrary
extend Unit <: Arbitrary<Unit> {}
extend Bool <: Arbitrary<Bool> {}
extend UInt8 <: Arbitrary<UInt8> {}
extend UInt16 <: Arbitrary<UInt16> {}
extend UInt32 <: Arbitrary<UInt32> {}
extend UInt64 <: Arbitrary<UInt64> {}
extend Int8 <: Arbitrary<Int8> {}
extend Int16 <: Arbitrary<Int16> {}
extend Int32 <: Arbitrary<Int32> {}
extend Int64 <: Arbitrary<Int64> {}
extend IntNative <: Arbitrary<IntNative> {}
extend UIntNative <: Arbitrary<UIntNative> {}
extend Float16 <: Arbitrary<Float16> {}
extend Float32 <: Arbitrary<Float32> {}
extend Float64 <: Arbitrary<Float64> {}
extend Ordering <: Arbitrary<Ordering> {}
extend Char <: Arbitrary<Char> {}
extend String <: Arbitrary<String> {}
extend Array<T> <: Arbitrary<Array<T>> where T <: Arbitrary<T> {}
extend Option<T> <: Arbitrary<Option<T>> where T <: Arbitrary<T> {}
extend Ordering <: Arbitrary<Ordering> {}
interface Shrink
public interface Shrink<T> {
func shrink(): Iterable<T>
}
将 T 类型的值缩减到多个“更小的”值
func shrink
func shrink(): Iterable<T>
功能:将该值缩小为一组可能的“较小”值
返回值:一组可能的“较小”值的迭代器
extend Types by Shrink
extend Unit <: Shrink<Unit> {}
extend Bool <: Shrink<Bool> {}
extend UInt8 <: Shrink<UInt8> {}
extend UInt16 <: Shrink<UInt16> {}
extend UInt32 <: Shrink<UInt32> {}
extend UInt64 <: Shrink<UInt64> {}
extend UIntNative <: Shrink<UIntNative> {}
extend Int8 <: Shrink<Int8> {}
extend Int16 <: Shrink<Int16> {}
extend Int32 <: Shrink<Int32> {}
extend Int64 <: Shrink<Int64> {}
extend IntNative <: Shrink<IntNative> {}
extend Float16 <: Shrink<Float16> {}
extend Float32 <: Shrink<Float32> {}
extend Float64 <: Shrink<Float64> {}
extend Ordering <: Shrink<Ordering> {}
extend Array<T> <: Shrink<Array<T>> where T <: Shrink<T> {}
extend Option<T> <: Shrink<Option<T>> where T <: Shrink<T> {}
extend Char <: Shrink<Char> {}
extend String <: Shrink<String> {}
class RandomDataProvider
public class RandomDataProvider<T> <: DataProvider<T> where T <: Arbitrary<T> {
public init(configuration:Configuration)
public override func provide(): Iterable<T>
public override prop isInfinite: Bool
}
使用随机数据生成的 DataProvider 接口的实现。
init
public init(configuration:Configuration)
功能:构造一个随机数据提供者的对象。
参数:
- configuration :配置对象,必须包含一个随机生成器,名称为
random
,类型为random.Random
异常 IllegalArgumentException:当 configuration 不包含 random 实例时,抛出异常。
func provide
public override func provide(): Iterable<T>
功能:提供随机化生成的数据。
返回值:从 T 的任意实例创建的无限迭代器
prop isInfinite
public override prop isInfinite: Bool
功能:是否生成无限的数据。
返回值:始终返回 true
class RandomDataShrinker
public class RandomDataShrinker<T> <: DataShrinker<T> {
public override func shrink(value: T): Iterable<T>
}
使用随机数据生成的 DataShrinker 接口的实现。
func shrinker
public override func shrink(value: T): Iterable<T>
功能:获取值的缩减器。
参数:
- value:参数值
返回值: 如果参数实现了 Shrink 接口,则返回缩减后的迭代器,如果未实现,则返回空的数组。
class RandomDataStrategy
public class RandomDataStrategy<T> <: DataStrategy<T> where T <: Arbitrary<T> {
public override func provider(configuration: Configuration): RandomDataProvider<T>
public override func shrinker(_: Configuration): RandomDataShrinker<T>
}
使用随机数据生成的 DataStrategy 接口的实现。
func provider
public override func provider(configuration: Configuration): RandomDataProvider<T>
功能:获取随机数据的提供者。
参数:
- configuration:参数配置信息
返回值: RandomDataProvider 的实例
func shrinker
public override func shrinker(_: Configuration): RandomDataShrinker<T>
功能:获取随机数据的缩减器。
参数:
- configuration:参数配置信息
返回值: RandomDataShrinker 的实例
func random
public func random<T>(): RandomDataStrategy<T> where T <: Arbitrary<T>
功能:使用随机数据生成的 RandomDataStrategy 接口的实现。
返回值:使用随机数据生成的 RandomDataStrategy 接口的实例
示例
简单示例
// Function under test
func concat(s1: String, s2: String): String {
return s1 + s2
}
// Test itself
@Test
class Tests {
@TestCase
func testConcat(): Unit {
//期望 concat("1", "2") 运行结果为 "12"
@Assert(concat("1", "2"), "12")
}
}
执行命令
cjc --test test.cj && ./main
输出内容:
TCS: TestCCC, time elapsed: 0 ns, RESULT:
[ PASSED ] CASE: sayhi (0 ns)
--------------------------------------------------------------------------------------------------
Summary: TOTAL: 1
PASSED: 1, SKIPPED: 0, ERROR: 0
FAILED: 0
使用 TopLevel 函数:
// Function under test
func concat(s1: String, s2: String): String {
return s1 + s2
}
// Test itself
@Test
func testConcat(): Unit {
//期望 concat("1", "2") 运行结果为 "12"
@Assert(concat("1", "2"), "12")
}
执行命令
cjc --test test.cj && ./main
输出内容:
TCS: TestCase_sayhi, time elapsed: 0 ns, RESULT:
[ PASSED ] CASE: sayhi (0 ns)
--------------------------------------------------------------------------------------------------
Summary: TOTAL: 1
PASSED: 1, SKIPPED: 0, ERROR: 0
FAILED: 0
@Test 修饰类的使用
@Test
修饰类,展开的测试类使用 @Bench
、 @TestCase
和 @Skip
的功能。
代码如下:
@Test
class MySimpleTest {
//无 @TestCase 修饰非测试函数
func test1(): Unit {
println("hi1")
}
// TestCase 跟 Bench 可以同时修饰一个函数。此时函数既可以为单个测试用例,也可以作为 Benchmark 被多次执行,通过 --bench 打开 Bench 执行模式
@Bench
@TestCase
//跳过
@Skip
func test2(): Unit {
println("hi2")
}
// 最简单的 Benchmark 用例函数,输出 Benchmark 测试结果数据。
@Bench
//不跳过
@Skip[false]
func test3(): Unit {
println("hi3")
}
@TestCase
func test4(): Unit {
println("hi4")
}
}
运行结果如下:
hi3
hi3
hi3
hi3
hi3
...
hi3
hi3
hi3
hi3
hi4
TCS: MySimpleTest, time elapsed: 6580329566 ns, RESULT:
| Case | Median | Err | Err% |
|------ |----------:|-----------:|--------:|
| test3 | 30.23 us | ±5.148 us | ±17.0% |
[ PASSED ] CASE: test4 (50300 ns)
--------------------------------------------------------------------------------------------------
Summary: TOTAL: 4
PASSED: 2, SKIPPED: 2, ERROR: 0
FAILED: 0
自定义逻辑函数的使用
用户可以通过重写 afterAll
, afterEach
, beforeAll
, beforeEach
在测试函数调用前后处理自定义的逻辑。
代码如下:
@Test
class MySimpleTest {
@TestCase
func test1(): Unit {
println("test1")
}
@TestCase
func test2(): Unit {
println("test2")
}
@TestCase
func test3(): Unit {
println("test3")
}
public override func afterAll(): Unit {
println("afterAll")
}
public override func afterEach(): Unit {
println("afterEach")
}
public override func beforeAll(): Unit {
println("beforeAll")
}
public override func beforeEach(): Unit {
println("beforeEach")
}
}
运行结果如下:
beforeAll
beforeEach
ccc1
afterEach
beforeEach
ccc2
afterEach
beforeEach
ccc3
afterEach
afterAll
TCS: MySimpleTest, time elapsed: 0 ns, RESULT:
[ PASSED ] CASE: ccc1 (0 ns)
[ PASSED ] CASE: ccc2 (0 ns)
[ PASSED ] CASE: ccc3 (0 ns)
--------------------------------------------------------------------------------------------------
Summary: TOTAL: 3
PASSED: 3, SKIPPED: 0, ERROR: 0
FAILED: 0
@PowerAssert 示例
@Test
修饰类,展开的测试类使用 @TestCase
的功能以及 @PowerAssert
的断言用法。
代码如下:
func foo(x: Int64, y!: String = "foo") { y.size / x }
func bar(_: Int64) { 33 }
@Test
class TestA {
@TestCase
func case1(): Unit {
let s = "123"
let a = 1
@PowerAssert(foo(10, y: "test" + s) == foo(s.size, y: s) + bar(a))
}
}
运行结果如下:
TCS: TestA_case1, time elapsed: 176000000 ns, RESULT:
[ FAILED ] CASE: case1 (175000000 ns)
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
--------------------------------------------------------------------------------------------------
Summary: TOTAL: 1
PASSED: 0, SKIPPED: 0, ERROR: 0
FAILED: 1, listed below:
TCS: TestA_case1, CASE: case1
参数化测试简单示例
想象一下,您有一个 String
的 reverse
实现,并且您想在许多不同的字符串上测试它。
您不需要为此编写单独的测试,您可以使用参数化测试 DSL 来代替:
func reverse(s: String) {
let temp = s.toRuneArray()
temp.reverse()
return String(temp)
}
@Test
func testA(): Unit {
@Expect("cba", reverse("abc"))
}
@Test[s in ["ab", "bc", "Hello", ""]]
func testB(s: String): Unit {
@Expect(s != reverse(s))
}
输出如下:
--------------------------------------------------------------------------------------------------
TP: default, time elapsed: 250236 ns, Result:
TCS: TestCase_testA, time elapsed: 65077 ns, RESULT:
[ PASSED ] CASE: testA (51004 ns)
TCS: TestCase_testB, time elapsed: 71557 ns, RESULT:
[ FAILED ] CASE: testB (45465 ns)
REASON: After 4 generation steps:
s =
with randomSeed = 1706690422527775114
Expect Failed: `(s != reverse ( s ) == true)`
left: false
right: true
Summary: TOTAL: 2
PASSED: 1, SKIPPED: 0, ERROR: 0
FAILED: 1, listed below:
TCS: TestCase_testB, CASE: testB
--------------------------------------------------------------------------------------------------
可以使用 random()
生成器来测试函数在随机值下的行为:
func reverse(s: String) {
let temp = s.toRuneArray()
temp.reverse()
return String(temp)
}
@Test[s in random()]
func testC(s: String): Unit {
@Expect(s != reverse(s))
}
输出如下:
--------------------------------------------------------------------------------------------------
TP: default, time elapsed: 170350 ns, Result:
TCS: TestCase_testC, time elapsed: 166996 ns, RESULT:
[ FAILED ] CASE: testC (160175 ns)
REASON: After 3 generation steps and 1 reduction steps:
s =
with randomSeed = 1691464803229918621
Expect Failed: `(s != reverse ( s ) == true)`
left: false
right: true
Summary: TOTAL: 1
PASSED: 0, SKIPPED: 0, ERROR: 0
FAILED: 1, listed below:
TCS: TestCase_testC, CASE: testC
--------------------------------------------------------------------------------------------------
请注意,由于随机生成的性质,每次运行测试时随机测试可能会给出不同的结果。 如果你想获得更稳定、可重复的结果,强烈建议配置随机生成的种子:
func reverse(s: String) {
let temp = s.toRuneArray()
temp.reverse()
return String(temp)
}
@Test[s in random()]
@Configure[randomSeed: 1] // randomSeed is set to 1
func testC(s: String): Unit {
@Expect(s != reverse(s))
}
如果配置了随机种子,随机生成器将在每次运行测试时生成相同的值:
--------------------------------------------------------------------------------------------------
TP: default, time elapsed: 297568 ns, Result:
TCS: TestCase_testC, time elapsed: 293383 ns, RESULT:
[ FAILED ] CASE: testC (284963 ns)
REASON: After 4 generation steps and 1 reduction steps:
s =
with randomSeed = 1
Expect Failed: `(s != reverse ( s ) == true)`
left: false
right: true
Summary: TOTAL: 1
PASSED: 0, SKIPPED: 0, ERROR: 0
FAILED: 1, listed below:
TCS: TestCase_testC, CASE: testC
--------------------------------------------------------------------------------------------------
请注意,输出包含运行测试时使用的 randomSeed
值。如果您将此值作为 randomSeed
参数值放入测试代码中,它应该产生完全相同的结果。
带多个参数的参数化测试
如果测试函数包含多个参数,则所有参数都可以通过参数化测试 DSL 提供。当前支持的最大参数数量为 5。
@Test[a in ["Hello", "a", ""], b in (0..4)]
func testB(a: String, b: Int64): Unit {
println("${a}, ${b}")
@Expect(a.size != b)
}
测试框架应用此值的不同组合来检查给定条件:
Hello, 0
Hello, 1
Hello, 2
Hello, 3
a, 0
a, 1
a, 2
a, 3
, 0
, 1
, 2
, 3
--------------------------------------------------------------------------------------------------
TP: default, time elapsed: 334280 ns, Result:
TCS: TestCase_testB, time elapsed: 236204 ns, RESULT:
[ FAILED ] CASE: testB (142534 ns)
REASON: After 6 generation steps:
a = a
b = 1
with randomSeed = 1706691095691478341
Expect Failed: `(a . size != b == true)`
left: false
right: true
[ FAILED ] CASE: testB (188789 ns)
REASON: After 9 generation steps:
a =
b = 0
with randomSeed = 1706691095691478341
Expect Failed: `(a . size != b == true)`
left: false
right: true
Summary: TOTAL: 1
PASSED: 0, SKIPPED: 0, ERROR: 0
FAILED: 1, listed below:
TCS: TestCase_testB, CASE: testB
--------------------------------------------------------------------------------------------------
这也可以与随机生成的值一起使用:
@Test[a in ["Hello", "a", ""], b in random()]
@Configure[randomSeed: 0]
func testB(a: String, b: Int64): Unit {
println("${a}, ${b}")
@Expect(a.size != b)
}
输出结果为:
Hello, -144895307711186549
a, 1
a, 0
--------------------------------------------------------------------------------------------------
TP: default, time elapsed: 118190 ns, Result:
TCS: TestCase_testB, time elapsed: 114702 ns, RESULT:
[ FAILED ] CASE: testB (108172 ns)
REASON: After 2 generation steps and 1 reduction steps:
a = a
b = 1
with randomSeed = 0
Expect Failed: `(a . size != b == true)`
left: false
right: true
Summary: TOTAL: 1
PASSED: 0, SKIPPED: 0, ERROR: 0
FAILED: 1, listed below:
TCS: TestCase_testB, CASE: testB
--------------------------------------------------------------------------------------------------
如果需要,所有参数都可以随机化(在本例中我们不执行 println
,因为我们不建议将随机字符串打印到标准输出,它可能会破坏您的终端):
@Test[a in random(), b in random()]
@Configure[randomSeed: 0]
func testB(a: String, b: Int64): Unit {
@Expect(a.size != b)
}
输出如下:
--------------------------------------------------------------------------------------------------
TP: default, time elapsed: 3226316 ns, Result:
TCS: TestCase_testB, time elapsed: 3222958 ns, RESULT:
[ FAILED ] CASE: testB (3214676 ns)
REASON: After 53 generation steps and 2 reduction steps:
a = _
b = 1
with randomSeed = 0
Expect Failed: `(a . size != b == true)`
left: false
right: true
Summary: TOTAL: 1
PASSED: 0, SKIPPED: 0, ERROR: 0
FAILED: 1, listed below:
TCS: TestCase_testB, CASE: testB
--------------------------------------------------------------------------------------------------
带类型参数的参数化测试
如果您需要在不同类型上运行测试类或函数,您可以使用类型参数进行测试。
这些测试与普通仓颉泛型类型和函数类似:您只需将测试类或测试函数设为泛型类或函数,然后使用特殊的 @Types
宏列出要运行测试的类型。
from std import collection.*
@Test
@Types[T in <String, Char, Int64>]
class HashSetTest<T> where T <: Hashable & Equatable<T> {
@TestCase
func testFoo(): Unit {
@Expect(HashSet<T>().size, 0)
}
}
当与 --test
编译器选项一起使用时,此测试的工作方式与三个对应声明为 HashSetTest<String>
、HashSetTest<Char>
和 HashSetTest<Int64>
类型的测试类相同:。示例输出如下所示:
--------------------------------------------------------------------------------------------------
TP: default, time elapsed: 33645 ns, Result:
TCS: HashSetTest<String>, time elapsed: 15947 ns, RESULT:
[ PASSED ] CASE: testFoo (9520 ns)
TCS: HashSetTest<Char>, time elapsed: 7644 ns, RESULT:
[ PASSED ] CASE: testFoo (5852 ns)
TCS: HashSetTest<Int64>, time elapsed: 6175 ns, RESULT:
[ PASSED ] CASE: testFoo (2570 ns)
Summary: TOTAL: 3
PASSED: 3, SKIPPED: 0, ERROR: 0
FAILED: 0
--------------------------------------------------------------------------------------------------
相同的机制可以应用于测试函数:
from std import collection.*
@Test
class HashSetTest {
@TestCase
@Types[T in <String, Char, Int64>]
func testFoo<T>(): Unit where T <: Hashable & Equatable<T> {
@Expect(HashSet<T>().size, 0)
}
}
当与 --test
编译器选项一起使用时,此测试将像测试类包含三个测试函数 testFoo<String>
、 testFoo<Char>
和 testFoo<Int64>
一样工作。示例输出如下所示:
--------------------------------------------------------------------------------------------------
TP: default, time elapsed: 30664 ns, Result:
TCS: HashSetTest, time elapsed: 27987 ns, RESULT:
[ PASSED ] CASE: testFoo<String> (13846 ns)
[ PASSED ] CASE: testFoo<Char> (3044 ns)
[ PASSED ] CASE: testFoo<Int64> (4612 ns)
Summary: TOTAL: 3
PASSED: 3, SKIPPED: 0, ERROR: 0
FAILED: 0
--------------------------------------------------------------------------------------------------
带超时信息的测试用例
from std import time.*
@Test
@Timeout[Duration.second]
func test(): Unit {
println("Hello from test")
while (true) {}
}
测试用例进入无限循环,但框架在一秒钟后中断它并打印报告:
Hello from test
--------------------------------------------------------------------------------------------------
TP: default, time elapsed: 1005261753 ns, Result:
TCS: TestCase_test, time elapsed: 1005255775 ns, RESULT:
[ FAILED ] CASE: test (1005191011 ns)
REASON: Wait timeout, process not exit.
Summary: TOTAL: 1
PASSED: 0, SKIPPED: 0, ERROR: 0
FAILED: 1, listed below:
TCS: TestCase_test, CASE: test
--------------------------------------------------------------------------------------------------
如果没有 @Timeout
,测试过程本身就会陷入无限循环。
高级特性
基于属性的随机化测试(实验特性)
基于属性的随机化测试是仓颉单元测试框架的一项实验性功能,允许您使用随机生成的值编写测试。
为了使用基于属性的随机化测试,您需要使用 random()
函数作为参数化值生成器:
@Test
class RandomTests {
@TestCase[p in random()]
func sayhi2(p: Int64): Unit {
@Assert(p > 0)
}
}
@Test[v in random()]
func ttt(v: String) {
@Assert(v[0] != v[1])
}
在这种模式下,相应的函数参数必须全部实现接口 Arbitrary
和 Shrink
(请参阅 unittest.prop_test
文档部分)。当运行这样的测试时,这些参数是随机生成的,并自动缩减到较小的值。
基于属性的随机测试对于根据大量随机数据快速测试代码非常有用,但您需要小心,因为生成/缩减过程是随机的,每次都可能会得到不同的结果。
随机数据生成使用以下配置参数:
generationSteps: Int64
用于随机生成值的步数(默认为 200)reductionSteps: Int64
用于缩减值的步数(默认为 200)randomSeed: Int64
框架内使用的随机值生成器的种子值。
增加 generationSteps
和, 或 reductionSteps
的值将提高生成的质量和,或缩减测试过程中使用的值,但也可能会增加测试的运行时间。建议谨慎。
randomSeed
可以显式设置此参数以使测试可重现,使用相同此参数的相同测试保证每次运行时都会产生相同的结果,如果不提供此参数,结果可能每次都会改变。
随机数生成支持自定义类型
要使用 random()
使自定义类型可用于随机生成,需要执行以下步骤:
- 该类型必须实现
unittest.prop_test
包中的Arbitrary
接口 - 如果您还想支持缩减值,则还必须实现
unittest.prop_test
包中的Shrink
接口
示例如下:
from std import unittest.prop_test.*
from std import random.Random
class SimpleClass <: ToString {
SimpleClass(let name: String, let number: Int64) {}
public func toString(): String {
"SimpleClass(${name}, ${number})"
}
}
extend SimpleClass <: Arbitrary<SimpleClass> {
public static func arbitrary(random: Random): Generator<SimpleClass> {
let stringGenerator = String.arbitrary(random)
let numberGenerator = Int64.arbitrary(random)
return Generators.generate {
SimpleClass(stringGenerator.next(), numberGenerator.next())
}
}
}
@Test[x in random()]
@Configure[randomSeed: 0]
func foo(x: SimpleClass): Unit {
// we can now write tests for SimpleClass
@Expect(x.name.size != x.number)
}
输出如下
--------------------------------------------------------------------------------------------------
TP: default, time elapsed: 2600034 ns, Result:
TCS: TestCase_foo, time elapsed: 2596678 ns, RESULT:
[ FAILED ] CASE: foo (2587608 ns)
REASON: After 53 generation steps
x = SimpleClass(_, 1)
with randomSeed = 0
Expect Failed: `(x . name . size != x . number == true)`
left: false
right: true
Summary: TOTAL: 1
PASSED: 0, SKIPPED: 0, ERROR: 0
FAILED: 1, listed below:
TCS: TestCase_foo, CASE: foo
--------------------------------------------------------------------------------------------------
请注意,随机生成不一定实现 ToString
,但如果没有它,它将无法在测试输出中打印生成的值。
增加新的数据生成策略
为了为参数化测试创建新的数据值源,需要执行以下步骤:
- 相应的类型必须实现
unittest.common
包中的DataStrategy<T>
接口,其中T
是您要支持的参数类型。 - 涉及此类类型的任何表达式都可以在数据 DSL 中用作值源。
示例如下:
class SimpleStorage {
SimpleStorage() {}
let v1: String = "1"
let v2: String = "2"
let v3: String = "3"
let v4: String = "4"
}
extend SimpleStorage <: DataProvider<String> {
public func provide(): Iterable<String> {
return [v1, v2, v3, v4]
}
public prop isInfinite: Bool {
get() { false }
}
}
extend SimpleStorage <: DataStrategy<String> {
public func provider(_: Configuration): DataProvider<String> {
return this
}
}
// SimpleStorage can now be used in data DSL as a source of String
@Test[x in SimpleStorage()]
func foo(x: String): Unit {
@Expect(x.size > 0)
}
附录
API list
在框架中,部分 API 由于整体实现结构要求,属性为对外可见,但用户不应直接使用如下 API 。因此此处仅列举 API 列表,而不详细说明对应 API 使用方式。
package unittest
public class AssertException <: Exception {
public init()
public init(message: String)
}
public class AssertIntermediateException <: Exception {
public let expression: String
public let position: Int64
public let originalException: Exception
public func getOriginalStackTrace(): String
}
public open class UnittestException <: Exception
public class UnittestCliOptionsFormatException <: UnittestException
public func assertEqual<T>(leftStr:String, rightStr:String, expected: T, actual: T): Unit where T <: Equatable<T>
public func assertEqual<T, C>(leftStr:String, rightStr:String, expected: C, actual: C): Unit where T <: Equatable<T>, C <: Array<T>
public func expectEqual<T>(leftStr:String, rightStr:String, expected: T, actual: T): Unit where T <: Equatable<T>
public func expectEqual<T, C>(leftStr:String, rightStr:String, expected: C, actual: C): Unit where T <: Equatable<T>, C <: Array<T>
public class BenchParameterTestCase<T> <: ParameterTestCase<T>{
public init(
isSkip!: Bool,
caseName!: String,
argumentNames!: Array<String>,
doRunBatch!: (T, Int64, Int64) -> Unit,
strategy!: DataStrategy<T>,
configuration!: Configuration,
timeout!: TimeoutInfo
)
}
public class CombinedDataFinisher2<T0, T1> <: DataFinisher<(T0, T1)> {
public override func finish(value: (T0, T1))
}
public class CombinedDataFinisher3<T0, T1, T2> <: DataFinisher<(T0, T1, T2)> {
public override func finish(value: (T0, T1, T2))
}
public class CombinedDataFinisher4<T0, T1, T2, T3> <: DataFinisher<(T0, T1, T2, T3)> {
public override func finish(value: (T0, T1, T2, T3))
}
public class CombinedDataFinisher5<T0, T1, T2, T3, T4> <: DataFinisher<(T0, T1, T2, T3, T4)> {
public override func finish(value: (T0, T1, T2, T3, T4))
}
public class CombinedDataProvider2<T0, T1> <: DataProvider<(T0, T1)> {
public func positions(): Array<Int64>
public prop isInfinite: Bool
public func provide(): Iterator<(T0, T1)>
}
public class CombinedDataProvider3<T0, T1, T2> <: DataProvider<(T0, T1, T2)> {
public func positions(): Array<Int64>
public prop isInfinite: Bool
public func provide(): Iterator<(T0, T1, T2)>
}
public class CombinedDataProvider4<T0, T1, T2, T3> <: DataProvider<(T0, T1, T2, T3)> {
public func positions(): Array<Int64> { pos }
public prop isInfinite: Bool
public func provide(): Iterator<(T0, T1, T2, T3)>
}
public class CombinedDataProvider5<T0, T1, T2, T3, T4> <: DataProvider<(T0, T1, T2, T3, T4)> {
public func positions(): Array<Int64> { pos }
public prop isInfinite: Bool
public func provide(): Iterator<(T0, T1, T2, T3, T4)>
}
public class CombinedDataShrinker2<T0, T1> <: DataShrinker<(T0, T1)> {
public override func shrink(value: (T0, T1)): Iterable<(T0, T1)>
}
public class CombinedDataShrinker3<T0, T1, T2> <: DataShrinker<(T0, T1, T2)> {
public override func shrink(value: (T0, T1, T2)): Iterable<(T0, T1, T2)>
}
public class CombinedDataShrinker4<T0, T1, T2, T3> <: DataShrinker<(T0, T1, T2, T3)> {
public override func shrink(value: (T0, T1, T2, T3)): Iterable<(T0, T1, T2, T3)>
}
public class CombinedDataShrinker5<T0, T1, T2, T3, T4> <: DataShrinker<(T0, T1, T2, T3, T4)> {
public override func shrink(value: (T0, T1, T2, T3, T4)): Iterable<(T0, T1, T2, T3, T4)>
}
public class CombinedDataStrategy2<T0, T1> <: FormattedDataStrategy<(T0, T1)> {
public init(t0Strategy: DataStrategy<T0>, t1Strategy: DataStrategy<T1>)
public override func provider(configuration: Configuration)
public override func shrinker(configuration: Configuration)
public override func finisher(configuration: Configuration)
}
public class CombinedDataStrategy3<T0, T1, T2> <: FormattedDataStrategy<(T0, T1, T2)> {
public init(
t0Strategy: DataStrategy<T0>,
t1Strategy: DataStrategy<T1>,
t2Strategy: DataStrategy<T2>)
public override func provider(configuration: Configuration)
public override func shrinker(configuration: Configuration)
public override func finisher(configuration: Configuration)
}
public class CombinedDataStrategy4<T0, T1, T2, T3> <: FormattedDataStrategy<(T0, T1, T2, T3)> {
public init(
t0Strategy: DataStrategy<T0>,
t1Strategy: DataStrategy<T1>,
t2Strategy: DataStrategy<T2>,
t3Strategy: DataStrategy<T3>)
public override func provider(configuration: Configuration)
public override func shrinker(configuration: Configuration)
public override func finisher(configuration: Configuration)
}
public class CombinedDataStrategy5<T0, T1, T2, T3, T4> <: FormattedDataStrategy<(T0, T1, T2, T3, T4)> {
public init(
t0Strategy: DataStrategy<T0>,
t1Strategy: DataStrategy<T1>,
t2Strategy: DataStrategy<T2>,
t3Strategy: DataStrategy<T3>,
t4Strategy: DataStrategy<T4>)
public override func provider(configuration: Configuration)
public override func shrinker(configuration: Configuration)
public override func finisher(configuration: Configuration)
}
public func checkDataStrategy<T>(strategy: DataStrategy<T>): DataStrategy<T>{
public func provider(configuration: Configuration): DataProvider<T>{ val.provider(configuration) }
public func shrinker(configuration: Configuration): DataShrinker<T> { val.shrinker(configuration) }
public func finisher(configuration: Configuration): DataFinisher<T> { val.finisher(configuration) }
}
public sealed abstract class FormattedDataStrategy<T> <: DataStrategy<T>
public func defaultConfiguration(): Configuration
public func entryMain(cases: TestPackage): Int64
public class SerializableProvider<T> <: DataProvider<T> where T <: Serializable<T> {
public override func provide(): Iterable<T>
public prop isInfinite: Bool
}
/**********************************/
public open class ParameterTestCase<T> <: UTestCase {
public override func run(ctx: TestCaseContext): Unit
}
public func makeParameterTestCase<T>(
caseName!: String,
isSkip!: Bool,
argumentNames!: Array<String>,
doRun!: (T) -> Unit,
strategy!: DataStrategy<T>,
configuration!: Configuration,
timeout!: TimeoutInfo
): ParameterTestCase<T>
public class PowerAssertDiagramBuilder {
public init(expression: String, initialPosition: Int64)
public func r<T>(value: T, exprAsText: String, position: Int64): T
public func r(value: String, exprAsText: String, position: Int64): String
public func h(exception: Exception, exprAsText: String, position: Int64): Nothing
public func w(result: Bool)
}
public func pprint(pp: PrettyPrinter): PrettyPrinter
public abstract class UTestCase <: Hashable & Equatable<UTestCase>{
public init(caseName: String, isSkip!: Bool, isBench!: Bool, timeout!: TimeoutInfo)
public func run(ctx: TestCaseContext): Unit
public func hashCode() : Int64
public operator func ==(rhs: UTestCase): Bool
public operator func !=(rhs: UTestCase): Bool
}
public class SimpleTestCase <: UTestCase {
public init(
isSkip: Bool, caseName: String, doRun: () -> Unit,
times!: Int64 = 1, isBench!: Bool = false, timeout!: TimeoutInfo = NoTimeout)
public init(
caseName: String, doRun: () -> Unit, times!: Int64 = 1,
isBench!: Bool = false, timeout!: TimeoutInfo = NoTimeout)
public override func run(ctx: TestCaseContext): Unit
}
public open class TestCases <: UTest {
public func addCase(t: UTestCase): Unit
public func withName(name: String): This
public init()
public init(name: String)
public init(name: String, isParallel!: Bool)
public func loadCases(): Unit
}
public open class TestInfo <: Serializable<TestInfo> & PrettyPrintable {
public var config: Configuration = Configuration()
public var name: String
public prop errorCount: Int64
public prop caseCount: Int64
public prop passedCount: Int64
public prop failedCount: Int64
public prop timeoutCount: Int64
public prop skippedCount: Int64
public func recordStartTime(): Unit
public func recordTimeDuration(): Unit
public func printResult(): Unit
public open override func pprint(pp: PrettyPrinter): PrettyPrinter
public func getBenchResults(): HashMap<String, (Float64,Float64)>
public open func serialize(): DataModel
public static func deserialize(dm: DataModel): TestInfo
public func addCheckResult(check: CheckResult)
public func serialize(): DataModel
public static func deserialize(dm: DataModel): TestResult
public override func pprint(pp: PrettyPrinter): PrettyPrinter
}
public open class CheckResult <: Serializable<CheckResult> {
public func serialize(): DataModel
public static func deserialize(dm: DataModel): addCheckResult
}
public class TestCaseContext {}
public class TestPackage <: UTest {
public init(packageName: String)
public func add(t: TestCases): TestPackage
public func add(elements: Collection<TestCases>): TestPackage
public func runCases(): UnittestCliOptionsFormatException
}
public class TestModule <: UTest {
public init(moduleName: String)
public func add(t: TestPackage): TestModule
public func add(elements: Collection<TestPackage>): TestModule
public func runCases(): Unit
}
public class TestPackageInfo <: TestInfo & Serializable<TestPackageInfo> {
public override func serialize(): DataModel
static public redef func deserialize(dm: DataModel): TestPackageInfo
public override func pprint(pp: PrettyPrinter): PrettyPrinter
}
public class TestModuleInfo <: TestInfo & Serializable<TestModuleInfo> {
public override func serialize(): DataMode
static public redef func deserialize(dm: DataModel): TestModuleInfo
public override func pprint(pp: PrettyPrinter): PrettyPrinter
}
public abstract class UTest {
public func execute(): Unit
public func printResult(): Unit
public open func jsonReport(): JsonValue
public open func getTestInfo(): TestInfo
public open prop ctx: TestCaseContext
}
public class Configuration <: ToString {
public init()
public func get<T>(key: String): ?T
public func set<T>(key: String, value: T)
public func remove<T>(key: String): ?T
public func clone(): Configuration
public func toString(): string
}
package unittest.common
public interface DataProvider<T> {
func provide(): Iterable<T>
func positions(): Array<Int64> /* users should not implement this API */
prop isInfinite: Bool
}
public enum Color {
| RED
| GREEN
| YELLOW
| BLUE
| CYAN
| MAGENTA
| GRAY
| DEFAULT_COLOR
}
public abstract class PrettyPrinter {
public init(indentationSize!: UInt64 = 4, startingIndent!: UInt64 = 0)
public prop isTopLevel: Bool
public func indent(body: () -> Unit): PrettyPrinter
public func indent(indents: UInt64, body: () -> Unit): PrettyPrinter
public func customOffset(symbols: UInt64, body: () -> Unit): PrettyPrinter
public func colored(color: Color, body: () -> Unit): PrettyPrinter
public func colored(color: Color, text: String): PrettyPrinter
public func append(text: String): PrettyPrinter
public func appendCentered(text: String, space: UInt64): PrettyPrinter
public func append<PP>(value: PP): PrettyPrinter where PP <: PrettyPrintable
public func newLine(): PrettyPrinter
public func appendLine(text: String): PrettyPrinter
public func appendLine<PP>(value: PP): PrettyPrinter where PP <: PrettyPrintable
}
public class PrettyText <: PrettyPrinter & PrettyPrintable & ToString {
public init()
public init(string: String)
public static func of<PP>(pp: PP) where PP <: PrettyPrintable
public func isEmpty(): Bool
public func pprint(to: PrettyPrinter): PrettyPrinter
public func toString(): String
}
public interface PrettyPrintable {
func pprint(to: PrettyPrinter): PrettyPrinter
}
extend Array<T> <: PrettyPrintable where T <: PrettyPrintable
extend ArrayList<T> <: PrettyPrintable where T <: PrettyPrintable
package unittest.prop_test
public func emptyIterable<T>(): Iterable<T>
public interface Generator<T> {
func next(): T
}
public class Generators {
public static func single<T>(value: T): Generator<T>
public static func generate<T>(body: () -> T): Generator<T>
public static func iterable<T>(random: Random, collection: Array<T>): Generator<T>
public static func weighted<T>(random: Random, variants: Array<(UInt64, Generator<T>)>): Generator<T>
public static func pick<T>(random: Random, variants: Array<Generator<T>>): Generator<T>
public static func lookup<T>(random: Random): Generator<T> where T <: Arbitrary<T>
public static func mapped<T, R>(random: Random, body: (T) -> R): Generator<R> where T <: Arbitrary<T>
public static func mapped<T1, T2, R>(random: Random, body: (T1, T2) -> R): Generator<R> where T1 <: Arbitrary<T1>, T2 <: Arbitrary<T2>
public static func mapped<T1, T2, T3, R>(random: Random, body: (T1, T2, T3) -> R): Generator<R>
where T1 <: Arbitrary<T1>, T2 <: Arbitrary<T2>, T3 <: Arbitrary<T3>
public static func mapped<T1, T2, T3, T4, R>(random: Random, body: (T1, T2, T3, T4) -> R): Generator<R>
where T1 <: Arbitrary<T1>, T2 <: Arbitrary<T2>, T3 <: Arbitrary<T3>, T4 <: Arbitrary<T4>
}
public class LazySeq<T> <: Iterable<T> {
public static func of(iterable: Iterable<T>)
public static func of(array: Array<T>)
public init(element: T)
public init()
public func iterator(): Iterator<T>
public func concat(other: LazySeq<T>): LazySeq<T>
public func prepend(element: T): LazySeq<T>
public func append(element: T): LazySeq<T>
public func mixWith(other: LazySeq<T>): LazySeq<T>
public static func mix(l1: LazySeq<T>, l2: LazySeq<T>)
public static func mix(l1: LazySeq<T>, l2: LazySeq<T>, l3: LazySeq<T>)
public static func mix(l1: LazySeq<T>, l2: LazySeq<T>, l3: LazySeq<T>, l4: LazySeq<T>)
public static func mix(l1: LazySeq<T>, l2: LazySeq<T>, l3: LazySeq<T>, l4: LazySeq<T>, l5: LazySeq<T>)
public func map<U>(body: (T) -> U): LazySeq<U>
}
public class ShrinkHelpers {
public static func shrinkTuple<T0, T1>
public static func shrinkTuple<T0, T1, T2>
public static func shrinkTuple<T0, T1, T2, T3>
public static func shrinkTuple<T0, T1, T2, T3, T4>
}
public interface IndexAccess {
func getElementAsAny(index: Int64): ?Any
}
public struct Function0Wrapper<R> {
public Function0Wrapper(public let function: () -> R) {}
public operator func () (): R { function() }
}
public struct TupleWrapper2<T0, T1> {
public TupleWrapper2(public let tuple: (T0, T1))
public func apply<R>(f: (T0, T1) -> R): R
}
public struct TupleWrapper3<T0, T1, T2> {
public TupleWrapper3(public let tuple: (T0, T1, T2))
public func apply<R>(f: (T0, T1, T2) -> R): R
}
public struct TupleWrapper4<T0, T1, T2, T3> {
public TupleWrapper4(public let tuple: (T0, T1, T2, T3))
public func apply<R>(f: (T0, T1, T2, T3) -> R): R
}
public struct TupleWrapper5<T0, T1, T2, T3, T4> {
public TupleWrapper5(public let tuple: (T0, T1, T2, T3, T4))
public func apply<R>(f: (T0, T1, T2, T3, T4) -> R): R
}
/* how to describe the specification for function and tuples?*/
extend Function0Wrapper<R> <: Arbitrary<Function0Wrapper<R>> where R <: Arbitrary<R>
extend TupleWrapper2<T0, T1> <: Arbitrary<TupleWrapper2<T0, T1>>
extend TupleWrapper3<T0, T1, T3> <: Arbitrary<TupleWrapper3<T0, T1, T3>>
extend TupleWrapper4<T0, T1, T3, T4> <: Arbitrary<TupleWrapper4<T0, T1, T3, T4>>
extend TupleWrapper5<T0, T1, T3, T4, T5> <: Arbitrary<TupleWrapper5<T0, T1, T3, T4, T5>>
extend Function0Wrapper<R> <: Shrink<Function0Wrapper<R>> {}
extend TupleWrapper2<T0, T1> <: Shrink<TupleWrapper2<T0, T1>>
extend TupleWrapper3<T0, T1, T3> <: Shrink<TupleWrapper3<T0, T1, T3>>
extend TupleWrapper4<T0, T1, T3, T4> <: Shrink<TupleWrapper4<T0, T1, T3, T4>>
extend TupleWrapper5<T0, T1, T3, T4, T5> <: Shrink<TupleWrapper5<T0, T1, T3, T4, T5>>
extend TupleWrapper2<T0, T1> <: IndexAccess
extend TupleWrapper3<T0, T1, T3> <: IndexAccess
extend TupleWrapper4<T0, T1, T3, T4> <: IndexAccess
extend TupleWrapper5<T0, T1, T3, T4, T5> <: IndexAccess
package unittest.testmacro
class MacroException <: Exception {
public init()
public init(message: String)
}