单元线程``2.2.4
几乎没有样板,使用内置单元测试块的高级多线程``单元测试框架.
要使用该包,请在项根目录中执行以下命令:
cpp
dub add unit-threaded
手动使用
请在你的项目依赖中填充以下依赖:
dub.json
cpp
"unit-threaded": "~>2.2.4"
dub.sdl
cpp
dependency "unit-threaded" version="~>2.2.4"
unit-threaded(线单)包提供可单独使用的子包:
1,线单:from,依赖保存声明工具
2,线单:exception,异常
3,线单:assertions,用来测试自定义断定
4,单元线程:运行器,测试运行器
5,单元线程:模拟,模拟功能
6,单元线程:整合,整合测试工具
7,线单:property,基于属性的测试工具
8,线单:autorunner,自动运行dub root项目所有模块的所有测试.
9,线单:bebehavior,BDD的输出格式化
单元线程
我用DConf2016的说明单元线程讲解.
适合D语言的多线程高级单元测试框架,这里.
增强D的单元测试块包括:
1,可命名测试并单独执行.
2,自定义断定以更好地报告错误(如1.should==2)
3,默认在线程中运行
4,用来测试自定义的UDA
5,基于属性的测试
6,模拟
快速入门配音
注意:虽然这样入门很简单,但也会增加构建时间,可能会遇见一些特例.下面说明了如何手动操作.
Dub用dub test来运行测试.可惜,因为D的编译时``反射特性,使用该库必须有一个列举所有需要反省的模块的测试运行器文件.
因为这是项麻烦且易于自动化的任务,线单有叫gen_ut_main的dub配置来实现它.要在dub项目中使用单元线程,可用如下单元测试配置,如本dub.json所示:
cpp
{
"name": "myproject",
"targetType": "executable",
"targetPath": "bin",
"configurations": [
{ "name": "executable" },
{
"name": "unittest",
"targetType": "executable",
"preBuildCommands": ["$DUB run --compiler=$$DC unit-threaded -c gen_ut_main -- -f bin/ut.d -d $DUB"],
"mainSourceFile": "bin/ut.d",
"excludedSourceFiles": ["src/main.d"],
"dependencies": {
"unit-threaded": "*"
}
}
]
}
使用dub.sdl:
cpp
configuration "executable" {
}
configuration "unittest" {
dependency "unit-threaded" version="*"
mainSourceFile "bin/ut.d"
excludedSourceFiles "src/main.d"
targetType "executable"
preBuildCommands "$DUB run --compiler=$$DC unit-threaded -c gen_ut_main -- -f bin/ut.d -d $DUB"
}
excludedSourceFiles是为了避免编译包含主要函数的文件,以避免链接错误.或可用版本去掉"真正的"主文件:
cpp
version(unittest) {
import unit_threaded;
mixin runTestsMain!(
"module1",
"module2",
//...
);
} else {
void main() {
//...
}
}
用测试手动列举D模块
另,推荐手动在单元测试的主函数中列举所有带测试的模块.这里有一个插件:
cpp
import unit_threaded;
mixin runTestsMain!(
"mypkg.mymod0",
"mypkg.mymod1",
//...
);
现在会在线程中运行你的单元测试块,且可单独运行.要为每个单位测试命名,只需附加UDA串:
cpp
@("Test that 2 + 3 is 5")
unittest {
assert(2 + 3 == 5);
}
还可对单元测试,设置多个配置,如一个使用标准D运行时``单元测试运行器,另一个使用单元线程的:
cpp
"configurations": [
{"name": "ut_default"},
{
"name": "unittest",
"preBuildCommands: ["$DUB run --compiler=$$DC unit-threaded -c gen_ut_main -- -f bin/ut.d -d $DUB"],
"mainSourceFile": "bin/ut.d",
...
}
]
在此,如果不使用该库,dub test -c ut_default正常运行,dub 测试则带单元线程测试运行器运行.
要使用单元线程断定或基于UDA的功能,必须导入该库:
cpp
//这里不要用`"version(unittest)"`,
//如果有人依赖你的代码而不依赖`单元线程`,
//他们就无法测试自己的代码!
version(TestingMyCode) { import unit_threaded; }
else { enum ShouldFail; }
//所以生产构建编译了
int adder(int i, int j) { return i + j; }
@("Test adder") unittest {
adder(2, 3).shouldEqual(5);
}
@("Test adder fails", ShouldFail) unittest {
adder(2, 3).shouldEqual(7);
}
如果如上对单元线程,使用自定义``dub配置,可在Have_unit_threaded中使用版本块(dub给构建添加了).
自定义断定
如下:
cpp
1.should == 1;
1.should.not == 2;
1.should in [1, 2, 3];
4.should.not in [1, 2, 3];
void funcThrows() { throw new Exception("oops"); }
funcThrows.shouldThrow;
//或用`.be`
1.should.be == 1;
1.should.not.be == 2;
1.should.be in [1, 2, 3];
4.should.not.be in [1, 2, 3];
//我知道这是滥用`重载符号`.我依然喜欢它.
[1, 2, 3].should ~ [3, 2, 1];
[1, 2, 3].should.not ~ [1, 2, 2];
1.0.should ~ 1.0001;
1.0.should.not ~ 2.0;
更多内容见unit_threaded.should模块.
快速编译模式
快速编译模式.按unitThreadedLight设置版本,编译速度会快很多,但不会报告错误,去掉了某些功能.是实验性支持.
高级用法:属性
用@ShouldFail来装饰期望会失败的测试,并可传递串来解释原因.@ShouldFail应该比@HiddenTest更受欢迎.如果已修复相关漏洞或完成尚未实现的功能,测试就会失败,这样更难忽视和忘记测试.
因为被测代码可能不是线安的,可在测试中使用@Serial属性.这会顺序执行同一模块中所有带该属性的测试,避免相互交错.
虽然不是最佳做法,但有时测试结果不稳定.建议按权宜之计修复测试,可用@Flaky,UDA来重复测试,默认最多为10次.可传递一个数字(如@Flaky(12))来自定义;
可用@Name,UDA来代替普通串来命名单元测试块.
线单使用D的包和模块系统,这样可选择要运行的测试子集.然而,有时不同模块中的测试,会涉及交叉问题,因此建议标明该分组这样仅选择这些测试.
可用@Tags,UDA来实现它.针对测试可用多个标签:
cpp
@Tags("foo", "tagged")
unittest { ... }
通过选择带标签或不带标签的测试来限制执行哪些测试,测试运行器可用串来标记测试:
cpp
./ut @foo ~@bar
它会运行所有带"福"标签,但无"栏"标签的测试.
可在模块中,给自由函数附加@安装和@ShutdownUDA.如果这样,它们会在复合测试中,每个单元测试块前后运行.
复合一般是个模块,但可用虚结构,来只对部分测试应用@安装和@关机功能:
cpp
struct Namespace {
@Setup void setup() { writelnUt("Setup should run exactly twice"); }
@("With setup1")
unittest {
}
@("With setup2")
unittest {
}
}
@("No Setup")
unittest {
}
注意:@安装和@关机仅适合当前域.如果你在结构里写更多单元测试,就没用.
基于属性的测试
当前已有初步支持基于属性的测试.要检查属性,可用unit_threaded.property中返回极的函数的检查函数:
cpp
check!((int a) => a % 2 == 0);
上述示例显然会失败.默认,检查会用100个随机值运行属性函数,给它传递一个不同的运行时参数来更改:
cpp
check!((int a) => a % 2 == 0)(10_000);
//还是会失败
如果如上使用编译时代理,必须显式说明输入参数的类型.只要每个参数是当前支持的类型之一,即可使用多个参数.
模拟
类和接口可如下模拟:
cpp
interface Foo { int foo(int, string); }
int fun(Foo f, int i, string s) { return f.foo(i * 2, s ~ "stuff"); }
auto m = mock!Foo;
m.expect!"foo";
fun(m, 3, "bar");
m.verify;
//如果未调用,则抛
要检查传入的值,给它们传递期望:
cpp
m.expect!"foo"(6, "barstuff");
fun(m , 3, "bar");
m.verify;
或先调用期望然后验证,或在结尾调用``expectCalled:
cpp
fun(m, 3, "bar");
m.expectCalled!"foo"(6, "barstuff");
除非调用returnValue(其是可变变量),返回值为T.init:
cpp
m.returnValue!"foo"(2, 3, 4);
assert(fun(m, 3, "bar") == 2);
assert(fun(m, 3, "bar") == 3);
assert(fun(m, 3, "bar") == 4);
assert(fun(m, 3, "bar") == 0);
也可模拟结构:
cpp
int fun(T)(T f, int i, string s) { return f.foo(i * 2, s ~ "stuff"); }
auto m = mockStruct(2, 3, 4);
//整数为`返回值`(无需传递他们)
assert(fun(m, 3, "bar") == 2);
m.expectCalled!"foo"(6, "barstuff");
如果需要结构,对不同函数,会返回不同类型:
cpp
auto m = mockStruct!(ReturnValues!("length", 5, 3), ReturnValues!("greet", "hello", "g'day"));
m.length.shouldEqual(5);
m.length.shouldEqual(3);
m.greet.shouldEqual("hello");
m.grett.shouldEqual("g'day");
总是抛的结构:
cpp
{
auto m = throwStruct;
m.foo.shouldThrow!UnitTestException;
}
{
auto m = throwStruct!MyException;
m.foo.shouldThrow!MyException;
}
命令行参数
要按单线程模式运行,使用-s.
测试,支持使用-d开关调试打印.测试可用writelnUt函数打印调试输出.
测试可随机顺序运行,而不是线程中.为此,可用-r选项.会打印一个种子,这样可用种子选项重复运行.即在单线程中运行.
整合测试与沙箱环境
如果想写可从文件系统读写的测试,可用unit_threaded.integration的Sandbox结构,比如这样:
cpp
with(immutable Sandbox()) {
writeFile("foo.txt", "foobarbaz\ntoto");
//对行也可传递`串[]`
shouldExist("foo.txt");
shouldNotExist("bar.txt");
shouldEqualLines("foo.txt", ["foobarbaz", "toto"]);
}
默认沙箱主路径是tmp/unitthread,但可调用Sandbox.setPath来更改.
测试注册与测试运行器
示例目录里有两个示例,一个带成功单元测试,另一个带失败的,用来展示各种情况下的输出.因为D包的工作原理,它们必须从仓库的顶级目录中运行.
在两个示例的输出中可见(example.tests.pass_tests.unittest及同目录中的example_fail),自动包含内置的D单元测试块.
他们会自动生成一个名字.用户可用串UDA或包含的UDA``@名,@Name装饰来指定名.
运行测试最好是像失败的示例代码一样:在runner.d中,用包含测试的模块按编译时参数(串)插件进入runTestsMain().
无需注册测试.注册是隐式的,并且每个单元测试块都注册了.
一般如上dub配置所示,在调用runTests或runTestsMain时,需要指定要反省的模块.
可用@HiddenTest属性隐藏测试.即默认不会执行该测试,但可用命令行参数传递其名来执行.HiddenTest会取编译时串来列举隐藏测试的原因.
这一般是个漏洞标识,但也可以是用户想要的任何东西.
因为D包只是目录,编译器在编译时无法读取文件系统,因此无法自动在包中添加所有测试.
为了缓解它,避免手动写入包含测试的所有模块名,有一个叫gen_ut_main的dub配置,按命令行工具运行单元线程来为你写文件.
相关项目
1,dunit:D的x单元测试框架,这里
2,DMocksrevived:一个允许模拟接口或类的模拟对象框架,这里
3,deject:自动依赖注入,这里
4,specd:受specs2和ScalaTest启发的单元测试框架,这里和这里
5,DUnit:测试断定工具包和支持模拟的模板插件.这里