原作: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()。
经验三、测试要避免人工干预 如果某段测试代码需要人工干预,那至少有两个不良后果:一则不能被包括在自动测试中,比如夜间的回归测试;二则不能被重复执行,例如数据删除的测试不能做完删除万事大吉,比较好的做法是自动补上删除掉的数据。经验二讲的是不同的测试要避免相关性,而经验三讲的其实是测试要避免自相关。