@Otokaze
2018-11-14T19:59:12.000000Z
字数 27248
阅读 742
Java
JUnit 是什么?
JUnit 是一个简单的开源框架,用于编写和运行可重复的测试。它是 xUnit 单元测试框架体系结构的一个实例。JUnit 功能包括:用于测试预期结果的断言、用于共享通用测试数据的测试夹具、用于运行测试的测试运行器。JUnit 最初由 Erich Gamma 和 Kent Beck 编写。
简单的说,JUnit 是 Java 中的一个单元测试开源库,基本上 JUnit 已经成为了 Java 单元测试的事实标准。目前 JUnit 存在 3 个主流版本,分别是 JUnit 3.x、JUnit 4.x、JUnit 5.x。其中 JUnit 3.x 已经过时,主流是 JUnit 4.x(4.x 版本开始支持 @Test
等注解,之前是要继承对于的测试基类的),而 JUnit 5.x 则是基于 Java 8 开发的测试框架,因此 JUnit 5.x 要求的最低 JDK 版本是 1.8,所以不是那么通用,我们主要学习 JUnit 4.x 就行了,目前 JUint 4.x 的最新 relase 版是 JUnit 4.12
。
什么是单元测试?
在计算机编程中,单元测试(英语:Unit Testing
)又称为 模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。对于面向过程编程,基本单元就是函数;对于面向对象编程,基本单元就是方法。好吧,其实本质来说都是函数而已,无论是 OPP 还是 OOP。而在 JUnit 中我们也是对方法进行测试。
通常来说,程序员每修改一次程序就会进行最少一次单元测试,在编写程序的过程中前后很可能要进行多次单元测试,以证实程序达到软件规格书要求的工作目标,没有程序错误;虽然单元测试不是什么必须的,但也不坏,这牵涉到项目管理的政策决定。
每个理想的测试案例独立于其它案例;为测试时隔离模块,经常使用 stubs、mock 或 fake 等测试马甲程序。单元测试通常由软件开发人员编写(俗称“白盒测试”),用于确保他们所写的代码匹配软件需求和遵循开发目标。它的实施方式可以是非常手动的(透过纸笔),或者是做成构建自动化的一部分。
白盒测试、灰盒测试、黑盒测试
所谓灰白黑,是指测试人员对软件的了解程度,白盒测试就是说测试人员非常了解软件的细节(软件开发人员),而黑盒测试就是说测试人员一点都不了解软件的细节,他只是测试软件提供的 API 是否有问题(普通用户),灰盒测试则处于白盒与黑盒之间,了解软件的大致逻辑,然后进行测试(测试人员)。
举个例子,某个软件出现了问题,那么:
这就是所谓的黑盒、白盒、灰盒测试,懂了吧。
首先,我们知道 JUnit 主要是用来进行白盒测试的,所以在 maven 的依赖配置中,它的 scope 一般是 test
,因为我们只需要在测试环境中用到 JUnit,打包后的 jar 包里面是不需要这个依赖的。所以:
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.zfl9</groupId>
<artifactId>junit-learn</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<exec.mainClass>com.zfl9.Main</exec.mainClass>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
然后项目的目录结构如下:
.
├── pom.xml
└── src
├── main
│ └── java
│ └── com
│ └── zfl9
│ ├── Calculator.java
│ └── Main.java
└── test
└── java
└── com
└── zfl9
└── CalculatorTest.java
按照约定,我们将项目源码放到 src/main/java
目录,将测试源码放到 src/test/java
目录,相应的,项目资源放到 src/main/resources
目录,测试资源放到 src/test/resources
目录,这些文件会自动加入到运行时的 CLASSPATH 中,便于程序使用。注意,maven 会自动将 src/main/java
和 src/test/java
进行合并,所以处于同一个包中的源码类和测试类是会放到一起的,也就是说我们可以直接在 src/test/java
的测试类里面使用同名包下面的其它类(比如上面的 com.zfl9.CalculatorTest
测试类中就可以直接使用 com.zfl9.Calculator
工具类,因为它们实际会被放到同一个目录空间中)。
我们来看看 com.zfl9.Calculator
工具类的源码:
package com.zfl9;
public class Calculator {
private Calculator() {
throw new AssertionError("private constructor");
}
public static int add(int a, int b) {
return a + b;
}
public static int sub(int a, int b) {
return a - b;
}
public static int mul(int a, int b) {
return a * b;
}
public static int div(int a, int b) {
return a / b;
}
}
简单的加减乘除而已,add/sub/mul/div。来看看对应的测试类(测试类命名一般用 Test 结尾):
package com.zfl9;
import org.junit.Test;
import static org.junit.Assert.*;
public class CalculatorTest {
@Test
public void testAdd() {
assertEquals(30, Calculator.add(20, 10));
}
@Test
public void testSub() {
assertEquals(10, Calculator.sub(20, 10));
}
@Test
public void testMul() {
assertEquals(200, Calculator.mul(20, 10));
}
@Test
public void testDiv() {
assertEquals(2, Calculator.div(20, 10));
}
}
最基本的测试大概就是上面这样,首先导入 org.junit.Test
注解,然后导入 org.junit.Assert
工具类的所有静态断言方法(不使用静态导入也行,但是使用静态导入明显是更方便的)。注意到测试类与源码类的命名特点了吗,测试类一般就是在源码类的名字后面加上 Test
后缀,这样的好处是可读性好,别人一看就知道这是用于测试 Calculator 的测试类,当然 junit 并没有规定测试类与源码类的命名规则,但是遵循这个约定是有好处的。
然后就是 @Test
注解了,我们需要将这个注解放到对应的测试方法上面,告诉 JUnit,这是一个用于测试的方法,而对于的测试方法的命名也是有约定的(注意是约定不是规定,所以名字什么的是可以自取的,当然遵守约定总是有好处的),约定是使用 test
开头,使用驼峰命名法来进行命名,如上。每个测试方法都是 public void MethodName()
修饰的,访问性为 public,没有返回值,不接收参数,是一个实例方法。
一般来说,每个测试方法都对应一个源码方法,比如 add 就是 testAdd
,div
就是 testDiv
。然后就是利用 Assert 提供的断言方法进行测试了,比如最常见的就是 assertEquals(expecteds, actuals)
,expected 表示期望结果值,而 actual 表示实际结果值,而 assertEquals 表示,如果实际值不等于预期值,则抛出断言错误,在进行 mvn test
生命周期时就会提示对应的错误信息。如果相等则没有什么输出。
我们来看看默认的执行输出:
$ mvn clean test exec:java -q
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.zfl9.CalculatorTest
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.163 sec
Results :
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
20 + 10 = 30
20 - 10 = 10
20 * 10 = 200
20 / 10 = 2
我们来改一下测试类,故意让它与预期值不相等,看下什么结果:
package com.zfl9;
import org.junit.Test;
import static org.junit.Assert.*;
public class CalculatorTest {
@Test
public void testAdd() {
assertEquals(30, Calculator.add(20, 10));
}
@Test
public void testSub() {
assertEquals(10, Calculator.sub(20, 10));
}
@Test
public void testMul() {
assertEquals(200, Calculator.mul(20, 10));
}
@Test
public void testDiv() {
assertEquals(3, Calculator.div(20, 10));
}
}
我们将 2 改为了 3,故意让它报错,这是运行结果:
$ mvn clean test exec:java -q
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.zfl9.CalculatorTest
Tests run: 4, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.05 sec <<< FAILURE!
testDiv(com.zfl9.CalculatorTest) Time elapsed: 0.004 sec <<< FAILURE!
java.lang.AssertionError: expected:<3> but was:<2>
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotEquals(Assert.java:834)
at org.junit.Assert.assertEquals(Assert.java:645)
at org.junit.Assert.assertEquals(Assert.java:631)
at com.zfl9.CalculatorTest.testDiv(CalculatorTest.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
Results :
Failed tests: testDiv(com.zfl9.CalculatorTest): expected:<3> but was:<2>
Tests run: 4, Failures: 1, Errors: 0, Skipped: 0
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test (default-test) on project junit-learn: There are test failures.
[ERROR]
[ERROR] Please refer to /root/maven-workspace/junit-learn/target/surefire-reports for the individual test results.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
资源对象的创建与销毁
在进行单元测试时,我们通常需要在执行测试方法前创建要用到的对象,然后在测试方法执行完之后又要销毁它们(释放资源),最无脑的做法是在每个测试方法的前后拷贝这些创建和销毁的代码(很多重复代码,不好),稍微明智一点的做法就是声明一个私有数据成员,然后将创建对象的代码单独放到一个成员函数中,销毁对象的代码也是单独放到一个成员函数中,然后在每个测试方法的首尾加入这两个函数的调用就行。
但是 junit 提供了一个更简便的方法,我们不需要在测试方法的首尾加入两个函数的调用,而是直接使用 @Before
和 @After
注解来标注创建函数和销毁函数,junit 在进行测试时会自动在运行测试方法前调用 @Before
方法,测试函数返回后又会自动调用 @After
方法,很方便,例子:
package com.zfl9;
import org.junit.*;
import static org.junit.Assert.*;
public class CalculatorTest {
private Object obj = null;
@Before
public void newObj() {
obj = new Object();
System.out.println("newObj()");
}
@After
public void delObj() {
obj = null;
System.out.println("delObj()");
}
@Test
public void testAdd() {
System.out.println("testAdd()");
assertEquals(30, Calculator.add(20, 10));
}
@Test
public void testSub() {
System.out.println("testSub()");
assertEquals(10, Calculator.sub(20, 10));
}
@Test
public void testMul() {
System.out.println("testMul()");
assertEquals(200, Calculator.mul(20, 10));
}
@Test
public void testDiv() {
System.out.println("testDiv()");
assertEquals(2, Calculator.div(20, 10));
}
}
运行结果:
$ mvn clean test exec:java -q
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.zfl9.CalculatorTest
newObj()
testAdd()
delObj()
newObj()
testDiv()
delObj()
newObj()
testMul()
delObj()
newObj()
testSub()
delObj()
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.091 sec
Results :
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
20 + 10 = 30
20 - 10 = 10
20 * 10 = 200
20 / 10 = 2
Test 注解的两个参数
@Test
注解是 junit 4.x 中最常用的一个注解元素,它有两个可选参数,分别是:
long timeout
:表示该方法必须在指定的时间内执行完成(单位为毫秒),如果不是则表示测试失败Class<? extends Throwable> expected
:表示该方法必须抛出指定异常类或其子类,否则测试失败例子:
package com.zfl9;
import org.junit.*;
import static org.junit.Assert.*;
public class SimpleTest {
@Test(expected=IndexOutOfBoundsException.class)
public void testException() {
throw new IndexOutOfBoundsException();
}
}
运行:
$ mvn clean test exec:java -q
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.zfl9.CalculatorTest
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.045 sec
Running com.zfl9.SimpleTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 sec
Results :
Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
20 + 10 = 30
20 - 10 = 10
20 * 10 = 200
20 / 10 = 2
如果我们把抛出异常的那条语句给注释掉,那么会测试失败:
package com.zfl9;
import org.junit.*;
import static org.junit.Assert.*;
public class SimpleTest {
@Test(expected=IndexOutOfBoundsException.class)
public void testException() {
// throw new IndexOutOfBoundsException();
}
}
运行:
$ mvn clean test exec:java -q
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.zfl9.CalculatorTest
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.046 sec
Running com.zfl9.SimpleTest
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.004 sec <<< FAILURE!
testException(com.zfl9.SimpleTest) Time elapsed: 0.002 sec <<< FAILURE!
java.lang.AssertionError: Expected exception: java.lang.IndexOutOfBoundsException
at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:32)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
Results :
Failed tests: testException(com.zfl9.SimpleTest): Expected exception: java.lang.IndexOutOfBoundsException
Tests run: 5, Failures: 1, Errors: 0, Skipped: 0
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test (default-test) on project junit-learn: There are test failures.
[ERROR]
[ERROR] Please refer to /root/maven-workspace/junit-learn/target/surefire-reports for the individual test results.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
同理,如果我们不传递对应的异常类给 Test 注解,而测试方法可能会抛出异常,则也会导致测试失败:
package com.zfl9;
import org.junit.*;
import static org.junit.Assert.*;
public class SimpleTest {
@Test
public void testException() throws IndexOutOfBoundsException {
throw new IndexOutOfBoundsException();
}
}
运行结果:
$ mvn clean test exec:java -q
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.zfl9.CalculatorTest
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024 sec
Running com.zfl9.SimpleTest
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.001 sec <<< FAILURE!
testException(com.zfl9.SimpleTest) Time elapsed: 0.001 sec <<< ERROR!
java.lang.IndexOutOfBoundsException
at com.zfl9.SimpleTest.testException(SimpleTest.java:9)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
Results :
Tests in error:
testException(com.zfl9.SimpleTest)
Tests run: 5, Failures: 0, Errors: 1, Skipped: 0
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test (default-test) on project junit-learn: There are test failures.
[ERROR]
[ERROR] Please refer to /root/maven-workspace/junit-learn/target/surefire-reports for the individual test results.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
然后我们来试试 timeout 参数,表示该方法必须能够在指定时间内返回:
package com.zfl9;
import org.junit.*;
import static org.junit.Assert.*;
public class SimpleTest {
@Test(timeout = 10)
public void testException() {
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
}
我们设置的是该方法必须在 10ms 之内返回,然而我们故意在里面 sleep 100ms,结果:
$ mvn clean test exec:java -q
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.zfl9.CalculatorTest
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.075 sec
Running com.zfl9.SimpleTest
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.009 sec <<< FAILURE!
testException(com.zfl9.SimpleTest) Time elapsed: 0.009 sec <<< ERROR!
org.junit.runners.model.TestTimedOutException: test timed out after 10 milliseconds
at java.lang.Thread.sleep(Native Method)
at com.zfl9.SimpleTest.testException(SimpleTest.java:9)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.FailOnTimeout$CallableStatement.call(FailOnTimeout.java:298)
at org.junit.internal.runners.statements.FailOnTimeout$CallableStatement.call(FailOnTimeout.java:292)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.lang.Thread.run(Thread.java:748)
Results :
Tests in error:
testException(com.zfl9.SimpleTest): test timed out after 10 milliseconds
Tests run: 5, Failures: 0, Errors: 1, Skipped: 0
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test (default-test) on project junit-learn: There are test failures.
[ERROR]
[ERROR] Please refer to /root/maven-workspace/junit-learn/target/surefire-reports for the individual test results.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
简单的测试类模板
import org.junit.*;
import static org.junit.Assert.*;
public class SampleTest {
private java.util.List emptyList;
@Before
public void setUp() {
emptyList = new java.util.ArrayList();
}
@After
public void tearDown() {
emptyList = null;
}
@Test
public void testSomeBehavior() {
assertEquals("Empty list should have 0 elements", 0, emptyList.size());
}
@Test(expected=IndexOutOfBoundsException.class)
public void testForException() {
Object o = emptyList.get(0);
}
}
一次性的 before 和 after 方法
我们注意到,上面的 @Before
、@After
注解的方法是每次执行测试方法时都会被执行的,而有时候我们可能需要 setUp() 方法和 tearDown() 方法执行一次就行(首尾),在 junit 中是运行我们这样做的,我们只需使用 @BeforeClass
、@AfterClass
注解来标注对应的 public static void 方法就行:
import java.util.*;
import org.junit.*;
import static org.junit.Assert.*;
public class SimpleTest {
private Collection collection;
@BeforeClass
public static void oneTimeSetUp() {
// one-time initialization code
}
@AfterClass
public static void oneTimeTearDown() {
// one-time cleanup code
}
@Before
public void setUp() {
collection = new ArrayList();
}
@After
public void tearDown() {
collection.clear();
}
@Test
public void testEmptyCollection() {
assertTrue(collection.isEmpty());
}
@Test
public void testOneItemCollection() {
collection.add("itemA");
assertEquals(1, collection.size());
}
}
执行顺序如下:
oneTimeSetUp()
setUp()
testEmptyCollection()
tearDown()
setUp()
testOneItemCollection()
tearDown()
oneTimeTearDown()
Javadoc API:https://junit.org/junit4/javadoc/latest/index.html
在第二章节,我们介绍了 junit 最常见的用法,涉及到的东西有:@Test
注解、@Before
/@After
注解、@BeforeClass
/@AfterClass
注解、以及 @Test
注解的 expected、timeout 参数。
我们来简单总结编写测试代码时要注意的几个东西:
@Test
注解进行标注public void
修饰,不能带任何的参数Test
作为类名的后缀(不是必须,但强烈建议)test
作为方法名的前缀(不是必须,但强烈建议)setUp()
,并且使用 @Before
进行标注tearDown()
,并且使用 @After
进行标注@BeforeClass
、@AfterClass
注解@Test
注解所标注的测试方法是可以抛出检查异常和非检查异常的,没有要求@Ignore
进行注解@Ignore
来注解整个测试类我们唯一没有介绍的就是 @Ignore
注解,我们来试一下,注解测试方法:
package com.zfl9;
import org.junit.*;
import static org.junit.Assert.*;
public class SimpleTest {
@Ignore
@Test(timeout = 100)
public void testException() {
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
}
运行结果如下:
$ mvn clean test exec:java -q
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.zfl9.CalculatorTest
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.051 sec
Running com.zfl9.SimpleTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 0.001 sec
Results :
Tests run: 5, Failures: 0, Errors: 0, Skipped: 1
20 + 10 = 30
20 - 10 = 10
20 * 10 = 200
20 / 10 = 2
注意到没,Skipped
的值为 1,表示 Runner 在执行测试时跳过了一个测试方法。
当然我们也可以直接使用 @Ignore
来注解整个类,这样整个测试类就不会运行了:
package com.zfl9;
import org.junit.*;
import static org.junit.Assert.*;
@Ignore
public class SimpleTest {
@Test(timeout = 100)
public void testException() {
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
}
运行结果如下:
$ mvn clean test exec:java -q
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.zfl9.CalculatorTest
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.103 sec
Running com.zfl9.SimpleTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 0.002 sec
Results :
Tests run: 5, Failures: 0, Errors: 0, Skipped: 1
20 + 10 = 30
20 - 10 = 10
20 * 10 = 200
20 / 10 = 2
Assert 工具类的常见方法
JUnit 提供了一些辅助函数,他们用来帮助我们确定被测试的方法是否按照预期正常执行,这些辅助函数我们称之为 断言(Assertion)。JUnit4 所有的断言都在 org.junit.Assert
类中,Assert 类包含了一组 静态 的测试方法,用于验证 期望值 expected 与 实际值 actual 之间的逻辑关系是否正确,如果不符合我们的预期则表示测试未通过。Assert 实用类提供的方法都是静态方法,即 public static void
,常见的有:
/**
* @param message 错误信息,字符串
* @param expected 期望值,任意类型(重载)
* @param actual 实际值,任意类型(重载)
* 如果期望值与实际值不相等则表示测试失败,JUnit 会报错
*/
assertEquals([message, ]expected, actual) // 是否相等 (标量)
assertNotEquals([message, ]unexpected, actual) // 是否不等 (标量)
assertArrayEquals([message, ]expecteds, actuals) // 是否相等 (数组)
assertSame([message, ]expected, actual) // 引用的内存地址相同
assertNotSame([message, ]expected, actual) // 引用的内存地址不同
assertNull([message, ]object) // 断言 object 为 null
assertNotNull([message, ]object) // 断言 object 不为 null
assertTrue([message, ]condition) // 断言 condition 为真
assertFalse([message, ]condition) // 断言 condition 为假
你可能会对 assertEquals()
、assertSame()
方法产生疑惑,其实很好分辨,一个是通过调用 equals()
方法进行判断是否“相等”,一个是通过调用 ==
运算符进行判断是否“相等”,后者判断的其实是对象的内存地址是否相同,这和前者的判断是不一样的,这样说估计你就能记住它们的区别了。
除了上面这些方法之外,还有两个 fail() 方法,用来手动导致测试失败:
public static void fail() // 没有消息
public static void fail(String message)
什么是"手动导致测试失败"?很简单,比如:
package com.zfl9;
import org.junit.*;
import static org.junit.Assert.*;
public class SimpleTest {
@Test
public void testFail() {
fail("test failed");
}
}
运行结果:
$ mvn clean test exec:java -q
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.zfl9.CalculatorTest
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.063 sec
Running com.zfl9.SimpleTest
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.001 sec <<< FAILURE!
testFail(com.zfl9.SimpleTest) Time elapsed: 0.001 sec <<< FAILURE!
java.lang.AssertionError: test failed
at org.junit.Assert.fail(Assert.java:88)
at com.zfl9.SimpleTest.testFail(SimpleTest.java:9)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
Results :
Failed tests: testFail(com.zfl9.SimpleTest): test failed
Tests run: 5, Failures: 1, Errors: 0, Skipped: 0
思考:测试类是如何运行的?
大家刚开始使用 JUnit 的时候,可能会跟我一样有一个疑问,JUnit 没有 main() 方法,那它是怎么开始执行的呢?众所周知,不管是什么程序,都必须有一个程序执行入口,而这个入口通常是 main() 方法。显然,JUnit 能直接执行某个测试方法,那么它肯定会有一个程序执行入口。没错,其实在 org.junit.runner
包下,有个 JUnitCore.java
类,这个类有一个标准的 main() 方法,这个其实就是 JUnit 程序的执行入口,其代码如下:
public static void main(String... args) {
Result result = new JUnitCore().runMain(new RealSystem(), args);
System.exit(result.wasSuccessful() ? 0 : 1);
}
通过分析里面的runMain()方法,可以找到最终的执行代码如下:
public Result run(Runner runner) {
Result result = new Result();
RunListener listener = result.createListener();
notifier.addFirstListener(listener);
try {
notifier.fireTestRunStarted(runner.getDescription());
runner.run(notifier);
notifier.fireTestRunFinished(result);
} finally {
removeListener(listener);
}
return result;
}
可以看到,所有的单元测试方法都是通过 Runner 来执行的。Runner 只是一个抽象类,它是用来跑测试用例并通知结果的,JUnit 提供了很多 Runner 的实现类,可以根据不同的情况选择不同的 test runner。
当然,我们一般都是使用 maven 或 IDE 来执行测试,你也许注意到了,我们在之前的使用中,并没有手动运行什么 Runner,而是直接使用 mvn test
来跑单元测试,其实 maven 自动运行了对应的 Runner 而已啦。