JUnit4快速入门
JUnit是一个Java语言的单元测试框架。它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中为最成功的一个。
基础断言方法
JUnit为所有的原始数据类型、对象以及列表提供了重载断言方法。断言方法的参数顺序为一个期望值与一个真实值,如assert(expected,actual)。另外还可以在所有参数前增加一个String参数来作为断言失败时的输出。
常见的断言包含以下8种:
Assert | 释义 |
---|---|
assertTrue | 真值断言 |
assertFalse | 假值断言 |
assertNotEquals | 不等断言 |
assertEquals | 相等断言 |
assertNull | 空断言 |
assertNotNull | 非空断言 |
assertFail | 断言失败 |
assertArrayEquals | 列表相等断言 |
例子:
@Test
public void testIntEquals() {
assertEquals(3,new JunitDemoApplication().add(1,2));
assertEquals(1,1);
}
@Test
public void testStringEquals(){
assertEquals("test","test");
}
@Test
public void testAssertArrayEquals(){
byte[] expected = "trail".getBytes(StandardCharsets.UTF_8);
byte[] actual = "trail".getBytes(StandardCharsets.UTF_8);
assertArrayEquals("两个数组是不同的",expected,actual);
}
//真值断言不需要期望值
@Test
public void testTrue(){
assertTrue("这是错的",true);
}
@Test
public void testFalse(){
assertFalse("这是对的",1==3);
}
@Test
public void testSame(){
assertSame("两个对象不同",new Object(),new Object());
}
@Test
public void testNotNull(){
assertNotNull("这是空对象",new Object());
}
聚合测试–@SuiteClasses
使用Suites可以建立一个包含多个单元测试类的集合。如果需要使用Suites,那么首先需要在测试类上添加以下注释:
@RunWith(Suite.class)
@SuiteClasses({TestA.class,TestB.class,...})
之后,在运行测试类时,将会运行SuiteClasses中的所有测试单元。
例子:
import org.junit.Test;
public class A{
@Test
public void a(){
System.out.println("单元测试a!");
}
}
import org.junit.Test;
public class B{
@Test
public void b(){
System.out.println("单元测试b!");
}
}
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({A.class,B.class})
public class SuiteTest {
}
当我们运行SuiteTest时,A和B中的所有单元测试方法都会被运行。
顺序测试
@FixMethodOrder
@FixMethodOrder(MethodSorters.JVM)
按照JVM返回的顺序运行单元测试方法,每一次单元测试的顺序都有可能不同。
例子:
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import java.util.Random;
@FixMethodOrder(MethodSorters.JVM)
public class OrderTest {
@Test
public void testAC() {
System.out.println("first");
}
@Test
public void testAB() {
System.out.println("second");
}
@Test
public void testC() {
System.out.println("third");
}
}
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
按照函数名的ascii码来对单元测试方法进行顺序执行。
@FixMethodOrder(MethodSorters.DEFAULT)
在没有显示指定单元测试的运行顺序是,会按照这种default顺序运行。
@OrderWith
在JUnit4的官网上,提供了一个按照函数名的ascii码来对单元测试函数进行顺序执行的例子。其实@OrderWith(Alphanumeric.class)的效果等同于@FixMethodOrder(MethodSorters.NAME_ASCENDING)。
例子:
import org.junit.Test;
import org.junit.runner.OrderWith;
import org.junit.runner.manipulation.Alphanumeric;
@OrderWith(Alphanumeric.class)
public class OrderTest {
@Test
public void testAC() {
System.out.println("first");
}
@Test
public void testAB() {
System.out.println("second");
}
@Test
public void testC() {
System.out.println("third");
}
}
异常测试
@assertThrows
JUnit在4.13版本加入了assertThrowns方法。借助该方法,可以断言某个方法会抛出异常。
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertThrows;
public class throwsTest {
@Test
public void testThrown(){
List<Object> list = new ArrayList<Object>();
IndexOutOfBoundsException thrown = assertThrows(
IndexOutOfBoundsException.class,
()->list.add(1,new Object()));
System.out.println(thrown.getMessage());
}
}
如果你使用的JUint版本低于4.13(当然,现在应该是很少人用那些的版本了,毕竟5都快出来了),也可以用下面的这种通用的方法。
import org.junit.Test;
import java.util.*;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
public class throwsTest {
@Test
public void testThrown1(){
List<Object> list = new ArrayList<>();
try {
list.get(0);
fail("Expected an IndexOutOfBoundsException to be thrown!");
}catch (IndexOutOfBoundsException e){
assertThat(e.getMessage(),is("Index: 0,Size: 0"));
}
}
}
另外还可以通过@Test注释来指定异常单元测试
import org.junit.Test;
import java.util.*;
public class throwsTest {
@Test(expected = IndexOutOfBoundsException.class)
public void empty() {
new ArrayList<Object>().get(0);
}
}
在使用这种形式进行单元测试时,要注意:只要单元测试方法中的任意位置抛出指定的异常,测试就能通过,而且我们无法获取异常的信息。
assertThat和Matchers
在最开始的时候,文章中介绍了8中最常用的Assert断言方法,这些方法可以适应很大一部分应用场景。
在这一节中,我们会了解到一种新的断言方式,即assertThat。与第一节的其他断言方法相比,assertThat方法具有更好的可读性。在使用时,assertThat需要配合Matcher使用。而且与其他断言方法不同的是,这个assertThat的期望值和实际值的位置是相反的。以下是一些简单的例子:
import org.junit.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public class ApplicationTests {
@Test
public void testThatBothContainsString(){
assertThat("albumen",both(containsString("a")).and(containsString("b")));
}
@Test
public void testThatHasItems(){
assertThat(Arrays.asList("one","two","three"),hasItem("one"));
}
@Test
public void testThatEveryItemContainsString(){
assertThat(Arrays.asList("one","two","threo"),everyItem(containsString("o")));
}
}
@Ignore注释
如果在@Test前添加@Ignore注释,那么这个测试方法将被忽略。@Ignore中可以添加一个String类型的参数,在单元测试后会输出这个String信息。
import org.junit.Ignore;
import org.junit.Test;
public class TestIgnore {
@Ignore("忽略这个单元测试方法!")
@Test
public void ignoreTest(){
}
}
超时测试
可以在@Test中设置timeout参数来进行超时测试,如果该测试方法运行时间超过设置的时间,那么就会抛出TestTimedOutException异常.
例子:
import org.junit.Test;
public class TestTime {
@Test(timeout = 100)
public void testTime(){
for (int i=0;i<100;){
}
}
}
超时规则
使用这种方式可以为类中的所有单元测试方法添加超时测试。
package com.zxq.junit_demo;
import org.junit.Test;
import org.junit.Rule;
import org.junit.rules.Timeout;
public class TestTime {
@Rule
public Timeout globalTimeout = Timeout.seconds(2);
@Test
public void testSleepForTooLong() throws Exception {
for (int i=0;i<100;){}
}
@Test
public void testBlockForever() throws Exception {
for (int i=0;i<100;){}
}
}
参数化测试
运用参数化的方法,批量进行测试,通过构造函数传入,如果有多个参数,需要使用Arrays的二维数组类型。以下是JUint官方文档中的两个例子:
在上面的例子中,Collections中的每个实例都会被测试。
public class Fibonacci {
public static int fibonacci(int n ){
int result = 0;
if(n<= 1){
result = n;
}else {
result= fibonacci(n-1)+fibonacci(n-2);
}
return result;
}
}
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collection;
@RunWith(Parameterized.class)
public class testFibonacci {
@Parameterized.Parameters(name = "{index}: fib({0})={1}")
public static Collection<Object[]> data(){
return Arrays.asList(new Object[][]{
{0,0},{1,1},{2,1},{3,2},{4,3},{5,5},{6,8}
});
}
private int fInput;
private int fExcepted;
public testFibonacci(int input, int excepted){
this.fInput = input;
this.fExcepted = excepted;
}
@Test
public void test(){
assertEquals(fExcepted, Fibonacci.fibonacci(fInput));
}
}
以下的这个例子是通过@parameter直接注入参数,而不是通过构造函数。使用这种形式时,需要把类中的属性类型改为public。
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collection;
@RunWith(Parameterized.class)
public class testFibonacci {
@Parameterized.Parameters(name = "{index}: fib({0})={1}")
public static Collection<Object[]> data(){
return Arrays.asList(new Object[][]{
{0,0},{1,1},{2,1},{3,2},{4,3},{5,5},{6,8}
});
}
@Parameter
public int fInput;
@Parameter(1)
public int fExcepted;
@Test
public void test(){
assertEquals(fExcepted, Fibonacci.fibonacci(fInput));
}
}
以上的两个例子都有两个参数,所以用了二维数组来包装测试数据。如果在只有一个参数的情况下,我们可以用Iterable或者一维的对象数组来实现。
Assumptioins
assume方法与assert方法的不同之处在于,默认的JUnit runner会将失败的assume测试方法视为忽略。
Assumption系列的方法只有assumeTrue和assumeThat两种。与assertThat一样,assumeThat方法也需要配合Matcher使用。
例子:
import org.junit.Test;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assume.assumeThat;
public class AssumptionTest {
private String name="asdf/";
@Test
public void assumname(){
assumeThat(name,not(containsString("/")));
}
}
Rules
在超时测试中,我们在例子中使用到了@Relu注释,这种方式允许开发人员比较方便地添加或者重新定义每个测试方法的行为。通俗地来说就是,我们定义的每一个@Rule都会作用到类中的每个测试方法上。
JUnit4为开发人员提供了几种基础的@Rule方法,包括TemporaryFolder、ExternalResource、ErrorCollector、Verifier、TestWatcher等,有兴趣的同学可以自己去官方文档,链接地址为: link.。
固定测试(Test fixtures)
固定测试的目的是确保有一个公认的、固定的测试环境来保证测试结果是可复现的。
在JUnit4中有以下四种注释:@Before、@After、@BeforeClass和@AfterClass。
在方法前加了@Before(或@After)后,该测试方法会在每个测试单元之前(或之后)会运行。(注:在JUnit5中,这两个注释会被弃用。)
在方法前加了@BeforeClass(或@AfterClass)后,该测试方法会在测试类的所有测试方法之前(或之后)运行。如果我们只对类中的一个测试方法进行测试,那么该测试方法也会运行。
例子:
import org.junit.*;
public class TestFiexture {
@BeforeClass
public static void setUpClass(){
System.out.println("@BeforeClass setUpClass");
}
@AfterClass
public static void tearDownClass() {
System.out.println("@AfterClass tearDownClass");
}
@Before
public void setUp(){
System.out.println("@Before setUp");
}
@After
public void tearDown() {
System.out.println("@After tearDown");
}
@Test
public void test1(){
System.out.println("@Test test1()");
}
@Test
public void test2(){
System.out.println("@Test test2()");
}
}
Categories
通过@Category注释,我们可以给测试类和测试方法打上标签。然后,使用@IncludeCategory我们可以把打上了特定标签的方法加入到一个测试集合中;使用@ExcludeCategory也可以从一个测试集合中移除某些方法。
例子:
public interface FastTests { /* category marker */ }
public interface SlowTests { /* category marker */ }
public class A {
@Test
public void a() {
System.out.println("单元测试a。");
}
@Category(SlowTests.class)
@Test
public void b() {
System.out.println("单元测试b。");
}
}
@Category({SlowTests.class, FastTests.class})
public class B {
@Test
public void c() {
System.out.println("单元测试c。");
}
}
@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@SuiteClasses( { A.class, B.class })
public class SlowTestSuite {
}
@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@ExcludeCategory(FastTests.class)
@SuiteClasses( { A.class, B.class })
public class SlowTestSuite {
}