Junit是Java的单元测试工具,同时也是极限编程的好帮手。Junit4借助于Java5的Annotation(标注类)和静态导入的新特性,与Junit3有很大的区别,所以建议初学者直接使用Junit4。
构建项目
虽然Junit早已成为Eclipse的标配,但我还是习惯使用Maven构建项目,我使用Junit版本是4.11,构建的项目JunitDemo构架如下图:
在src/main/java我们新建一个计算器类(Calculator),以待测试,Calculator.java代码如下:
package net.oseye; public class Calculator { /** * 加法 * @param x * @param y * @return */ public int addition(int x,int y){ return x+y; } /** * 减法 * @param x * @param y * @return */ public int subtraction(int x,int y){ return x-y; } /** * 乘法 * @param x * @param y * @return */ public int multiplication(int x,int y){ return x*y; } }
添加测试类
使用Eclipse添加测试类也是非常方便的,在src/test/java右键->New->Junit Test Case,在对话框中填写名称和选择待测试类:
点击 Next,可以选择待测试的方法:
我们选择了addition方法,生成的测试类CalculatorTest.java代码:
package net.oseye; import static org.junit.Assert.*; import org.junit.Test; public class CalculatorTest { @Test public void testAddition() { fail("Not yet implemented"); } }
生成默认的测试使用fail,所以运行总会是失败的。
运行测试
在CalculatorTest.java上右键(或Ctrl+F11),运行测试用例:
@Test
每个单元测试用例都会以@Test标注,而@Test有三种方式:断言测试、异常测试和超时测试。
- 断言测试:
package net.oseye; import static org.junit.Assert.*; import org.junit.Test; public class CalculatorTest { @Test public void testAddition() { Calculator c=new Calculator(); assertEquals(5, c.addition(2, 3)); } }
void assertEquals(long expected, long actual)
- 异常测试
package net.oseye; import static org.junit.Assert.*; import org.junit.Test; public class CalculatorTest { @Test(expected=RuntimeException.class) public void testAddition() { Calculator c=new Calculator(); assertEquals(5, c.addition(2, 3)); } }
- 超时测试
package net.oseye; import static org.junit.Assert.*; import org.junit.Test; public class CalculatorTest { @Test(timeout=1000) public void testAddition() { Calculator c=new Calculator(); assertEquals(5, c.addition(2, 3)); } }
@Before和@After
我上面的例子中测试方法中是要先New一个Calculator实例,如果成千上百个测试用例,我都要做一些重复准备资源代码,如New示例、I/O对文件,创建数据库连接等等,那是比较麻烦而且无意义。
Junit为我们提供了@Before和@After标注,每个测试方法执行前执行@Before标注的方法,每个测试方法执行后执行@After标注的方法,@After可以清理现场:
package net.oseye; import static org.junit.Assert.*; import org.junit.After; import org.junit.Before; import org.junit.Test; public class CalculatorTest { private Calculator c=null; @Before public void init(){ System.out.println("@Before"); c=new Calculator(); } @Test public void testAddition() { assertEquals(5, c.addition(2, 3)); } @Test public void testAddition2() { assertEquals(8, c.addition(5, 3)); } @After public void destroy(){ System.out.println("@After"); c=null; } }
运行结果:
@Before
@After
@Before
@After
示例的@After我只是销毁了c,其实这里意义不大,但比如要清理由于测试二导致的垃圾数据,就很有必要了。
@BeforeClass和@AfterClass
@Before和@After在每次执行测试方法时都会执行,这对于进行I/O操作等又比较耗资源,所以Junit又提供了@BeforeClass和@AfterClass标注,是在测试类之前和之后执行,@BeforeClass和@AfterClass必须是static类型的:
package net.oseye; import static org.junit.Assert.*; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; public class CalculatorTest { private static Calculator c=null; @BeforeClass public static void init(){ System.out.println("@BeforeClass"); c=new Calculator(); } @Test public void testAddition() { assertEquals(5, c.addition(2, 3)); } @Test public void testAddition2() { assertEquals(8, c.addition(5, 3)); } @AfterClass public static void destroy(){ System.out.println("@AfterClass"); c=null; } }
输出:
@BeforeClass
@AfterClass
测试运行器Runner
这里先说下测试运行器,为后面的参数化测试和套件测试做个铺垫。
测试运行期Runner是JUnit中负责执行测试方法的类,JUnit为提供了默认的测试运行器,但是没有要求你必须使用默认运行器,因此你可以定制自己的测试运行器。
定制自己的测试运行器必须继承自abstract类org.junit.runner.Runner,然后使用@Runwith(定制的运行器.class)来指定测试使用自己的定制运行器,定制这部分可以自己尝试下,暂时不深入。
测试套件Suite
什么是测试套件?其实就是把各个测试组装在一起测试,JUnit4的测试套件反而没有JUnit3的测试套件直观,而是要使用定制的测试运行器和指定测试类,步骤是这样的:
- 创建一个空类作为测试套件的入口;
- 使用Suite运行器代替默认运行器;
- 将需要测试的类组成数组作为@SuiteClasses 的参数,以@SuiteClasses标注测试套件类;
注意:这个空类必须使用public 修饰符,而且存在public 的无参构造函数(类的默认构造函数即可)。
再添加一个测试类,用来测试减法CalculatorTest2.java:
package net.oseye; import static org.junit.Assert.*; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; public class CalculatorTest2 { private static Calculator c=null; @BeforeClass public static void init(){ System.out.println("@BeforeClass"); c=new Calculator(); } @Test public void testSubtraction() { assertEquals(7, c.subtraction(10, 3)); } @Test public void testSubtraction2() { assertEquals(5, c.subtraction(20, 15)); } @AfterClass public static void destroy(){ System.out.println("@AfterClass"); c=null; } }
生成的AllTests.java代码如下:
package net.oseye; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.class) @SuiteClasses({ CalculatorTest.class, CalculatorTest2.class }) public class AllTests { }
Eclipse已经为你添加了标注,省了好多事。运行输出:
@BeforeClass
@AfterClass
@BeforeClass
@AfterClass
参数化测试
我们看到CalculatorTest测试类中用了两个测试用例2+3=5和5+3=8,但对于一个方法的测试可能有成千上百个测试用例,如果每个测试用例都要用一个测试方法的话那我们就惨了!
因此JUnit又为你准备了参数化测试方法,编码步骤:
- 对于测试类使用Parameterized运行器代替默认运行器;
- 创建一个静态(static)测试数据供给(feed)方法,其返回类型为Collection,并用@Parameter 注释以修饰;Collection就是输入参数和期望值;
- 通过构造函数把Collection的值传入测试类;
- 编写测试方法;
仍使用CalculatorTest测试类,代码如下:
package net.oseye; import static org.junit.Assert.*; import java.util.Arrays; import java.util.Collection; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) public class CalculatorTest { private static Calculator c=null; private int expected; private int x; private int y; public CalculatorTest(int expected,int x,int y){ this.expected=expected; this.x=x; this.y=y; } @BeforeClass public static void init(){ System.out.println("@BeforeClass"); c=new Calculator(); } @Test public void testAddition() { assertEquals(this.expected, c.addition(this.x, this.y)); } @AfterClass public static void destroy(){ System.out.println("@AfterClass"); c=null; } @Parameters public static Collection<Object[]> data(){ return Arrays.asList(new Object[][]{ {5,2,3}, {11,8,3}, {12,8,4} }); } }