您现在的位置是:首页 >技术交流 >2305d很不错的模拟技术网站首页技术交流
2305d很不错的模拟技术
D很不错.
这里有个.
今天,我正在编写一些迭代
数据结构并把输出
写进一堆不同文件
的代码.它像这样:
void genSplitHtml(Data data, ...) {
auto outputTemplate = File("template.html", "r");
foreach (...) {
auto filename = generateFilename(...);
auto sink = File(filename, "w").lockingTextWriter;
...
foreach (line; outputTemplate.byLine) {
if (line.canFind("MAGIC_TOKEN")) {
generateOutput(...);
} else {
sink.put(line);
}
}
}
}
此函数的全部目的
是把输出写入不同的(有自动确定名)文件,因此想编写一个单元测试
来测试它是否创建
了有正确
内容的正确文件.
但是我也不想单元测试
触及实际文件系统
,不想在开发
过程中清理
混乱,因为代码
有时会破坏并在临时
目录中留下垃圾,或与其他并行
运行的单元测试
不好交互,等等.
而且不想重构
来使genSplitHtml
更可单元测试.这更可能搞砸
并蔓延错误
,这违背了本练习的目的.
所以我想出了如下方法:
1)
重写函数为:
void genSplitHtml(File = std.stdio.File)(Data data, ...) { ... }
默认
参数默认绑定到实际
文件系统,因此用此函数
的其他代码不必更改即可处理新的API
.然后,对单元测试代码
:
2)
在单元测试
块中创建假的虚文件系统
:
static struct MockFile {
static string[string] files;
string fname;
this(string _fname, string mode) {
//忽略此测试的"模式"
fname = _fname;
}
//模拟替换`std.stdio.File.lockingTextWriter`
auto lockingTextWriter() {
return (const(char)[] data) {
//模拟写入文件
files[fname] ~= data.dup;
};
}
void rewind() {} //挂名挂名
void close() {} //
auto byLine() {
//主要写入文件,只读取指定的文件.所以在此只是`伪造`它的内容.
if (fname != "template.html") return [];
else return [
"<html>",
"MAGIC_TOKEN",
"</html>"
];
}
}
然后,单元测试
不再调用genSplitHtml(...)
,而是调用genSplitHtml!MockFile(...)
,这用MockFile
替换了std.stdio.File
,
由于D的模板
和区间API
,其余代码
会自动适应MockFile
假文件系统.函数
完成后,单元测试
只需检查MockFile.files
的内容,以验证
是否存在正确
的文件,及正确
的内容.
花了大约10
分钟来编写MockFile
,及用假文件系统
来检查正确行为的单元测试.
未来可扩展MockFile
来模拟
,如完整的文件系统
,偶尔(或总是)失败或破坏
数据的文件系统
等使用真实
文件系统测试
是不切实际的测试用例
.
最重要的是,我"免费
"获得所有这些:除了目标
函数的单个新模板参数
外,无需
更改现有代码.
D不仅是r0x0rs
,D
是巨石!!
非常好!你展示的技术叫"模拟
".在正确
参数化函数输入和输出
时,它特别有效.
我修复D
的lexer.d
为不再访问全局变量
时,我可通过模拟
输入和输出来简化它的单元测试
.如,我不必使用全局"闭嘴
"开关来抑制
错误消息,而是使用闲着的模拟
错误处理器.
是的,至少从Java
时代开始,它就是个古老
的技术.
但是,在该特例下,使它脱颖而出
的是D可让你仅通过1行
更改来模拟全局(模块级)
符号引用.如,要模拟Java
中的顶级"系统
",你必须在外部包
帮助下做一些神秘的侵改;
这很难.在D中,给函数
添加单个模板参数
,就可以了.*这就是*
力量.
很高兴听到.错误阻塞
是过去一些最令人恼火的D编译器
错误的根源;越少使用它,就越好.