单元测试的小技巧介绍和举例

发表于:2007-4-13 09:14

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Roy Osherove    来源:MSDN

  你可以打乱代码去测试覆盖,这有一些关于如何测试的变化:

' Try this...
If Not True Then ' replace flag with const
?If x < 0 OrElse y < 0 Then Throw New Exception()
End If
' Or this...
If Not allowNegatives Then
?' replace check with const
If False OrElse y < 0 Then Throw New Exception()
End If

  如果所有的测试依然通过,那么你缺少了一个测试,另外一个红色标志是在你为多种相同值测试的检查。如下:

  ssert.AreEqual(3, retval)

  一些方法的关系只看一次(在一个测试中)意味着你可以安全的返回3作为一个值,然后所有的针对这个方法的测试都将通过,这个当然意味着你丢失了一个测试。如果你在单元测试中检查一下代码,它就很容易被检查出来。

  确保你的测试写的越简单越好,一个单元测试一般不包括一个if switch或者其他任何的逻辑声明。如果你发现你自己在你的测试中写了一些类似于逻辑声明的东西,这是一个好的机会来测试一个以上的事件,在做这样的操作的时候,你会使得你的测试比读和维护更加的困难,在生产代码中同样如此。保持你的测试简单,你在生产代码中发现bug要胜于在你的单元测试中。

  使测试易于运行? 如果你的测试并不容易运行,那么人们不会信任它。你的应用程序最有可能有下面两种类型的测试:

  测试在没有任何配置的情况下平稳的运行(这种类型的测试,我们可以在任何的机器上,在代码的最终版上或者在源控制上测试,并且做到没有任何故障的测试。)

  在运行前需要一些配置

  第一种类型是你应该模仿的,第二种类型是你通常做的,尤其你如果你是一个新的单元测试。如果你发现你自己测试时有很多的特殊的需求,现在是正常的,但是重要的一点就是你要隔离出两个组让他们能够单独的去做测试。

  我们的想法是任意一个开发者都应该有能力修改和运行一些不需要设置特殊的配置的测试进行测试。如果这有一些测试需要在运行前有特殊的关注,开发者应该知道他们,然后他可以花一些时间学习这些测试的方法。因为很多的开发者比较的懒(当然,不是你),你可以设想,他们不会去做那些特殊的设置,相反,他们会让测试失败因为他们有更好的事情去做。

  当用户让测试失败时,他们开始考虑他们不能够信任这些测试了。很难说是否测试可以在一个中找到一个正式的bug或者只是一个错误的定位。开发者可能不明白为什么测试者会在一开始就执行失败。一旦他们不再信任你的测试,开发者将会停止运行它们,那么bug就会驻留在程序中,之后一连串的麻烦就来了。

  为了避免这件事情,确认你总是有一个组准备好了去测试,测试程序则是可以安全运行,可信任的。把那些属于配置挑战组的测试放到不同的文件夹,树或者工程中,同时标记特殊的说明指明他们在运行前需要做什么。完成这些后,开发者可以不投入时间去配置就开始测试工作。当他们有时间和需要时,他们也可以配置,运行更多的测试环节。

  创建维护测试

  我们应该试着避免测试私有或保护成员。这篇文章也许能够帮助一些人解决一部分问题,但是我很坚决相信百分之九十九的时间,你可以全面的测试一个类,通过编写一些与它的独立公共接口相反的单元测试。测试私有成员可以使你的测试更加脆弱,如果这个需要被测试的类的一些内在方面略有改动的话。你应该使用通过调用一些代码里别处的公共功能这一方法去测试私有功能。当你依然能够确定全部功能并没有改变的时候,仅仅测试公共成员会导致测试遭受常量代码的因式分解以及内部的执行情况改变。

  在可能的时候,应该重新使用你的创造物,处理过程,和声明代码。不要在一个单元测试中直接的创建类的实例。如果你在任何并不包含在此单元测试框架中的类前面看到这个单词“new”,你应该考虑一下将你创造的代码放在一个特殊的整体方法之中,它可以为你创建一个对象实例。你可以到时再重新使用这个方法来获得你的测试在其他测试之中的最新实例。这样可以帮助你来保持这个测试维护所需的时间,然后在测试进行的时候,从对代码无法预料的改变之中保护你的测试。作为一个例子,Figure 1展示了一对简单的测试,它使用了一个Calc类。

  假设你有20,或者你甚至有100,与Calc类做相反测试,所有这些看起来令人吃惊的相似。现在一个计划的改变迫使你不得不删除默认的Calc构造器并且使用一个含有一些参数的不同的构造器。马上,你所有的测试就被暂停了。你可能可以很轻易的发现问题的关键并修复它,但你也可能做不到。最主要的问题是你将会浪费很多宝贵时间在修理你的测试上面。如果你在你的测试类之中使用一个整体的方法去创建Calc 实例,就像Figure 2所显示的那样,这些就并不是个问题。

  我已经对测试做了一些改变已使它们能够具有更多可维护性。首先,我将新创建的代码迁移至可以再度使用的整体方法之中。这就意味着我只需仅仅改变一个简单的方法以使得在这个测试类中的所有测试在一个新的构造器中的能够正常的工作。另外一个为创造问题而设的简单解决方法是把创作物迁移到测试类的<TestInitialize()>方法之中。不幸运的是,这个能够很好的工作仅仅在你重新使用一个对象并在一些测试中把它当作一个局部类变量。如果你仅仅为一些测试使用它(部分相关成员),你倒不如在测试中将它们实例化,并且使它们更具易读性。

  顺便一提的是,请注意,我已经将方法命名为Factory_CreateDefaultCalc 。我很喜欢将我测试中的任何帮助方法用特殊的前缀来命名,这样我就能很轻易的掌握它是做什么用的。这样对易读性也是非常有帮助的。

  我的第二个改变是重新使用测试中的声明代码,并将这段代码迁移到一个确认方法之中。所谓确认方法是你测试中的一个可再度使用的方法,这个方法包含了一个声明语句但是它可以接受不同输入和在输入的基础上进行校验。当你在不同输入或者不同的初始状态下一次又一次的声明同一事物时,你可以使用确认方法。这一方法的优点是既使在一个不同的方法里面声明,如果这个声明失败了你将可以继续保有一个异常处理,而且原始调用测试将会显示在测试失败输出窗口之中。

  我也在Calc 中传递实例而不是使用一个局部变量,因此我知道我经常传递一个实例,而且这个实例是调用测试将其初始化的。当你想要改变对象状态时你可能想要做同样的事情,举个例子来说,当在测试下或者在将会传递给测试的对象下配置特殊对象时,可以使用特殊的Configure_XX方法。这些方法应该能够解释他们配置一个对象将会用来做什么用。Figure 3之中的代码就是以上方法的实例。

  这个测试拥有很多设置代码可以用来处理向注册管理器对象中添加初始状态,它是这个测试类之中的成员。在此的确也有一些重复。Figure 4显示了在初始代码之外这些事例在因式分解之后将会如何变化。

  修订测试具有非常高的可读性和稳定性。仅仅需要注意的是不要那么的refactor你的测试,他们可能会以一个单一的,不可读的代码行作为结束。应该注意的是我在这里可能依然使用一个Verify_XX 方法,但是这并不是我真正要在这里加以说明的。

42/4<1234>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号