Rog*_*son 6 tdd parsing unit-testing compiler-theory abstract-syntax-tree
什么被认为是对复杂单元(如编译器)进行单元测试的最佳方法?
多年来我写了一些编译器和解释器,我发现这种代码很难以一种好的方式进行测试.
如果我们采用类似抽象语法树生成的东西.你会如何使用TDD测试?
小结构可能很容易测试.例如:
string code = @"public class Foo {}";
AST ast = compiler.Parse(code);
Run Code Online (Sandbox Code Playgroud)
因为那不会产生很多ast节点.
但是,如果我真的想测试编译器可以为类似方法生成AST:
[TestMethod]
public void Can_parse_integer_instance_method_in_class ()
{
string code = @"public class Foo { public int method(){ return 0;}}";
AST ast = compiler.Parse(code);
Run Code Online (Sandbox Code Playgroud)
你会断言什么?手动定义代表给定代码的AST,并断言生成的AST符合手动定义的AST看起来非常简洁,甚至可能容易出错.
那么像这样的TDD复杂场景的最佳策略是什么?
小智 6
首先,如果您测试编译器,则无法获得足够的测试!用户真的很依赖编译器生成的输出,就好像它是一个永远的黄金标准,所以要真正注意质量。所以如果可以的话,用你能想到的每一个测试来测试!
其次,使用所有可用的测试方法,并在适当的地方使用它们。事实上,您或许能够在数学上证明某个变换是正确的。如果你能够这样做,你应该这样做。
但是我见过的每个编译器的内部结构都涉及启发式和大量优化的、手工编写的内部代码;因此,辅助证明方法通常不再适用。在这里,测试到位,我的意思是很多!
在收集测试时,请考虑不同的情况:
现在,这听起来像是一项艰巨的工作!是的,它确实如此,但有帮助:世界上有几个用于 (C-) 编译器的商业测试套件以及可能会帮助您应用它们的专家。这是我所知道的一小部分:
首先,解析通常是编译器项目的一个微不足道的部分。根据我的经验,它永远不会花费超过 10% 的时间(除非我们正在谈论 C++,但如果您正在设计它,您就不会在这里提出问题),因此您宁愿不要将大量时间投入到解析器测试中。
尽管如此,TDD(或者无论您如何称呼它)在开发中端方面仍然占有一席之地,您经常希望验证您刚刚添加的优化是否确实导致了预期的代码转换。根据我的经验,这样的测试通常是通过为编译器提供特制的测试程序和 grep 输出程序集以获取预期模式来实现的(这个循环是否展开了四次?我们是否设法避免内存写入是这个函数?等等)。Grepping 汇编不如分析结构化表示(S-exprs 或 XML)那么好,但它很便宜并且在大多数情况下工作得很好。不过,随着编译器的增长,支持起来非常困难。