读源码-JUnit4使用
JUnit4简介
之前梳理了JUnit3,此篇将梳理JUnit4。
JUnit4主要引入了注解。我们依然使用之前的例子来进行测试
场景
假设,我们有一个Person类
- 包含两个方法say和sayHi
- 两个方法都接收一个String类型的参数
- say返回"Hello,"+arg
- sayHi返回"Hi,"+arg
代码如下:
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"));
}
}
- 在JUnit4中,测试类不再需要继承TestCase和Assert了
- 测试方法也不需要以test开头,只需要添加@Test注解
- assertEquals用来比较,方法调用结果是否与期望相同。其通过静态导入来引入。
- setUp()和tearDown()方法名也不再必须,可以使用任意名字。只需要分别添加@Before和@After注解即可
运行测试
- 如果使用IDE则可以直接在测试类上,右击来运行测试,如果测试成功会出现绿条
- 通过mvn test来运行测试,输出的是文字测试结果
- 通过java org.junit.runner.JUnitCore org.ivan.PersonTest来运行
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,该如何做?
- 对于IDE,直接在测试包上右击,运行测试即可
- 对于Maven,依然使用mvn test命令
- 对于命令行来说,则需要新建一个类
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 {
}
- 完全使用注解来进行处理
- 需要执行的测试类添加到SuiteClasses内即可
- 命令行执行时,通过java org.junit.runner.JUnitCore org.ivan.TestAll来运行
BeforeClass,AfterClass
上面提到的setUp()和tearDown()方法是在每次测试方法前后被调用,假如你希望:
- 运行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");
}
...
}
- 注意这里的方法是static的
重复测试
假设需要对某个测试进行多次测试,该怎么办?
JUnit3中提供了RepeatedTest。很遗憾,JUnit4没有提供类似的功能。
不过我们可以通过扩展JUnit4提供的Rule来自己实现。我们先来看下Rule.
Rule
此部分内容取自此博客
Rule是JUnit4中的新特性,它让我们可以扩展JUnit的功能,灵活地改变测试方法的行为。JUnit中用@Rule和@ClassRule两个注解来实现Rule扩展,这两个注解需要放在实现了TestRule接口的成员变量(@Rule)或者静态变量(@ClassRule)上。@Rule和@ClassRule的不同点是,@Rule是方法级别的,每个测试方法执行时都会调用被注解的Rule,而@ClassRule是类级别的,在执行一个测试类的时候只会调用一次被注解的Rule
内置Rule
- TemporaryFolder Rule:使用这个Rule可以创建一些临时目录或者文件,在一个测试方法结束之后,系统会自动清空他们。
@Rule
public TemporaryFolder tempFolder = new TemporaryFolder();
@Test
public void testTempFolderRule() throws IOException {
tempFolder.newFile("test.txt");
tempFolder.newFolder("test");
}
- ExternalResource Rule:ExternalResource 是TemporaryFolder的父类,主要用于在测试之前创建资源,并在测试完成后销毁。
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());
}
- ErrorCollector Rule:ErrorCollector允许我们收集多个错误,并在测试执行完后一次过显示出来
@Rule
public ErrorCollector errorCollector = new ErrorCollector();
@Test
public void testErrorCollector() {
errorCollector.addError(new Exception("Test Fail 1"));
errorCollector.addError(new Throwable("fff"));
}
- Verifier Rule:Verifier是ErrorCollector的父类,可以在测试执行完成之后做一些校验,以验证测试结果是不是正确
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";
}
- TestWatcher Rule:TestWatcher 定义了五个触发点,分别是测试成功,测试失败,测试开始,测试完成,测试跳过,能让我们在每个触发点执行自定义的逻辑。
@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");
}
- TestName Rule:TestName能让我们在测试中获取目前测试方法的名字。
@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");
}
- 如上代码表示执行10次
UML
与JUnit3相比,没有了相应的继承关系,由注解进行了处理 !