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

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

问题是我根本不敢修改代码。事实上,单元测试本身是:复数,并且 b:脆。

所以我试图想出解决这个问题的替代方法,但我发现我可能以错误的方式解决了这个问题。也许我需要更多地关注结果,即:我生成的代码是否实际运行并执行我想要的操作,而不是代码看起来是否如我想要的那样。

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

有帮助吗?

解决方案

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

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

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

其他提示

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

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

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

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

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

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

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

单元测试只是测试特定的单元。因此,如果您正在为 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);
    }
}

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

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

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

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

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

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

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

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

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

非常甜蜜。

祝你好运!

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top