2008-08-14 53 views
22

这是一个困难的,开放式的问题,我知道,但我想我扔在地上,看看如果任何人有任何有趣的建议。我应该如何测试代码生成器?

我已经开发出一种码生成器,其将我们Python接口我们的C++代码(通过SWIG生成),并产生以暴露此作为Web服务所需的代码。当我开发这个代码时,我使用TDD做了它,但是我发现我的测试很脆弱。因为每个测试基本上是想验证的输入给定码位(这恰好是一个C++头)我得到的输出给定码位我写了一个小引擎,从XML输入文件读取测试定义和生成测试来自这些期望的案例。

问题是我害怕去修改代码。这一点以及单元测试自己的事实是:复杂的,而b:脆弱的。

所以我试图想其他办法解决这个问题,这让我觉得我也许解决了错误的方式。也许我需要更多地关注结果,IE:我生成的代码实际上是否运行并执行我想要的代码,而不是代码是否按照我希望的方式运行。

有没有人有过类似的东西,他们会关心分享的任何经验?

+0

我实际上面临着同样的问题,下面的答案都不能令人满意。当然,您可以单元测试代码生成器的各个部分。问题是你怎么知道生成的代码是正确的,即没有回归或类似的东西,因此如何为生成的代码编写自动化测试(无论它们是否称为单元测试或集成测试)? – 2009-11-30 15:58:39

+1

@詹姆斯:那里没有简单的答案......我刚刚重读了这个问题,当时我的回应和所有问题都回来了。我可能会在接下来的几周内再次发布这款游戏,因为我会时不时地遇到各种各样的回归消息,并且发现这些消息越来越关键。 – jkp 2009-11-30 21:46:58

回答

12

我开始写了我有我自己的代码发生器经验的总结,然后回去和重新阅读你的问题,发现你已经在同样的问题触动自己,专注于执行结果,而不是代码布局/看。

问题是,这是很难测试,生成的代码可能不适合实际的单元测试系统的环境中运行,你怎么编码预期的结果?

我发现你需要将代码生成器分解成更小的部分并进行单元测试。如果你问我,单元测试完整的代码生成器更像集成测试,而不是单元测试。

0

是,结果是唯一重要的事情。真正的琐事是编写一个框架,允许您生成的代码独立运行......在那里度过您的时间。

0

如果您正在* nux上运行,您可能会考虑转储unittest框架以支持bash脚本或makefile。在Windows上,你可能会考虑构建一个运行生成器的shell应用程序/函数,然后使用代码(作为另一个进程)并进行单元测试。

第三种选择是生成代码,然后从中构建一个应用程序,该应用程序只包含unittest。同样你需要一个shell脚本或者不需要为每个输入运行这个脚本。至于如何对预期行为进行编码,我认为它可以以与您使用生成的接口而非C++代码的C++代码几乎相同的方式完成。

4

回想一下,“单元测试”只是一种测试。你应该能够单元测试你的代码生成器的内部部分。你真正在看的是系统级测试(又名回归测试)。它不只是语义...有不同的思维方式,方法,期望等这当然更多的工作,但你可能需要咬紧牙关,建立一个终端到终端的回归测试套件:固定的C++文件 - >痛饮接口 - > python模块 - >已知的输出。你真的想检查已知的输入(固定的C++代码)与期望的输出(来自最终的Python程序)。直接检查代码生成器结果就像差异目标文件一样...

0

只是想指出的是,你仍然可以实现细粒度的测试,同时验证结果:通过嵌套他们内部的一些设置和验证码,你可以测试的代码单独大块:

int x = 0; 
GENERATED_CODE 
assert(x == 100); 

只要你有您生成的代码由较小的块组装而成,并且块不会频繁更改,您可以运行更多条件并进行更好的测试,并且希望避免在更改某个块的特定情况时使所有测试中断。

0

单元测试就是测试一个特定的单元。所以,如果你正在编写一个A类规范,这是理想的,如果A类没有B和C类的真实具体版本。

好的我注意到后来这个问题的标签包括C++/Python,但原理是相同的:

public class A : InterfaceA 
    { 
     InterfaceB b; 

     InterfaceC c; 

     public A(InterfaceB b, InterfaceC c) { 
      this._b = b; 
      this._c = c; } 

     public string SomeOperation(string input) 
     { 
      return this._b.SomeOtherOperation(input) 
       + this._c.EvenAnotherOperation(input); 
     } 
    } 

由于上述系统A至喷射系统B和C的接口,就可以进行单元测试只是系统A,而无需真正的功能正在由任何其他系统来执行。这是单元测试。

这里是从创建接近系统完成,用不同的当规范为每个片行为的一个巧妙的方式:

public class When_system_A_has_some_operation_called_with_valid_input : SystemASpecification 
{ 
    private string _actualString; 

    private string _expectedString; 

    private string _input; 

    private string _returnB; 

    private string _returnC; 

    [It] 
    public void Should_return_the_expected_string() 
    { 
     _actualString.Should().Be.EqualTo(this._expectedString); 
    } 

    public override void GivenThat() 
    { 
     var randomGenerator = new RandomGenerator(); 
     this._input = randomGenerator.Generate<string>(); 
     this._returnB = randomGenerator.Generate<string>(); 
     this._returnC = randomGenerator.Generate<string>(); 

     Dep<InterfaceB>().Stub(b => b.SomeOtherOperation(_input)) 
         .Return(this._returnB); 
     Dep<InterfaceC>().Stub(c => c.EvenAnotherOperation(_input)) 
         .Return(this._returnC); 

     this._expectedString = this._returnB + this._returnC; 
    } 

    public override void WhenIRun() 
    { 
     this._actualString = Sut.SomeOperation(this._input); 
    } 
} 

所以在最后,单个单元/规范可以有多个行为,规范随着您开发单位/系统而增长;如果您的系统受其他系统的影响,请注意。

0

我的建议是找出一组已知的输入输出结果,例如您已经拥有的一些更简单的情况,并且单元测试产生的代码。完全可能的是,当你改变发生器时,产生的确切字符串可能会略有不同......但你真正关心的是它是否以相同的方式解释。因此,如果您测试结果的方式是测试该代码(如果它是您的功能),那么您会发现它是否以您想要的方式成功。

基本上,你真正想知道的是,如果没有对每种可能的组合(也是不可能的)进行物理测试,你的发电机是否会产生你所期望的。通过确保您的发电机符合您的期望,您可以更好地感受到发电机将会在更复杂的情况下取得成功。

通过这种方式,您还可以建立一套回归测试(单元测试需要保持正常工作)。这将有助于确保对生成器的更改不会破坏其他形式的代码。当你遇到你的单元测试没有捕获到的错误时,你可能希望包含它以防止类似的破坏。

0

我发现你需要测试你生成的东西比生成它更多。

在我的情况下,程序生成编译成Web应用程序的许多类型的代码(C#,HTML,SCSS,JS等)。我发现减少总体回归错误的最好方法是测试Web应用程序本身,而不是测试生成器。

不要误解我的意思,还有单元测试检查一些生成器代码,但是我们最大的挑战就是对生成的应用程序本身进行了UI测试。

由于我们产生它我们还产生一个很好的抽象的JS,我们可以用它来编程测试应用程序。我们遵循这里概述的一些想法:http://code.tutsplus.com/articles/maintainable-automated-ui-tests--net-35089

最重要的是,它真正从端到端测试系统,从代码生成到实际生成的内容。一旦测试失败,很容易将其追溯到发生器损坏的地方。

这是非常甜蜜。

祝你好运!