Unit testing 编译器的单元测试

Unit testing 编译器的单元测试,unit-testing,parsing,tdd,compiler-theory,abstract-syntax-tree,Unit Testing,Parsing,Tdd,Compiler Theory,Abstract Syntax Tree,对复杂单元(如编译器)进行单元测试的最佳方法是什么 这些年来,我已经编写了一些编译器和解释器,我确实发现这种代码很难以一种好的方式进行测试 如果我们采取类似于抽象语法树生成的方法。您将如何使用TDD对此进行测试 小构造可能很容易测试。 e、 g.大致如下: string code = @"public class Foo {}"; AST ast = compiler.Parse(code); 因为这不会生成很多ast节点 但如果我真的想测试编译器是否可以为类似方法的东西生成AST: [Tes

对复杂单元(如编译器)进行单元测试的最佳方法是什么

这些年来,我已经编写了一些编译器和解释器,我确实发现这种代码很难以一种好的方式进行测试

如果我们采取类似于抽象语法树生成的方法。您将如何使用TDD对此进行测试

小构造可能很容易测试。 e、 g.大致如下:

string code = @"public class Foo {}";
AST ast = compiler.Parse(code);
因为这不会生成很多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);
你会坚持什么? 手动定义一个表示给定代码的AST,并断言生成的AST符合手动定义的AST,这看起来非常复杂,甚至可能容易出错


那么,对于这样的复杂场景,TDD的最佳策略是什么呢?

首先,解析通常是编译器项目的一个微不足道的部分。从我的经验来看,它不需要超过10%的时间(除非我们在谈论C++,但是如果你在设计C++,你就不会问问题),所以你不愿意把你的时间投入到语法分析器测试中。
尽管如此,TDD(或者你怎么称呼它)在开发中间端时仍占有一席之地,在这个中间端,你经常想要验证,例如,你刚刚添加的优化确实会导致预期的代码转换。根据我的经验,像这样的测试通常是通过给编译器精心编制的测试程序和对预期模式的输出程序集进行grepping来实现的(这个循环是否展开了四次?我们是否设法避免了内存写入是这个函数?等等)。Grepping assembly不如分析结构化表示(S-exprs或XML),但它价格便宜,在大多数情况下工作良好。但是,随着编译器的增长,支持它是非常困难的。

首先,如果您测试编译器,您将无法获得足够的测试!用户真正依赖于编译器生成的输出,就好像它始终是一个黄金标准一样,所以要真正注意质量。所以,如果你可以,用你能想出的每一个测试来测试

其次,使用所有可用的测试方法,但在适当的地方使用它们。事实上,你可以从数学上证明,某个变换是正确的。如果你能做到这一点,你应该这样做

但我见过的每一个编译器的内部都包含了启发法和大量优化的手工代码;因此,辅助证明方法通常不再适用。在这里,测试已经到位,我的意思是很多

收集测试时,请考虑不同的情况:

  • 积极的标准一致性:前端应该接受某些代码模式,并且编译器必须生成正确运行的程序。这类测试要么需要黄金参考编译器,要么需要生成测试程序正确输出的生成器;或者它涉及手工编写的程序,其中包括对照人类推理提供的值进行检查
  • 否定测试:每个编译器都必须拒绝错误代码,比如语法错误、类型不匹配等等。它必须产生特定类型的错误和警告消息。我不知道有什么方法可以自动生成这样的测试。所以这些也需要由人类书写
  • 转换测试:每当您在编译器(中间端)中提出一个奇特的优化时,您可能会想到一些演示优化的示例代码。请注意在这样一个模块之前和之后的转换,它们可能需要为您的编译器或只插入该模块的裸骨编译器提供特殊选项。也要测试一组合理的大模块组合。我通常在特定转换前后对中间表示进行回归测试,通过与同事的深入推理定义引用。尝试在转换的两个方面编写代码,即您希望转换的代码段和不能转换的稍有不同的代码段
  • 现在,这听起来像是一个可怕的工作很多!是的,确实如此,但也有帮助:世界上有几个(C-)编译器的商业测试套件和专家可以帮助您应用它们。下面是我所知道的一些人的小名单:

    • C编译器验证套件
    • GCC和LLVM环境中的测试
    • 你说得对!或谷歌“编译器测试套件”

    这只是单元测试无用和低劣的众多例子之一,重点应该放在集成测试上。TDD用于积垢,不用于严重的东西。对于编译器来说,随机生成的代码测试是迄今为止最好的方法。例如:您可能还对安全编译器构造的高级方法感兴趣:-正式规范肯定比任何可能的测试都能更好地保证质量。@peer,您在说什么“方法”?如果生成了一个解析器(想想bison之类的东西),您将拥有一个单一的语法和一堆无法读取的生成代码。除了整个语法之外,没什么可考的。如果它是一个手写的递归下降解析器,那么进行单元测试就更加困难了(比如说,请参阅铿锵的源代码,并尝试思考如何为每个微小的解析器条目模拟ASTContext和输入流)。单元测试对于任何相当复杂的代码来说都是毫无意义的。@SK logic我完全同意你的观点,我总是发现使用TDD开发这种代码几乎是不可能的,所以这篇文章是为了看看TDD专家是否知道一些我在这方面不知道的东西。或者就像你说的,TDD是CRUD的。我没有编写编译器的经验,所以这是一个第一眼的答案,如果我是在胡说八道的话,我很抱歉:例如,我认为对服务消费者进行单元测试没有什么真正的区别,最好的办法是对服务结果进行存根。你可以使用真正的服务(或