28

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

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

问题是我根本不敢修改代码。那以及单元测试本身是a:复杂和b:脆弱的事实。

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

有没有人有类似的经历,他们愿意分享?

4

8 回答 8

13

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

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

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

于 2008-08-14T14:04:25.800 回答
5

回想一下,“单元测试”只是一种测试。您应该能够对代码生成器的内部部分进行单元测试。您在这里真正看到的是系统级测试(也称为回归测试)。这不仅仅是语义......还有不同的心态、方法、期望等。当然还有更多的工作,但你可能需要硬着头皮建立一个端到端的回归测试套件:固定 C++ 文件 -> SWIG接口-> python 模块-> 已知输出。您真的想检查已知输入(固定 C++ 代码)与预期输出(最终 Python 程序的输出)。直接检查代码生成器结果就像区分目标文件......

于 2008-08-14T18:15:42.733 回答
0

是的,结果是唯一重要的事情。真正的苦差事是编写一个框架,让您生成的代码能够独立运行……花时间在那里。

于 2008-08-14T14:38:19.630 回答
0

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

第三种选择是生成代码,然后从中构建一个应用程序,其中只包含一个单元测试。同样,您需要一个 shell 脚本或诸如此类的东西来为每个输入运行它。至于如何对预期行为进行编码,我突然想到,它可以以与仅使用生成的接口而不是 C++ 接口的 C++ 代码相同的方式完成。

于 2008-08-14T15:46:01.927 回答
0

只是想指出,您仍然可以在验证结果的同时实现细粒度测试:您可以通过将单个代码块嵌套在一些设置和验证代码中来测试它们:

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

如果您将生成的代码由较小的块组装而成,并且这些块不会经常更改,那么您可以练习更多条件并进行更好的测试,并希望在您更改一个块的细节时避免所有测试都中断。

于 2008-09-16T09:41:03.870 回答
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);
    }
}

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

于 2010-05-19T23:17:11.220 回答
0

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

基本上,您真正想知道的是您的生成器是否会产生您期望的结果,而无需对所有可能的组合进行物理测试(也:不可能)。通过确保您的生成器以您期望的方式保持一致,您可以更好地感觉生成器将在越来越复杂的情况下取得成功。

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

于 2010-07-25T23:56:06.117 回答
0

我发现你需要测试你正在生成的东西,而不是你如何生成它。

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

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

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

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

它很甜。

祝你好运!

于 2015-07-20T04:10:25.057 回答