我应该如何对代码生成器进行单元测试?

时间:2008-08-14 13:59:22

标签: c++ python unit-testing code-generation swig

这是一个我知道的难题和开放式问题,但我想我会把它扔到地板上,看看是否有人有任何有趣的建议。

我开发了一个代码生成器,它将我们的python接口带到我们的C ++代码(通过SWIG生成),并生成将其公开为WebServices所需的代码。当我开发这个代码时,我使用TDD做到了,但我发现我的测试很脆弱。因为每个测试本质上都想验证对于给定位的输入代码(恰好是C ++标头)我会得到一个给定的输出代码,我写了一个小引擎,从XML输入文件中读取测试定义并生成测试来自这些期望的案例。

问题是我害怕进入修改代码。那个单元测试自己的事实是:复杂的,b:脆弱的。

所以我试图想出这个问题的替代方法,这让我感到震惊,我可能会以错误的方式解决它。也许我需要更多地关注结果,IE:我生成的代码实际运行并执行我想要的代码,而不是代码看起来像我想要的那样。

有没有人有过与他们想要分享的类似的经历?

8 个答案:

答案 0 :(得分:12)

我开始用我自己的代码生成器编写我的经验总结,然后回过头来重新阅读你的问题,发现你自己已经触及了同样的问题,专注于执行结果而不是代码布局/看。

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

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

答案 1 :(得分:5)

回想一下“单元测试”只是一种测试。您应该能够对代码生成器的内部部分进行单元测试。你在这里真正看到的是系统级测试(a.k.a.回归测试)。这不仅仅是语义......有不同的思维方式,方法,期望等等。这肯定是更多的工作,但你可能需要咬紧牙关并建立一个端到端的回归测试套件:修复C ++文件 - > SWIG接口 - > python模块 - >已知输出。你真的想根据预期的输出检查已知的输入(固定的C ++代码)(最终的Python程序出来的)。直接检查代码生成器结果就像区分对象文件......

答案 2 :(得分:0)

我发现你需要测试生成的内容而不是生成内容。

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

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

由于我们正在生成它,我们还在JS中生成一个很好的抽象,我们可以使用它来编程测试应用程序。我们遵循了此处列出的一些想法:http://code.tutsplus.com/articles/maintainable-automated-ui-tests--net-35089

最重要的是,它真正测试了您的系统端到端,从代码生成到您实际生成的内容。一旦测试失败,就很容易将其追溯到发电机损坏的位置。

非常好。

祝你好运!

答案 3 :(得分: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进行单元测试,而不需要任何其他系统执行实际功能。这是单元测试。

这是一种从创建到完成接近系统的聪明方式,每种行为都有不同的When规范:

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);
    }
}

总而言之,单个单元/规范可以具有多种行为,并且随着您开发单元/系统,规范也会增长;如果您的被测系统依赖于其中的其他具体系统,请注意。

答案 4 :(得分:0)

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

答案 5 :(得分:0)

只是想指出在验证结果时仍然可以实现细粒度测试:您可以通过将代码嵌入到某些设置和验证代码中来测试各个代码块:

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

如果您生成的代码是从较小的块组装而且块不经常更改,您可以运行更多条件并进行更好的测试,并希望避免在更改一个块的细节时让所有测试中断。 / p>

答案 6 :(得分:0)

答案 7 :(得分:0)

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

第三个选项是生成代码,然后从中构建一个只包含单元测试的应用程序。你需要一个shell脚本或者其他什么来为每个输入运行它。至于如何编码预期的行为,我发现它可以用与使用生成的接口而不是C ++的C ++代码完全相同的方式完成。