@Otokaze
2018-11-14T11:59:12.000000Z
字数 27248
阅读 865
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 {@Testpublic void testAdd() {assertEquals(30, Calculator.add(20, 10));}@Testpublic void testSub() {assertEquals(10, Calculator.sub(20, 10));}@Testpublic void testMul() {assertEquals(200, Calculator.mul(20, 10));}@Testpublic 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.CalculatorTestTests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.163 secResults :Tests run: 4, Failures: 0, Errors: 0, Skipped: 020 + 10 = 3020 - 10 = 1020 * 10 = 20020 / 10 = 2
我们来改一下测试类,故意让它与预期值不相等,看下什么结果:
package com.zfl9;import org.junit.Test;import static org.junit.Assert.*;public class CalculatorTest {@Testpublic void testAdd() {assertEquals(30, Calculator.add(20, 10));}@Testpublic void testSub() {assertEquals(10, Calculator.sub(20, 10));}@Testpublic void testMul() {assertEquals(200, Calculator.mul(20, 10));}@Testpublic void testDiv() {assertEquals(3, Calculator.div(20, 10));}}
我们将 2 改为了 3,故意让它报错,这是运行结果:
$ mvn clean test exec:java -q-------------------------------------------------------T E S T S-------------------------------------------------------Running com.zfl9.CalculatorTestTests 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;@Beforepublic void newObj() {obj = new Object();System.out.println("newObj()");}@Afterpublic void delObj() {obj = null;System.out.println("delObj()");}@Testpublic void testAdd() {System.out.println("testAdd()");assertEquals(30, Calculator.add(20, 10));}@Testpublic void testSub() {System.out.println("testSub()");assertEquals(10, Calculator.sub(20, 10));}@Testpublic void testMul() {System.out.println("testMul()");assertEquals(200, Calculator.mul(20, 10));}@Testpublic 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.CalculatorTestnewObj()testAdd()delObj()newObj()testDiv()delObj()newObj()testMul()delObj()newObj()testSub()delObj()Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.091 secResults :Tests run: 4, Failures: 0, Errors: 0, Skipped: 020 + 10 = 3020 - 10 = 1020 * 10 = 20020 / 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.CalculatorTestTests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.045 secRunning com.zfl9.SimpleTestTests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 secResults :Tests run: 5, Failures: 0, Errors: 0, Skipped: 020 + 10 = 3020 - 10 = 1020 * 10 = 20020 / 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.CalculatorTestTests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.046 secRunning com.zfl9.SimpleTestTests 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.IndexOutOfBoundsExceptionat 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.IndexOutOfBoundsExceptionTests 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 {@Testpublic void testException() throws IndexOutOfBoundsException {throw new IndexOutOfBoundsException();}}
运行结果:
$ mvn clean test exec:java -q-------------------------------------------------------T E S T S-------------------------------------------------------Running com.zfl9.CalculatorTestTests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024 secRunning com.zfl9.SimpleTestTests 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.IndexOutOfBoundsExceptionat 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.CalculatorTestTests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.075 secRunning com.zfl9.SimpleTestTests 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 millisecondsat 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 millisecondsTests 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;@Beforepublic void setUp() {emptyList = new java.util.ArrayList();}@Afterpublic void tearDown() {emptyList = null;}@Testpublic 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;@BeforeClasspublic static void oneTimeSetUp() {// one-time initialization code}@AfterClasspublic static void oneTimeTearDown() {// one-time cleanup code}@Beforepublic void setUp() {collection = new ArrayList();}@Afterpublic void tearDown() {collection.clear();}@Testpublic void testEmptyCollection() {assertTrue(collection.isEmpty());}@Testpublic 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.CalculatorTestTests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.051 secRunning com.zfl9.SimpleTestTests run: 1, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 0.001 secResults :Tests run: 5, Failures: 0, Errors: 0, Skipped: 120 + 10 = 3020 - 10 = 1020 * 10 = 20020 / 10 = 2
注意到没,Skipped 的值为 1,表示 Runner 在执行测试时跳过了一个测试方法。
当然我们也可以直接使用 @Ignore 来注解整个类,这样整个测试类就不会运行了:
package com.zfl9;import org.junit.*;import static org.junit.Assert.*;@Ignorepublic 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.CalculatorTestTests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.103 secRunning com.zfl9.SimpleTestTests run: 1, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 0.002 secResults :Tests run: 5, Failures: 0, Errors: 0, Skipped: 120 + 10 = 3020 - 10 = 1020 * 10 = 20020 / 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 为 nullassertNotNull([message, ]object) // 断言 object 不为 nullassertTrue([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 {@Testpublic void testFail() {fail("test failed");}}
运行结果:
$ mvn clean test exec:java -q-------------------------------------------------------T E S T S-------------------------------------------------------Running com.zfl9.CalculatorTestTests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.063 secRunning com.zfl9.SimpleTestTests 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 failedat 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 failedTests 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 而已啦。