IntelliTest(1) - One Test to rule them all[译]

  在传统单元测试方案中,每个测试用例代表一个使用场景,开发者可以使用断言来验证每种场景下的输入和输出是否符合预期。

  针对相对少量的测试用例来说,此方案似乎已足够,但是,那些做了多年开发的老鸟们深知,即使经过相对较全面测试的代码,依然潜伏着bug,就像一个常规的输入可以得到正确的结果,但是换一个类似的但是未经测试的输入,它就神奇的崩了。

传统单元测试的生成

  IntelliTest可以生成传统单元测试用例。当在一段被测试代码上运行IntelliTest时,它会尽量去生成高覆盖率的单元测试用例(默认情况下,它会以代码分支为单位进行覆盖率统计)。为了做到这些,它会迭代执行具体的输入以便可以覆盖被测代码更多分支,点击这里可以了解到它是怎么做到的
  这些生成的传统测试用例包含一个单独的用例执行方法和多个对应不同输入的用例,这些对应不同输入的用例里面包括了相应的输出和断言。这些输入中包括了几乎所有边界情况,而对于边界情况,往往是开发者难以考虑完全的。

  对的,你可能会问:“IntelliTest是怎么去验证代码正确与否呢?对于一个做加法操作的方法,最终返回了乘法操作的结果,那么高覆盖率的测试用例又有多大用处呢?”。

  虽然不能理解被测试代码的预期行为,但是IntelliTest生成的测试用例却真实的描述了不同输入条件下代码的执行情况。同时保证每一次对代码的修改后可以快速知道是否影响了原有逻辑。

正确性验证

  要验证正确性,很自然会引入断言。引擎能够计算出输入以测试不同的分支,而我们需要为其增加断言。测试用例中必须包含断言才能保证被测代码的正确性。如果没有断言,代码覆盖率和代码正确性就无从说起。

一次测试搞定所有

  摸爬滚打多年你的开发老鸟都明白,现在为它写了测试用例,未来它很可能会被修改,因此现在的测试用例可能无法满足未来的分支覆盖要求。但是我们依然有必要为现在的代码写单元测试,当我们写的足够多的时候,我们对被测代码和各个测试用例有了更深的理解,甚至可以抽象出一些通用的用例以表示其行为。在此之前,我们可能会说“对于这个确切的输入a,会有这个期望的结果b”,经过经验的累积,我们则会说“这个方法将对它的输入做这样的计算”。更通俗一点,在此之前我们会说“输入2,应该返回4”,在此之后,我们则说“输入一个整数,返回两倍于它的整数”。看吧,我们已经提升了对测试用例的理解,根本上,其验证的是输入输出之间的关系。这种关系可以封装在一个通用的测试方法中 - 此时,我们所做的任何断言都必须能够应对所有可能的输入。下面的例子就是一个通用的测试方法,当一个元素加入到一个非空列表后,这个列表一定包含此元素。

void TestAdd(ArrayList list,object element)
{
    PexAssume.IsNotNull(list);
    list.Add(element);
    PexAssert.IsTrue(list.Contains(element));
}

这里很好的利用了假设(Assume)对输入做出的约束,这可以放方便的让开发者定制自己的输入。IntelliTest 提供了丰富的假设API,这个例子也展示了IntelliTest提供的断言API,但是我们也可以使用其他测试框架的断言库。IntelliTest会自动生成通用的测试方法 - 这就是所谓的参数化单元测试(PUT),它将作为被测代码在测试工程中的入口。IntelliTest 可以直接对被测代码进行探测, 也可以对PUT进行探测,以便生成或更新单元测试用例。这就是IntelliTest的神奇威力。

  在文章的开头,我们讲了IntelliTest试图生成高覆盖率的单元测试用例。但是,要想IntelliTest发挥最大威力还需要克服一些障碍,被测代码的复杂性就是最大的障碍之一,在接下来的博客中,我们会介绍如果克服这些障碍。

强烈建议阅读英文原文

date: 2017-10-20 08:43:52

posted @ 2018-02-05 22:45  .NET学徒  阅读(233)  评论(0编辑  收藏  举报