ivaneye.com

读源码-JUnit4使用

JUnit4简介

之前梳理了JUnit3,此篇将梳理JUnit4。

JUnit4主要引入了注解。我们依然使用之前的例子来进行测试

场景

假设,我们有一个Person类

代码如下:

package org.ivan;
public class Person {
    public String say(String name) {
        return "Hello," + name;
    }
    public String sayHi(String name) {
        return "Hi," + name;
    }
}

在JUnit4中如何进行测试呢?

引入JUnit

首先还是引入JUnit4

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
  <scope>test</scope>
</dependency>

编写测试类

package org.ivan;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class PersonTest {
    private Person person;
    @Before
    public void setUp() throws Exception {
        System.out.println("setUp invoke");
        person = new Person();
    }
    @After
    public void tearDown() throws Exception {
        System.out.println("tearDown invoke");
    }
    @Test
    public void testSay() throws Exception {
        assertEquals("Hello,Ivan", person.say("Ivan"));
    }
    @Test
    public void testSayHi() throws Exception {
        assertEquals("Hi,Ivan", person.sayHi("Ivan"));
    }
}

运行测试

Intellij IDEA运行结果显示:

Maven运行结果:

Running org.ivan.PersonTest
setUp invoke
tearDown invoke
setUp invoke
tearDown invoke
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 sec

命令行输出结果:

JUnit version 4.12
.setUp invoke
tearDown invoke
.setUp invoke
tearDown invoke
Time: 0.014
OK (2 tests)

运行多个测试类

假设我们新增一个类Animal:

package org.ivan;
public class Animal {
    public String say(String name) {
        return "momo," + name;
    }
    public String sayHi(String name) {
        return "gigi," + name;
    }
}

以及对应的测试类:

package org.ivan;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class AnimalTest {
    private Animal animal;
    @Before
    public void setUp() throws Exception {
        System.out.println("setUp invoke");
        animal = new Animal();
    }
    @After
    public void tearDown() throws Exception {
        System.out.println("tearDown invoke");
    }
    @Test
    public void testSay() throws Exception {
        assertEquals("momo,Ivan", animal.say("Ivan"));
    }
    @Test
    public void testSayHi() throws Exception {
        assertEquals("gigi,Ivan", animal.sayHi("Ivan"));
    }
}

如果我们想同时运行PersonTest和AnimalTest,该如何做?

package org.ivan;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({
        PersonTest.class,
        AnimalTest.class
})
public class TestAll {
}

BeforeClass,AfterClass

上面提到的setUp()和tearDown()方法是在每次测试方法前后被调用,假如你希望:

该怎么办?

JUnit3提供了TestSetup,JUnit4提供了@BeforeClass和@AfterClass注解

public class PersonTest {
    ...
    @BeforeClass
    public static void beforeClass() throws Exception {
        System.out.println("BeforeClass");
    }
    @AfterClass
    public static void afterClass() throws Exception {
        System.out.println("AfterClass");
    }
    ...
}

重复测试

假设需要对某个测试进行多次测试,该怎么办?

JUnit3中提供了RepeatedTest。很遗憾,JUnit4没有提供类似的功能。

不过我们可以通过扩展JUnit4提供的Rule来自己实现。我们先来看下Rule.

Rule

此部分内容取自此博客

Rule是JUnit4中的新特性,它让我们可以扩展JUnit的功能,灵活地改变测试方法的行为。JUnit中用@Rule和@ClassRule两个注解来实现Rule扩展,这两个注解需要放在实现了TestRule接口的成员变量(@Rule)或者静态变量(@ClassRule)上。@Rule和@ClassRule的不同点是,@Rule是方法级别的,每个测试方法执行时都会调用被注解的Rule,而@ClassRule是类级别的,在执行一个测试类的时候只会调用一次被注解的Rule

内置Rule

@Rule
public TemporaryFolder tempFolder = new TemporaryFolder();
@Test
public void testTempFolderRule() throws IOException {
   tempFolder.newFile("test.txt");
   tempFolder.newFolder("test");
}
File tempFile;
@Rule
public ExternalResource extResource = new ExternalResource() {
    //每个测试执行之前都会调用该方法创建一个临时文件
    @Override
    protected void before() throws Throwable {
        tempFile = File.createTempFile("test", ".txt");
    }
    //每个测试执行之后都会调用该方法删除临时文件
    @Override
    protected void after() {
        tempFile.delete();
    }
};
@Test
public void testExtResource() throws IOException {
    System.out.println(tempFile.getCanonicalPath());
}
@Rule
public ErrorCollector errorCollector = new ErrorCollector();
@Test
public void testErrorCollector() {
    errorCollector.addError(new Exception("Test Fail 1"));
    errorCollector.addError(new Throwable("fff"));
}
String result;
@Rule
public Verifier verifier = new Verifier() {
    //当测试执行完之后会调用verify方法验证结果,抛出异常表明测试失败
    @Override
    protected void verify() throws Throwable {
        if (!"Success".equals(result)) {
            throw new Exception("Test Fail.");
        }
    }
};
@Test
public void testVerifier() {
    result = "Fail";
}
@Rule
public TestWatcher testWatcher = new TestWatcher() {
    @Override
    protected void succeeded(Description description) {
        System.out.println(description.getDisplayName() + " Succeed");
    }
    @Override
    protected void failed(Throwable e, Description description) {
        System.out.println(description.getDisplayName() + " Fail");
    }
    @Override
    protected void skipped(AssumptionViolatedException e, Description description) {
        System.out.println(description.getDisplayName() + " Skipped");
    }
    @Override
    protected void starting(Description description) {
        System.out.println(description.getDisplayName() + " Started");
    }
    @Override
    protected void finished(Description description) {
        System.out.println(description.getDisplayName() + " finished");
    }
};
@Test
public void testTestWatcher() {
    /*
        测试执行后会有以下输出:
        testTestWatcher(org.haibin369.test.RulesTest) Started
        Test invoked
        testTestWatcher(org.haibin369.test.RulesTest) Succeed
        testTestWatcher(org.haibin369.test.RulesTest) finished
     */
    System.out.println("Test invoked");
}
@Rule
public TestName testName = new TestName();
@Test
public void testTestName() {
    //打印出测试方法的名字testTestName
    System.out.println(testName.getMethodName());
}

使用Rule来实现重复执行测试

首先实现一个Rule,这里叫RepeatRule

package org.ivan;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class RepeatRule implements TestRule {
    @Retention(RetentionPolicy.RUNTIME)
    @Target({
            java.lang.annotation.ElementType.METHOD
    })
    public @interface Repeat {
        public abstract int times();
    }
    private static class RepeatStatement extends Statement {
        private final int times;
        private final Statement statement;
        private RepeatStatement(int times, Statement statement) {
            this.times = times;
            this.statement = statement;
        }
        @Override
        public void evaluate() throws Throwable {
            for (int i = 0; i < times; i++) {
                statement.evaluate();
            }
        }
    }
    public Statement apply(
            Statement statement, Description description) {
        Statement result = statement;
        Repeat repeat = description.getAnnotation(Repeat.class);
        if (repeat != null) {
            int times = repeat.times();
            result = new RepeatStatement(times, statement);
        }
        return result;
    }
}

编写测试

@Rule
public RepeatRule repeatRule = new RepeatRule();
@Test
@RepeatRule.Repeat( times = 10 )
public void testTempFolderRule() throws IOException {
  System.out.println("Times");
}

UML

与JUnit3相比,没有了相应的继承关系,由注解进行了处理 !