Pex(测试生成)是一个真正有用的工具吗?

29

是的,可以对“Sum”或“Divide”等函数的边界值生成测试。 Pex是一个好的工具。

但更常见的是我们根据业务行为创建测试。让我们来看看经典的Beck tdd书中的例子:

[Test]
public void ShouldRoundOnCreation()
{
  Money money = new Money(20.678);
  Assert.AreEqual(20.68,money.Amount);
  Assert.AreEqual(2068,money.Cents);
}

这个测试能够生成吗?不行 :) 我项目中95%的测试检查业务逻辑,无法自动生成。

Pex(尤其是与Moles配合使用)可以提供100%的代码覆盖率,但测试套件的高代码覆盖率从来不意味着代码被充分测试-它只会产生虚假信心,认为所有东西都已经被测试了。这非常危险。

所以问题是-Pex真的是一个有用的工具吗?

2个回答

30

我认为你对 Pex 的使用方法有误:我们强烈建议用户在其参数化的单元测试中编写断言。

如果编写了断言,Pex 将会系统地尝试使其无效 - 这正是 Pex 的优势所在:你编写的断言越多,它就会尝试找到更多的 bug。

如果你使用 Pex 来获得高代码覆盖率的测试套件而没有编写断言,那么你只能得到你要求的东西:代码覆盖率和保证运行时异常。Pex ‘只’尝试覆盖分支(1个断言=1个分支),如果没有分支需要覆盖(没有断言),它将不会生成有趣的测试用例。

总的来说,在参数化的单元测试中编写断言更难编写,因为...嗯,它们更加通用。让我们以舍入行为为例:肯定有一个界限,你允许进行舍入操作,这应该自然地转化为参数化的单元测试:

[PexMethod]
public void RoundInBounds(double value) 
{
    var money = new Money(value);
    // distance between value and amount should be at most 0.01/2
    Assert.AreEqual(value, money.Amount, 0.005);
}

还有许多模式可用于编写这些内容。例如,您的“Money”类可能是自幂的:如果将“Amount”的值反馈到Money实例中,您将获得Amount。这可以优雅地转化为带参数的单元测试:

[PexMethod]
public void RoundIsIdempotent(double value) 
{
     var first = new Money(value).Amount;
     var second = new Money(first).Amount;
     Assert.AreEqual(first, second, 0.0001);
}

需要注意的是,带参数的单元测试绝对属于TDD(测试驱动开发)的范畴。只需先编写带有参数的单元测试,Pex会找到失败的bug,修复它,然后Pex再找到下一个失败的bug,如此反复...

这是否让Pex成为一个有用的工具?由你来评判。


简而言之:1)Pex本身无法生成足够的测试。2)它可以用于为参数化测试生成参数。第二个是有用的,同意。但是回归呢?如果我发现代码片段在参数=123,43时失败,那么我可能想要在nunit参数化测试中保存这个案例。您是否尝试过将pex和nunit参数化测试混合使用? - Yauheni Sivukha
出于好奇,你在使用Visual Studio的时候为什么要使用NUnit? - zumalifeguard
2
我更喜欢NUnit而不是MSTest,因为它更完整。然而,我真的没有任何反对MSTest的意见。 - Yauheni Sivukha
2
欢迎您混合使用传统单元测试和参数化单元测试:参数化单元测试只是带有参数的方法。如果您需要添加回归场景,只需添加即可 - 不要陷入一种方法论中,拥抱所有可能性。 - Peli
2
Pex可以直接为MSTest、NUnit、xUnit.net或MbUnit生成单元测试。事实上,它是可扩展的,因此您也可以自己设计单元测试框架并将其集成进去。 - Peli
这是一个好答案。如果天真的开发人员大规模地错误使用框架,那么这并不是框架本身的错。他们同样有可能错误地采用其他库。工具只是工具,并不是某种万能神器。 - Joshua Ramirez

3

Pex有一些有用的功能。

  1. 代码覆盖率。先把Pex放在一边。通常来说,100%的代码覆盖并不意味着你有很好的代码覆盖。这只是意味着每个路径都被执行了,但程序具有数据流,测试该代码不同的附加输入会给你最好的“测试覆盖率”,如果不是代码覆盖率。在这里,我只是重申你的观点。100%的代码覆盖并不一定是好的,但你可以确定25%的代码覆盖是不好的,所以这就是代码覆盖率有用的地方。当你的代码覆盖率很低时,你知道你的测试覆盖率也很低。

    当你使用Pex来获得100%的代码覆盖率时,它并不真正帮助你获得更好的测试覆盖率,但它确实为生产代码的每个部分提供了一些测试,这些测试可以用于调试器。事实上,一个关于Pex的演示会议展示了使用Pex进行这个目的的方法。程序员说:“看看NHibernate中的这个方法。我想通过调试器步进它,看看它做了什么,但是我如何通过库的正常“业务入口点”调用该方法?如果你对库一无所知,你不能。所以他运行了Pex,并能够使用各种参数步进代码。有趣,是的。有用,也许是,也许不是。

  2. 除了自动创建测试之外,Pex还可用于参数化测试。创建数据驱动的单元测试要好得多。为什么要重复编写相同的代码,只是带有不同的参数。只需编写一次,然后从数据源提供参数即可。

  3. Pex还可以作为一个简单的存根框架。使用新的lambda表达式,这可能是创建模拟对象最容易的方法,比如RhinoMocks等复杂框架要容易理解得多。通过Moles,您还可以存根不仅是接口,而且是类中的具体非虚拟方法。

  4. 我也会小心“在业务逻辑”层进行过多的测试。你很容易陷入行为驱动开发,这不是单元测试。在那里,你正在针对一个规范进行测试。如果你只做这个,你怎么测试,比如没有业务价值,但是是整个应用程序中使用的内部库的自定义数据结构等。


  1. 百分之百的覆盖率并不一定意味着好,这就是为什么它很危险的原因。
  2. 你可以使用经典测试在调试器下运行代码。但如果你有适当的测试,你需要调试器的情况非常少。
  3. Nunit也支持参数化测试,唯一的区别是参数不是自动生成的。
  4. Moles可以与Pex分开使用。Moles相对于Rhino或Moq的唯一优势在于它可以为静态调用和构造函数创建存根(从设计角度来看这并不好)。
  5. BDD - 只是TDD的一个子集,以稍微不同的方式完成。
- Yauheni Sivukha
小心说它很危险。仅仅因为它可能被滥用并不意味着它不能也是有用的。BDD 不是 TDD 的子集 - BDD 在更高的层面上捕获用户故事、行为,而单元测试则捕获代码的结构和设计。 - zumalifeguard
我并没有说Pex是无用和危险的,因为它可以提供100%的覆盖率。我只是说100%的覆盖率会给人虚假的信心。关于BDD - 你不觉得在常规测试(单元测试、集成测试和功能测试)中捕获用户故事是可能的吗?(http://www.code-magazine.com/article.aspx?quickid=0805061&page=1 最后一段) - Yauheni Sivukha
这个答案基于错误的前提和错误的前提。不想争论,只是指出这一点。 - Joshua Ramirez
第三点与PEX无关,应从答案中删除。在VS 2012中,Moles已被Fakes取代。 - Michael Freidgeim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接