Junit单元测试

来源:互联网 发布:淘宝好看的女鞋店铺 编辑:程序博客网 时间:2024/04/30 15:36

Junit是干什么的我在这里就不讲解了。直接开始正题。

常用的注解

Junit中的注解很多,我们首先来看一下最常用的一些注解

  • @Test:把一个方法标记未测试方法
    • excepted:用来测试异常的,方法抛出该异常说明测试成功
    • timeout:用来测试性能的,在规定的时间内完成,说明成功。注意单位是毫秒
  • @Before:每个测试方法执行前自动调用一次
  • @After:每个测试方法执行完自动调用一次
  • @BeforeClass:在类被加载到内存中后执行,这意味着方法需要时static的,并且在构造函数之前执行
  • @AfterClass:在类被销毁之前执行,所以需要时static的
  • @Ignore:暂不执行该测试方法

下面我们将使用一个简单的例子,将这些注解贯穿起来,一起进行讲解:

public class JunitTest {    private int count = 10;    public JunitTest() {        System.out.println("构造函数运行");    }    @BeforeClass    public static void beforeClass(){        System.out.println("BeforeClass...");    }    @AfterClass    public static void afterClass(){        System.out.println("AfterClass...");    }    @Before    public void setUp() throws Exception {        System.out.println("Before Method run...");    }    @After    public void tearDown() throws Exception {        System.out.println("After Method run...");    }    @Test    public void test1() throws Exception {        count++;        System.out.println("Test1 run,count=" + count);    }    @Test(expected = RuntimeException.class)    public void test2() throws Exception {        count++;        System.out.println("Test2 run, count=" + count);        throw new RuntimeException("Exception");    }    @Test(timeout = 500)    public void test3() throws Exception {        count++;        System.out.println("Test3 run, count=" + count);        Thread.sleep(300);    }    @Ignore    public void test4() throws Exception{        count++;        System.out.println("Test4 run, count=" + count);    }}

程序的运行结果如下:

BeforeClass...构造函数运行Before Method run...Test1 run,count=11After Method run...构造函数运行Before Method run...Test2 run, count=11After Method run...构造函数运行Before Method run...Test3 run, count=11After Method run...AfterClass...

上述程序中,包含了四个测试用例,但是有一个标记未@Ignore,因此只运行了三个。所以我们可以将上述的运行结果拆分为三个部分进行分析,得出如下的结论:

  1. BeforeClass在构造函数之前运行,需要是static的。AfterClass在程序的最后运行
  2. 在运行每个测试用例的时候,都重新构造了类的一个实例,Junit这样做保证了每个测试用例的相互独立性。这也解释了为什么构造函数运行了三次,以及count值始终为11.
  3. Before和After标记的方法在每个测试用例的前后执行
  4. 测试用例的运行顺序,其实是没有规定的,也就是test1()方法不一定非得在test2()之前运行。这里只是因为test1(),test2(),test3()他们的方法名恰巧是按字母序递增的,所以才会这样。如果,我们将测试方法test3()名字改为test0(),那么我们将看到test0()将在test1()前面运行。关于测试用例的运行顺序,稍后我将给大家介绍。
  5. expected属性用来对异常进行测试,如果抛出该异常,那么测试用例通过;timeout用来对性能进行测试,指定一个上限时间,如果超过这个时间,测试方法还没有运行结束,则直接中断该方法。如果该方法正在执行不响应中断的操作,则方法永远不能结束。

Suite

Suite可以翻译为套件,意味着将多个单元测试类一起执行。在Junit中,需要使用@RunWith和@SuiteClasses注解

public class Test1 {    @Test    public void test1(){        System.out.println("test1");    }}public class Test2 {    @Test    public void test1(){        System.out.println("test2");    }}public class Test3 {    @Test    public void test1(){        System.out.println("test3");    }}@RunWith(Suite.class)@Suite.SuiteClasses({        Test1.class,        Test2.class,        Test3.class})public class FeatureTestSuite {    //do nothing, just suite them all}

运行FeatureTestSuite后,测试类将按照在@SuiteClasses中定义的顺序依次执行,结果为

test1test2test3

Parameters

Parameters,参数化测试,允许开发人员使用不同的参数多次运行同一个测试用例。需要@RunWith和@parameters注解配合使用

//计算Fibonacci数public class Fibonacci {    public static int compute(int n ){        int result = 0;        if(n <= 1){            result = n;        }else{            result = compute(n-1) + compute(n-2);        }        return result;    }}@RunWith(Parameterized.class)public class FibonacciTest {    @Parameterized.Parameters    public static Collection<Object[]> data(){        return Arrays.asList(new Object[][]{                {0, 0},{1, 1},{2, 1},{3, 2},{4, 3}        });    }    private int fInput;    private int fExpected;    public FibonacciTest(int fInput, int fExpected) {        this.fInput = fInput;        this.fExpected = fExpected;    }    @Test    public void compute() throws Exception {        assertEquals(fExpected, Fibonacci.compute(fInput));    }}

上述程序将分别使用{0, 0},{1, 1},{2, 1},{3, 2},{4, 3}这五组样例作为输入,运行compute()程序
这里写图片描述

我们还可以通过@Parameters的name属性,来为每个测试用例命名,例如,我们在data()上这样添加name属性

@Parameterized.Parameters(name = "{index}: fib({0})={1}")

那么,测试类的运行结果为:
这里写图片描述

Exception Test

前面我们介绍过,通过在@Test上添加excepted属性,可以用来对异常进行测试。但是这样只能测试异常的类型,不能够对异常中的message进行测试。如果我们对异常中抛出的message感兴趣,那么我们可以尝试使用ExpectedException规则(Rule)。

public class ExceptionRule {    @Test    public void showException() throws Exception{        List<Object> list = new ArrayList<>();        list.get(0);//just for test    }}

运行上述这段代码将抛出IndexOutOfBoundsException异常,message为:Index: 0, Size: 0,后面跟上方法的调用堆栈。为了对message进行测试,我们添加一个Rule和一个测试类,并将原来的测试方法设置expected使其通过。添加后的完整代码为:

public class ExceptionRule {    @Test(expected = IndexOutOfBoundsException.class)    public void showException() throws Exception{        List<Object> list = new ArrayList<>();        list.get(0);//just for test    }    @Rule    public ExpectedException thrown = ExpectedException.none();    @Test    public void showTestExceptionMessage() throws Exception {        List<Object> list = new ArrayList<>();        thrown.expect(IndexOutOfBoundsException.class); //相当于expected        thrown.expectMessage("Index: 0, Size: 0"); //期望消息中包含Index: 0, Size: 0        list.get(0);    }}

再次运行上面的测试,发现测试方法均能够通过。上面的程序只是演示了ExpectedException的一种最简单的用法,通过和hamcrest包中的Matcher相结合,我们还能够对消息进行很多有趣的操作。

Timeout

前面已经介绍过,我们可以在@Test上添加timeout属性来对方法进行性能测试,这样的确能够解决问题。考虑这样一种情况:我的测试类中含有一百个测试方法,我需要测试这一百个方法每一个的运行时间均小于一个阈值(threshold),这时我们应该怎么做呢?是为每一个Test上均添加一个timeout属性吗?No,我们可以使用Timeout Rule来解决问题

public class TestTimeout {    private final CountDownLatch latch = new CountDownLatch(1);    @Rule    public Timeout globalTimeout = Timeout.millis(500);    @Test    public void testSleepForTooShort() throws Exception{        TimeUnit.MILLISECONDS.sleep(100);    }    @Test    public void testSleepForTooLong() throws Exception {        TimeUnit.SECONDS.sleep(1);    }    @Test    public void testBlockForever() throws Exception {        latch.await(); //waitforever until timeout    }}

上面这个测试类中,我们使用CountDownlatch的await操作来模拟永久等待(由于没有countDown操作)。运行上面的程序,只有testSleepForTooShort()运行通过,其他两个方法均抛出异常。这说明Timeout Rule对所有的Test方法均有效,并且超时的时候会通过中断的方式打断Test方法的运行

org.junit.runners.model.TestTimedOutException: test timed out after 500 milliseconds

Test Execution Order(测试用例的运行顺序)

Junit框架在设计的时候并没有考虑测试方法应该按照什么样的顺序执行,默认是按照Java反射API返回的顺序逐个调用。诚然,良好的测试类是不应该预设测试方法的执行顺序的,但是有些场景下,我们确实有必要使得测试方法按照一定的顺序去执行。

从Junit4.11开始,测试方法将按照MethodSorters.DEFAULT排序方法进行运行。为了改变测试方法的运行顺序,我们可以使用@FixMethodOrder注解来进行指定

  • @FixMethodOrder(MethodSorters.JVM):让JVM来决定测试方法的运行顺序。这种排序模式下,每一次的运行顺序有可能大不相同
  • @FixMethodOrder(MethodSorters.NAME_ASCENDING):按照字典升序对测试方法名进行排序后执行
@FixMethodOrder(MethodSorters.NAME_ASCENDING)public class TestMethodOrder {    @Test    public void testA() throws Exception {        System.out.println("first");    }    @Test    public void testB() throws Exception {        System.out.println("second");    }    @Test    public void testC() throws Exception {        System.out.println("third");    }}

测试方法将按照testA->testB->testC的顺序依次运行

Assert

Junit中提供了很多断言(Assert),其中大部分的断言都很好理解,我们需要注意的是assertThat断言。首先我们来看下assertThat长什么样:

<T> void assertThat(T actual, Matcher<? super T> matcher)<T> void assertThat(String reason, T actual,            Matcher<? super T> matcher)

assertThat为我们提供了一种全新的判断方式,能够判断给定的target(参数actual),是否满足我们自定义的规则组合(Matcher)。

Matcher是什么呢?Mather是一套匹配符,由JUnit4.4中引入的Hamcrest框架提供。这些匹配符更接近自然语言,可读性高,更加灵活;通过对Matcher的灵活组合,可以实现我们想要的任何测试。

import org.junit.Test;import static org.hamcrest.core.AnyOf.*;import static org.hamcrest.core.Is.*;import static org.hamcrest.core.IsEqual.*;import static org.hamcrest.core.StringContains.*;import static org.hamcrest.core.StringEndsWith.*;import static org.hamcrest.core.StringStartsWith.*;import static org.hamcrest.core.AllOf.*;import static org.junit.Assert.*;public class AssertThatTest {    @Test    public void test() throws Exception{        assertThat(evaluate(), allOf(startsWith("H"), endsWith("d"), containsString("World")));        assertThat(evaluate(), anyOf(isA(String.class), equalTo(100)));    }    //simulation biz method    private String evaluate() {        return "Hello World";    }}

上述程序中,我们用一个evaluate方法来模拟需要测试的业务方法,这个方法返回固定的字符串。在Test方法中,我们使用hamcrest框架提供的Matcher制定了两个规则,一个规则是返回值必须以字母H开头,以字母d结尾并且包含字符串World;另一个规则是返回值要么是一个字符串(String),要么等于数字100。我们使用assertThat来进行判断。很显然,我们的返回值是同时满足这两条规则的,因此该测试方法能够通过

结尾

Junit中还有一些东西没有介绍到,例如Theories、Categories,这里简要的提一下:Theories是一种更具灵活和表现力的断言,能够在特定的场景下捕获预期的行为,例如一个Test方法需要两个int参数进行多组测试,我们可以这样做

@Testpublic void multiplyTest(        @TestedOn(ints ={0,5,10}) int first,        @TestedOn(ints = {0, 1, 2})  int second        ){    //....}

Categories用来给Test方法进行分类,这样我们可以通过指定需要运行的Categories,来部分运行单元测试。例如,我们通过@IncludeCategory(SuperClass.class)指明需要运行的分类为SuperClass.class,那么任何标记有@Category({SubClass.class}) 的测试方法都将运行。@Category注解可以标在任何类和接口上面。

1 0
原创粉丝点击