原作:Andy Schneider Richard Dallaway 等
编译:PMT 测试工作组
译者注:
很多测试人员都有过编写测试框架的经历,JUnit的出现避免了其中的大量重复劳动。但如同其他的工具一样用得好和用得差的结果是截然不同的。我们编辑这样一个JUnit的系列希望能够帮助越来越多的JUnit使用者用好JUnit。我们也希望读者们能够把自己的一些经验所得和大家分享。
经验一、不要在测试用例的构造函数中做初始化
当我们需要增加一个测试时,我们要书写一个自己的测试用例,比如SomeTest。如果你喜欢在SomeTest的构造函数中做有关的初始化工作,这可不是个好习惯。如下例:
public class SomeTest extends TestCase{
public SomeTest(String testName){
super(testName);
//初始化代码
}
}
一旦初始化代码产生异常,比如IllegalStateException,JUnit随之将产生一个AssertionFailedError,并显示类似下面的出错信息:
j u n i t . f r a m e w o r k . A s s e r t i o n F a i l e d E r r o r : C a n n o t i n s t a n t i a t e t e s t c a s e : t e s t 1 a t
j u n i t . f r a m e w o r k . A s s e r t . f a i l ( A s s e r t . j a v a : 1 4 3 ) a t
j u n i t . f r a m e w o r k . T e s t S u i t e $ 1 . r u n T e s t ( T e s t S u i t e . j a v a : 1 7 8 ) a t
j u n i t . f r a m e w o r k . T e s t C a s e . r u n B a r e ( T e s t C a s e . j a v a : 1 2 9 ) a t
j u n i t . f r a m e w o r k . T e s t R e s u l t $ 1 . p r o t e c t ( T e s t R e s u l t . j a v a : 1 0 0 ) a t
j u n i t . f r a m e w o r k . T e s t R e s u l t . r u n P r o t e c t e d ( T e s t R e s u l t . j a v a : 1 1 7 ) a t
j u n i t . f r a m e w o r k . T e s t R e s u l t . r u n ( T e s t R e s u l t . j a v a : 1 0 3 ) a t
j u n i t . f r a m e w o r k . T e s t C a s e . r u n ( T e s t C a s e . j a v a : 1 2 0 ) a t
j u n i t . f r a m e w o r k . T e s t S u i t e . r u n ( T e s t S u i t e . j a v a , C o m p i l e d C o d e ) a t
j u n i t . u i . T e s t R u n n e r $ 1 2 . r u n ( T e s t R u n n e r . j a v a : 4 2 9 )
这一大堆出错信息只会让人一头雾水,我们只知道JUnit无法实例化某个测试用例,到底出了什么问题,在哪儿出错了呢?不知道!那么好的做法是怎样呢?
答案是重载测试用例的setUp()方法进行初始化。当setUp()中的初始化代码产生异常时我们得到的是类似下面的出错信息:
j a v a . l a n g . I l l e g a l S t a t e E x c e p t i o n : O o p s a t b p . D T C . s e t U p ( D T C . j a v a : 3 4 ) a t
j u n i t . f r a m e w o r k . T e s t C a s e . r u n B a r e ( T e s t C a s e . j a v a : 1 2 7 ) a t
j u n i t . f r a m e w o r k . T e s t R e s u l t $ 1 . p r o t e c t ( T e s t R e s u l t . j a v a : 1 0 0 ) a t
j u n i t . f r a m e w o r k . T e s t R e s u l t . r u n P r o t e c t e d ( T e s t R e s u l t . j a v a : 1 1 7 ) a t
j u n i t . f r a m e w o r k . T e s t R e s u l t . r u n ( T e s t R e s u l t . j a v a : 1 0 3 )
...
显然这要清楚得多我们一下子可以知道是在DTC.java 的第34 行产生了IllegalStateException
经验二、不要假定测试用例中测试的执行次序
我们知道在一个JUnit 的测试用例类中可以包含多个测试,每个测试其实是一个method。在下面的例子中有两个不同的测试,尽管testDoThisFirst()在位置上先于testDoThisSecond(),但我们不能此假定testDoThisFirst()会先执行。
public class SomeTestCase extends TestCase{
public SomeTestCase(String testName){
super(testName);
}
public void testDoThisFirst(){
...
}
public void testDoThisSecond(){
}
}
由于JUnit 内部使用一个Vector 来存储所有的test,因此在不同的操作系统和Java 虚拟机上,test 的执行次序是不可预测的。好的习惯是保持测试之间的独立性,使得它们在任何次序下执行的结果都是相同的。如果真得需要某些测试按照特定的次序执行,我们可以借助addTest 来实现。如下例:
public static Testsuite(){
suite.addTest(new SomeTestCase(“testDoThisFirst”;));
suite.addTest(new SomeTestCase(“testDoThisSecond”;));
return suite;
}
这样我们可以确保JUnit先执行testDoThisFirst(),然后执行testDoThisSecond()。
经验三、测试要避免人工干预
如果某段测试代码需要人工干预,那至少有两个不良后果:一则不能被包括在自动测试中,比如夜间的回归测试;二则不能被重复执行,例如数据删除的测试不能做完删除万事大吉,比较好的做法是自动补上删除掉的数据。经验二讲的是不同的测试要避免相关性,而经验三讲的其实是测试要避免自相关。