8.1.3 对代码的详细介绍
一个单元测试代码主要分为以下几个部分。
(1)设置环境。
(2)运行测试。
(3)结果判断。
(4)清理环境。
这里,设置环境和清理环境是有区别的。
设置环境:比如,建立数据库连接。
清理环境:比如,断开数据库连接。
1.包含必要的Package
importstatic org.junit.Assert.*;
assertEquals是Assert类中的一系列的静态方法,一般的使用方式是Assert. assertEquals(),但是使用了静态包后,前面的类名就可以省略了,使用起来更加方便。比如:
assertEquals(8, calculator.getResult());
显而易见,assertEquals函数的主要功能是实现“结果判断”。
2.测试类的声明
测试类是一个独立的类,没有任何父类。测试类的名字也可以任意命名,没有任何局限性。所以,不能通过类的声明来判断它是不是一个测试类。测试类与普通类的区别在于它内部方法的声明。
3.创建一个待测试的对象
要测试哪个类,要创建一个该类的对象。比如第8.1.1节和第8.1.2节中,为了测试Calculator类,必须创建一个Calculator对象。
privatestatic Calculator calculator = new Calculator();
4.测试方法的声明
在测试类中,并不是每个方法都用于测试,必须使用“标注”来明确表明哪些是测试方法。“标注”也是JDK5的一个新特性,用在此处非常恰当。可以看到,某些方法的前面有@Before、@Test、@Ignore、@After等字样,这些就是标注,以一个“@”作为开头。第8.1.3节开始的描述的@Before、@Test、@After对应于:
(1)标记@Before:设置环境。
(2)标记@Test:运行测试。
(3)标记@After:清理环境。
这个方法的前面使用@Test标注,表明这是一个测试方法。对方法的声明,也有如下要求:名字可以随便取,没有任何限制,但是返回的值必须为void,而且不能有任何参数。如果违反这些规定,会在运行时抛出一个异常。
@Test publicvoid testSubstract() { calculator.substract(10,2); assertEquals(8,calculator.getResult()); }
在测试方法中调用substract函数,将10减去2,期待的结果应该是8。如果最终的实际结果也是8,则说明substract函数是正确的,反之说明它是错的。
assertEquals(8,calculator.getResult()):用来判断期待结果和实际结果是否相等,第一个参数填写期待结果,第二个参数填写实际结果,也就是通过计算得到的结果。这样写好后,JUnit会自动进行测试,并把测试结果反馈给用户。
8.1.4 对JUnit 4的高级操作
1.@BeforeClass和 @AfterClass
有一个类是负责对大文件(超过500MB)进行读写,它的每一个方法都是对文件进行操作。换句话说,调用每个方法前,都要打开一个大文件,并读入文件内容,这绝对是一个非常耗时的操作。如果使用@Before和@After,那么每次测试都要读取一次文件,效率极其低下。
@BeforeClass和@AfterClass两个标识来帮助实现这个功能。从名字上就可以看出,用这两个Fixture标注的函数,只在测试用例初始化时执行 @BeforeClass方法,当所有测试执行完毕后,执行@AfterClass方法进行收尾工作。这里要注意,每个测试类只能有一个方法被标注为@BeforeClass或@AfterClass,并且该方法必须是public和static。
2.防止超时
比如,程序里存在死循环,如何处理?如在“简单计算器”产品代码中,有语句:
publicvoid squareRoot( int n) { for(; ;) ; // Bug : 死循环 }
要实现这一功能,给@Test标注加一个参数即可:
@Test(timeout=1000) //1000ms publicvoid testSquareRoot() { calculator.squareRoot( 4 ); assertEquals( 2,calculator.getResult()); }
也就是说,测试用例等待时间1000ms(即1s),如果1s内没有反应,就认为测试失败。
3.Runner(运行器)
当测试代码提交给JUnit 4框架后,JUnit 4框架通过Runner如何来运行测试代码。如果不设置,就认为是默认的,但是也可以设置:
import org.junit.internal.runners.TestClassRunner; import org.junit.runner.RunWith; // 使用了系统默认的TestClassRunner,与下面代码完全一样 public classCalculatorTest { ... } @RunWith(BlockJUnit4ClassRunner.class) public class CalculatorTest { ... }
JUnit 4的Runner主要有BlockJUnit4ClassRunner、ParentRunner、Statement、TestRule、Description、RunNotifier、Enclosed和InvokeMethod。其中BlockJUnit4ClassRunner.class是默认的Runner。
l Enclosed:是实现内部类中测试类的运行器。
l ParentRunner:是JUnit 4测试执行器的基类,它提供了一个测试器所需要的大部分功能。继承它的类需要实现:
protectedabstract List<T> getChildren();
protectedabstract Description describeChild(T child);
protectedabstract void runChild(T child,RunNotifier notifier);
l Parameterized:则可以设置参数化测试用例。
l JUnit38ClassRunner:是为了向后兼容JUnit 3而定义的运行器。
l Statement:在运行时,执行test case前可以插入一些用户动作,它就是描述这些动作的一个类。继承这个类要实现:
/** *Run the action,throwing a {@code Throwable} ifanything goes wrong. */ publicabstract void evaluate() throws Throwable;
这个方法会先后在ParentRunner.run()和ParentRunner.runLeaf()这两个方法里面调用。另外,我们可以自定义一个Statement,并且实现evaluate()方法。
l TestRule:TestRule可以描述一个或多个测试方法如何运行和报告信息的接口。在TestRule中可以额外加入一些check,我们可以让一个test case失败/成功,也可以加入一些setup和cleanup要做的事,也可以加入一些log之类的报告信息。总之,跑test case之前的任何事,都可以在里面做。需要实现apply()方法。
/** * Modifies the method-running{@link Statement} to implement this * test-running rule. * @param base The {@linkStatement} to be modified * @param description A {@linkDescription} of the test implemented in {@code base} * @return a new statement,which may be the same as {@codebase}, * a wrapper around {@code base},or a completely new Statement. */ Statement apply(Statementbase,Description description);
l Description:存储着当前单个或多个test case的描述信息。这些信息跟逻辑无关,比如原数据信息等。实例化Description用Description.createTestDescription()方法。
l RunNotifier:运行时通知器。执行Runner.run(RunNotifierrunNotifier)方法时,需要传一个RunNotifier进去,这个RunNotifier是事件的管理器,它能帮助我们监控测试执行的情况。
l InvokeMethod:最终执行test case里面的测试方法通过这个类来做,这个类会间接调用Method.invoke()方法通知编译器执行@test方法。
l ……
4.参数化测试
案例8-2:计算一个数的平方。
测试“计算一个数的平方”这个函数,暂且分3类:正数、0、负数。测试代码如下:
import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert. * ; publicclass AdvancedTest { privatestatic Calculator calculator=newCalculator(); @Before publicvoid clearCalculator() { calculator.clear(); } @Test public void square1() { calculator.square( 2 ); assertEquals( 4,calculator.getResult()); } @Test Public void square2() { calculator.square( 0 ); assertEquals( 0,calculator.getResult()); } @Test Public void square3() { calculator.square( - 3 ); assertEquals( 9,calculator.getResult()); } }
如果用参数化实现代码,就简化成如下形式:
import static org.junit.Assert.assertEquals; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import java.util.Arrays; import java.util.Collection; @RunWith(Parameterized.class ) public class SquareTest{ private static Calculator calculator = new Calculator(); private int param; private int result; @Parameters public static Collection<Object[]>data { return Arrays.asList( new Object[][] { {2,4}, {0,0}, {-3,9}, } ); } // 构造函数,对变量进行初始化 public void SquareTest(int param,int result) { this .param=param; this .result =result; } @Test Public void square() { calculator.square(param); assertEquals(resul,calculator.getResult()); } }
5.打包测试
对于打包测试,就是把所有的测试类打成一个包一起运行,其代码如下:
import org.junit.runner.RunWith; import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ CalculatorTest.class, SquareTest.class } ) public class AllCalculatorTests{ }
顾翔凡言:
不是好的工作会给你带来好的心情,而是好的心情会给你带来好的工作。