整理数据太麻烦时如何测试?


19

我正在编写一个解析器,作为其中的一部分,我有一个Expander将单个复杂语句“扩展”为多个简单语句的类。例如,它将扩展此内容:

x = 2 + 3 * a

变成:

tmp1 = 3 * a
x = 2 + tmp1

现在,我正在考虑如何测试此类,特别是如何安排测试。我可以手动创建输入语法树:

var input = new AssignStatement(
    new Variable("x"),
    new BinaryExpression(
        new Constant(2),
        BinaryOperator.Plus,
        new BinaryExpression(new Constant(3), BinaryOperator.Multiply, new Variable("a"))));

或者我可以将其编写为字符串并进行解析:

var input = new Parser().ParseStatement("x = 2 + 3 * a");

第二个选项更简单,更短且更易读。但它也引入了依赖性Parser,这意味着中的错误Parser可能会使测试失败。因此,测试将不再是一个单元测试Expander,我想在技术上成为一个集成测试ParserExpander

我的问题是:可以主要(或完全)依靠这种集成测试来测试此类Expander吗?


3
Parser如果您习惯性地仅在零次失败时提交错误,则该错误可能会使其他测试失败,这不是问题,相反,这意味着您可以覆盖更多的内容Parser。我宁愿担心的是,如果存在一个错误,当它应该失败时,它Parser会使该测试成功。毕竟,单元测试可用于发现错误-测试在没有但应该有的时候就被破坏了。
JonasKölker2014年

Answers:


27

如果您可以简单地做的话,您将发现自己编写了更多的测试,这些测试具有更加复杂,有趣和有用的行为。所以涉及的选择

var input = new Parser().ParseStatement("x = 2 + 3 * a");

是相当有效的。它确实取决于另一个组件。但是,一切都取决于许多其他组件。如果您要模拟某个事物的生命力,则可能取决于许多模拟功能和测试装置。

开发者有时过度集中在他们的单元测试的纯度,或显影单元测试和单元测试,无任何模块,集成,应力或其他类型的测试。所有这些形式都是有效和有用的,它们都是开发人员的适当责任-而不仅仅是Q / A或运营人员。

我使用的一种方法是从这些较高级别的运行开始,然后使用从它们生成的数据来构建测试的长格式,最低共母表达式。例如,当您转储input上面产生的数据结构时,则可以轻松构造以下内容:

var input = new AssignStatement(
    new Variable("x"),
    new BinaryExpression(
        new Constant(2),
        BinaryOperator.Plus,
        new BinaryExpression(new Constant(3), BinaryOperator.Multiply, new Variable("a"))));

一种在最低级别进行测试的测试。这样,您就可以得到很好的组合:少数几个最基本的原始测试(纯单元测试),但是没有花一个星期的时间在那个原始级别编写测试。这为您提供了时间资源,以使用Parser辅助工具编写更多,更少的原子测试。最终结果:更多的测试,更多的覆盖范围,更多的麻烦和其他有趣的案例,更好的代码和更高的质量保证。


2
这是明智的,尤其是在一切都取决于许多其他因素的情况下。一个好的单元测试应该测试尽可能少的东西。在最小可能数量之内的任何事物都应通过先前的单元测试进行测试。如果您已经完全测试了Parser,则可以假定可以安全地使用Parser来测试ParseStatement
Jon Story

6
我认为主要的纯度问题是避免在单元测试中编写循环依赖项。如果解析器或解析器测试都使用扩展器,并且该扩展器测试依赖解析器的工作,那么您将难以管理的风险是,您所测试的只是解析器和扩展器是一致的,而您想要做的是测试扩展器是否确实执行了预期的操作。但是只要不存在其他依赖关系,在此单元测试中使用解析器与在单元测试中使用标准库实际上并没有什么不同。
史蒂夫·杰索普

@SteveJessop好点。使用独立的组件很重要。
乔纳森·尤尼斯

3
在解析器本身是一项昂贵的操作(例如,通过com interop从Excel文件中读取数据)的情况下,我所做的事情是编写运行解析器的测试生成方法,并将代码输出到控制台,以重新创建解析器返回的数据结构。然后,我将生成器的输出复制到更常规的单元测试中。这样可以减少交叉依赖性,因为解析器仅在创建测试时才需要正确运行,而不是在每次运行时都可以正常运行。(不浪费几秒钟/创建/销毁Excel流程的测试是一个不错的选择。)
Dan Neely 2014年

+1 @DanNeely的方法。我们使用类似的方法将数据模型的多个序列化版本存储为测试数据,以便可以确保新代码仍然可以与旧数据一起使用。
克里斯·海斯

6

当然可以!

您始终需要执行完整代码路径的功能/集成测试。在这种情况下,完整的代码路径意味着包括对生成代码的评估。那就是您测试解析x = 2 + 3 * a产生的代码,如果运行a = 5将设置x17,如果运行a = -2将设置x-4

在此之下,您应该对较小的位进行单元测试,只要它实际上有助于调试代码。您拥有的粒度测试越精细,对代码的任何更改也需要更改测试的可能性也就越高,因为内部接口会发生变化。这样的测试没有什么长期价值,并且增加了维护工作。因此,存在收益递减的问题,您应该在此之前停下来。


4

单元测试使您可以查明损坏的特定项目及其在代码中的损坏位置。因此,它们非常适合进行细粒度的测试。好的单元测试将有助于减少调试时间。

但是,根据我的经验,单元测试很少能足以实际验证正确的操作。因此,集成测试也有助于验证操作链或操作顺序。集成测试可以帮助您完成功能测试。正如您指出的那样,由于集成测试的复杂性,很难在代码中找到测试中断的特定位置。它也具有更大的脆性,因为链中任何地方的故障都会导致测试失败。但是,您仍将在生产代码中保留该链,因此测试实际链仍然很有帮助。

理想情况下,您应该同时拥有这两种功能,但总的来说,自动测试总比没有测试好。


0

在解析器上进行大量测试,并在解析器通过测试时,将这些输出保存到文件中以模拟解析器并测试其他组件。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.