[关闭]
@Otokaze 2018-11-14T11:59:12.000000Z 字数 27248 阅读 541

JUnit 笔记

Java

JUnit 简介

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 使用

首先,我们知道 JUnit 主要是用来进行白盒测试的,所以在 maven 的依赖配置中,它的 scope 一般是 test,因为我们只需要在测试环境中用到 JUnit,打包后的 jar 包里面是不需要这个依赖的。所以:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project>
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>com.zfl9</groupId>
  5. <artifactId>junit-learn</artifactId>
  6. <version>1.0.0</version>
  7. <packaging>jar</packaging>
  8. <properties>
  9. <exec.mainClass>com.zfl9.Main</exec.mainClass>
  10. <maven.compiler.source>1.8</maven.compiler.source>
  11. <maven.compiler.target>1.8</maven.compiler.target>
  12. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  13. </properties>
  14. <dependencies>
  15. <dependency>
  16. <groupId>junit</groupId>
  17. <artifactId>junit</artifactId>
  18. <version>4.12</version>
  19. <scope>test</scope>
  20. </dependency>
  21. </dependencies>
  22. </project>

然后项目的目录结构如下:

  1. .
  2. ├── pom.xml
  3. └── src
  4. ├── main
  5.    └── java
  6.    └── com
  7.    └── zfl9
  8.    ├── Calculator.java
  9.    └── Main.java
  10. └── test
  11. └── java
  12. └── com
  13. └── zfl9
  14. └── CalculatorTest.java

按照约定,我们将项目源码放到 src/main/java 目录,将测试源码放到 src/test/java 目录,相应的,项目资源放到 src/main/resources 目录,测试资源放到 src/test/resources 目录,这些文件会自动加入到运行时的 CLASSPATH 中,便于程序使用。注意,maven 会自动将 src/main/javasrc/test/java 进行合并,所以处于同一个包中的源码类和测试类是会放到一起的,也就是说我们可以直接在 src/test/java 的测试类里面使用同名包下面的其它类(比如上面的 com.zfl9.CalculatorTest 测试类中就可以直接使用 com.zfl9.Calculator 工具类,因为它们实际会被放到同一个目录空间中)。

我们来看看 com.zfl9.Calculator 工具类的源码:

  1. package com.zfl9;
  2. public class Calculator {
  3. private Calculator() {
  4. throw new AssertionError("private constructor");
  5. }
  6. public static int add(int a, int b) {
  7. return a + b;
  8. }
  9. public static int sub(int a, int b) {
  10. return a - b;
  11. }
  12. public static int mul(int a, int b) {
  13. return a * b;
  14. }
  15. public static int div(int a, int b) {
  16. return a / b;
  17. }
  18. }

简单的加减乘除而已,add/sub/mul/div。来看看对应的测试类(测试类命名一般用 Test 结尾):

  1. package com.zfl9;
  2. import org.junit.Test;
  3. import static org.junit.Assert.*;
  4. public class CalculatorTest {
  5. @Test
  6. public void testAdd() {
  7. assertEquals(30, Calculator.add(20, 10));
  8. }
  9. @Test
  10. public void testSub() {
  11. assertEquals(10, Calculator.sub(20, 10));
  12. }
  13. @Test
  14. public void testMul() {
  15. assertEquals(200, Calculator.mul(20, 10));
  16. }
  17. @Test
  18. public void testDiv() {
  19. assertEquals(2, Calculator.div(20, 10));
  20. }
  21. }

最基本的测试大概就是上面这样,首先导入 org.junit.Test 注解,然后导入 org.junit.Assert 工具类的所有静态断言方法(不使用静态导入也行,但是使用静态导入明显是更方便的)。注意到测试类与源码类的命名特点了吗,测试类一般就是在源码类的名字后面加上 Test 后缀,这样的好处是可读性好,别人一看就知道这是用于测试 Calculator 的测试类,当然 junit 并没有规定测试类与源码类的命名规则,但是遵循这个约定是有好处的。

然后就是 @Test 注解了,我们需要将这个注解放到对应的测试方法上面,告诉 JUnit,这是一个用于测试的方法,而对于的测试方法的命名也是有约定的(注意是约定不是规定,所以名字什么的是可以自取的,当然遵守约定总是有好处的),约定是使用 test 开头,使用驼峰命名法来进行命名,如上。每个测试方法都是 public void MethodName() 修饰的,访问性为 public,没有返回值,不接收参数,是一个实例方法。

一般来说,每个测试方法都对应一个源码方法,比如 add 就是 testAdddiv 就是 testDiv。然后就是利用 Assert 提供的断言方法进行测试了,比如最常见的就是 assertEquals(expecteds, actuals),expected 表示期望结果值,而 actual 表示实际结果值,而 assertEquals 表示,如果实际值不等于预期值,则抛出断言错误,在进行 mvn test 生命周期时就会提示对应的错误信息。如果相等则没有什么输出。

我们来看看默认的执行输出:

  1. $ mvn clean test exec:java -q
  2. -------------------------------------------------------
  3. T E S T S
  4. -------------------------------------------------------
  5. Running com.zfl9.CalculatorTest
  6. Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.163 sec
  7. Results :
  8. Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
  9. 20 + 10 = 30
  10. 20 - 10 = 10
  11. 20 * 10 = 200
  12. 20 / 10 = 2

我们来改一下测试类,故意让它与预期值不相等,看下什么结果:

  1. package com.zfl9;
  2. import org.junit.Test;
  3. import static org.junit.Assert.*;
  4. public class CalculatorTest {
  5. @Test
  6. public void testAdd() {
  7. assertEquals(30, Calculator.add(20, 10));
  8. }
  9. @Test
  10. public void testSub() {
  11. assertEquals(10, Calculator.sub(20, 10));
  12. }
  13. @Test
  14. public void testMul() {
  15. assertEquals(200, Calculator.mul(20, 10));
  16. }
  17. @Test
  18. public void testDiv() {
  19. assertEquals(3, Calculator.div(20, 10));
  20. }
  21. }

我们将 2 改为了 3,故意让它报错,这是运行结果:

  1. $ mvn clean test exec:java -q
  2. -------------------------------------------------------
  3. T E S T S
  4. -------------------------------------------------------
  5. Running com.zfl9.CalculatorTest
  6. Tests run: 4, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.05 sec <<< FAILURE!
  7. testDiv(com.zfl9.CalculatorTest) Time elapsed: 0.004 sec <<< FAILURE!
  8. java.lang.AssertionError: expected:<3> but was:<2>
  9. at org.junit.Assert.fail(Assert.java:88)
  10. at org.junit.Assert.failNotEquals(Assert.java:834)
  11. at org.junit.Assert.assertEquals(Assert.java:645)
  12. at org.junit.Assert.assertEquals(Assert.java:631)
  13. at com.zfl9.CalculatorTest.testDiv(CalculatorTest.java:24)
  14. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  15. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  16. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  17. at java.lang.reflect.Method.invoke(Method.java:498)
  18. at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
  19. at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
  20. at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
  21. at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
  22. at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
  23. at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
  24. at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
  25. at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
  26. at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
  27. at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
  28. at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
  29. at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
  30. at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
  31. at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
  32. at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
  33. at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
  34. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  35. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  36. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  37. at java.lang.reflect.Method.invoke(Method.java:498)
  38. at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
  39. at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
  40. at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
  41. at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
  42. at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
  43. Results :
  44. Failed tests: testDiv(com.zfl9.CalculatorTest): expected:<3> but was:<2>
  45. Tests run: 4, Failures: 1, Errors: 0, Skipped: 0
  46. [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.
  47. [ERROR]
  48. [ERROR] Please refer to /root/maven-workspace/junit-learn/target/surefire-reports for the individual test results.
  49. [ERROR] -> [Help 1]
  50. [ERROR]
  51. [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
  52. [ERROR] Re-run Maven using the -X switch to enable full debug logging.
  53. [ERROR]
  54. [ERROR] For more information about the errors and possible solutions, please read the following articles:
  55. [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

资源对象的创建与销毁
在进行单元测试时,我们通常需要在执行测试方法前创建要用到的对象,然后在测试方法执行完之后又要销毁它们(释放资源),最无脑的做法是在每个测试方法的前后拷贝这些创建和销毁的代码(很多重复代码,不好),稍微明智一点的做法就是声明一个私有数据成员,然后将创建对象的代码单独放到一个成员函数中,销毁对象的代码也是单独放到一个成员函数中,然后在每个测试方法的首尾加入这两个函数的调用就行。

但是 junit 提供了一个更简便的方法,我们不需要在测试方法的首尾加入两个函数的调用,而是直接使用 @Before@After 注解来标注创建函数和销毁函数,junit 在进行测试时会自动在运行测试方法前调用 @Before 方法,测试函数返回后又会自动调用 @After 方法,很方便,例子:

  1. package com.zfl9;
  2. import org.junit.*;
  3. import static org.junit.Assert.*;
  4. public class CalculatorTest {
  5. private Object obj = null;
  6. @Before
  7. public void newObj() {
  8. obj = new Object();
  9. System.out.println("newObj()");
  10. }
  11. @After
  12. public void delObj() {
  13. obj = null;
  14. System.out.println("delObj()");
  15. }
  16. @Test
  17. public void testAdd() {
  18. System.out.println("testAdd()");
  19. assertEquals(30, Calculator.add(20, 10));
  20. }
  21. @Test
  22. public void testSub() {
  23. System.out.println("testSub()");
  24. assertEquals(10, Calculator.sub(20, 10));
  25. }
  26. @Test
  27. public void testMul() {
  28. System.out.println("testMul()");
  29. assertEquals(200, Calculator.mul(20, 10));
  30. }
  31. @Test
  32. public void testDiv() {
  33. System.out.println("testDiv()");
  34. assertEquals(2, Calculator.div(20, 10));
  35. }
  36. }

运行结果:

  1. $ mvn clean test exec:java -q
  2. -------------------------------------------------------
  3. T E S T S
  4. -------------------------------------------------------
  5. Running com.zfl9.CalculatorTest
  6. newObj()
  7. testAdd()
  8. delObj()
  9. newObj()
  10. testDiv()
  11. delObj()
  12. newObj()
  13. testMul()
  14. delObj()
  15. newObj()
  16. testSub()
  17. delObj()
  18. Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.091 sec
  19. Results :
  20. Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
  21. 20 + 10 = 30
  22. 20 - 10 = 10
  23. 20 * 10 = 200
  24. 20 / 10 = 2

Test 注解的两个参数
@Test 注解是 junit 4.x 中最常用的一个注解元素,它有两个可选参数,分别是:

例子:

  1. package com.zfl9;
  2. import org.junit.*;
  3. import static org.junit.Assert.*;
  4. public class SimpleTest {
  5. @Test(expected=IndexOutOfBoundsException.class)
  6. public void testException() {
  7. throw new IndexOutOfBoundsException();
  8. }
  9. }

运行:

  1. $ mvn clean test exec:java -q
  2. -------------------------------------------------------
  3. T E S T S
  4. -------------------------------------------------------
  5. Running com.zfl9.CalculatorTest
  6. Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.045 sec
  7. Running com.zfl9.SimpleTest
  8. Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 sec
  9. Results :
  10. Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
  11. 20 + 10 = 30
  12. 20 - 10 = 10
  13. 20 * 10 = 200
  14. 20 / 10 = 2

如果我们把抛出异常的那条语句给注释掉,那么会测试失败:

  1. package com.zfl9;
  2. import org.junit.*;
  3. import static org.junit.Assert.*;
  4. public class SimpleTest {
  5. @Test(expected=IndexOutOfBoundsException.class)
  6. public void testException() {
  7. // throw new IndexOutOfBoundsException();
  8. }
  9. }

运行:

  1. $ mvn clean test exec:java -q
  2. -------------------------------------------------------
  3. T E S T S
  4. -------------------------------------------------------
  5. Running com.zfl9.CalculatorTest
  6. Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.046 sec
  7. Running com.zfl9.SimpleTest
  8. Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.004 sec <<< FAILURE!
  9. testException(com.zfl9.SimpleTest) Time elapsed: 0.002 sec <<< FAILURE!
  10. java.lang.AssertionError: Expected exception: java.lang.IndexOutOfBoundsException
  11. at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:32)
  12. at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
  13. at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
  14. at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
  15. at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
  16. at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
  17. at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
  18. at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
  19. at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
  20. at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
  21. at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
  22. at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
  23. at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
  24. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  25. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  26. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  27. at java.lang.reflect.Method.invoke(Method.java:498)
  28. at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
  29. at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
  30. at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
  31. at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
  32. at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
  33. Results :
  34. Failed tests: testException(com.zfl9.SimpleTest): Expected exception: java.lang.IndexOutOfBoundsException
  35. Tests run: 5, Failures: 1, Errors: 0, Skipped: 0
  36. [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.
  37. [ERROR]
  38. [ERROR] Please refer to /root/maven-workspace/junit-learn/target/surefire-reports for the individual test results.
  39. [ERROR] -> [Help 1]
  40. [ERROR]
  41. [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
  42. [ERROR] Re-run Maven using the -X switch to enable full debug logging.
  43. [ERROR]
  44. [ERROR] For more information about the errors and possible solutions, please read the following articles:
  45. [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

同理,如果我们不传递对应的异常类给 Test 注解,而测试方法可能会抛出异常,则也会导致测试失败:

  1. package com.zfl9;
  2. import org.junit.*;
  3. import static org.junit.Assert.*;
  4. public class SimpleTest {
  5. @Test
  6. public void testException() throws IndexOutOfBoundsException {
  7. throw new IndexOutOfBoundsException();
  8. }
  9. }

运行结果:

  1. $ mvn clean test exec:java -q
  2. -------------------------------------------------------
  3. T E S T S
  4. -------------------------------------------------------
  5. Running com.zfl9.CalculatorTest
  6. Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024 sec
  7. Running com.zfl9.SimpleTest
  8. Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.001 sec <<< FAILURE!
  9. testException(com.zfl9.SimpleTest) Time elapsed: 0.001 sec <<< ERROR!
  10. java.lang.IndexOutOfBoundsException
  11. at com.zfl9.SimpleTest.testException(SimpleTest.java:9)
  12. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  13. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  14. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  15. at java.lang.reflect.Method.invoke(Method.java:498)
  16. at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
  17. at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
  18. at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
  19. at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
  20. at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
  21. at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
  22. at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
  23. at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
  24. at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
  25. at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
  26. at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
  27. at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
  28. at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
  29. at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
  30. at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
  31. at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
  32. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  33. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  34. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  35. at java.lang.reflect.Method.invoke(Method.java:498)
  36. at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
  37. at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
  38. at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
  39. at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
  40. at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
  41. Results :
  42. Tests in error:
  43. testException(com.zfl9.SimpleTest)
  44. Tests run: 5, Failures: 0, Errors: 1, Skipped: 0
  45. [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.
  46. [ERROR]
  47. [ERROR] Please refer to /root/maven-workspace/junit-learn/target/surefire-reports for the individual test results.
  48. [ERROR] -> [Help 1]
  49. [ERROR]
  50. [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
  51. [ERROR] Re-run Maven using the -X switch to enable full debug logging.
  52. [ERROR]
  53. [ERROR] For more information about the errors and possible solutions, please read the following articles:
  54. [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

然后我们来试试 timeout 参数,表示该方法必须能够在指定时间内返回:

  1. package com.zfl9;
  2. import org.junit.*;
  3. import static org.junit.Assert.*;
  4. public class SimpleTest {
  5. @Test(timeout = 10)
  6. public void testException() {
  7. try { Thread.sleep(100); } catch (InterruptedException e) {}
  8. }
  9. }

我们设置的是该方法必须在 10ms 之内返回,然而我们故意在里面 sleep 100ms,结果:

  1. $ mvn clean test exec:java -q
  2. -------------------------------------------------------
  3. T E S T S
  4. -------------------------------------------------------
  5. Running com.zfl9.CalculatorTest
  6. Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.075 sec
  7. Running com.zfl9.SimpleTest
  8. Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.009 sec <<< FAILURE!
  9. testException(com.zfl9.SimpleTest) Time elapsed: 0.009 sec <<< ERROR!
  10. org.junit.runners.model.TestTimedOutException: test timed out after 10 milliseconds
  11. at java.lang.Thread.sleep(Native Method)
  12. at com.zfl9.SimpleTest.testException(SimpleTest.java:9)
  13. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  14. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  15. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  16. at java.lang.reflect.Method.invoke(Method.java:498)
  17. at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
  18. at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
  19. at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
  20. at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
  21. at org.junit.internal.runners.statements.FailOnTimeout$CallableStatement.call(FailOnTimeout.java:298)
  22. at org.junit.internal.runners.statements.FailOnTimeout$CallableStatement.call(FailOnTimeout.java:292)
  23. at java.util.concurrent.FutureTask.run(FutureTask.java:266)
  24. at java.lang.Thread.run(Thread.java:748)
  25. Results :
  26. Tests in error:
  27. testException(com.zfl9.SimpleTest): test timed out after 10 milliseconds
  28. Tests run: 5, Failures: 0, Errors: 1, Skipped: 0
  29. [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.
  30. [ERROR]
  31. [ERROR] Please refer to /root/maven-workspace/junit-learn/target/surefire-reports for the individual test results.
  32. [ERROR] -> [Help 1]
  33. [ERROR]
  34. [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
  35. [ERROR] Re-run Maven using the -X switch to enable full debug logging.
  36. [ERROR]
  37. [ERROR] For more information about the errors and possible solutions, please read the following articles:
  38. [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

简单的测试类模板

  1. import org.junit.*;
  2. import static org.junit.Assert.*;
  3. public class SampleTest {
  4. private java.util.List emptyList;
  5. @Before
  6. public void setUp() {
  7. emptyList = new java.util.ArrayList();
  8. }
  9. @After
  10. public void tearDown() {
  11. emptyList = null;
  12. }
  13. @Test
  14. public void testSomeBehavior() {
  15. assertEquals("Empty list should have 0 elements", 0, emptyList.size());
  16. }
  17. @Test(expected=IndexOutOfBoundsException.class)
  18. public void testForException() {
  19. Object o = emptyList.get(0);
  20. }
  21. }

一次性的 before 和 after 方法
我们注意到,上面的 @Before@After 注解的方法是每次执行测试方法时都会被执行的,而有时候我们可能需要 setUp() 方法和 tearDown() 方法执行一次就行(首尾),在 junit 中是运行我们这样做的,我们只需使用 @BeforeClass@AfterClass 注解来标注对应的 public static void 方法就行:

  1. import java.util.*;
  2. import org.junit.*;
  3. import static org.junit.Assert.*;
  4. public class SimpleTest {
  5. private Collection collection;
  6. @BeforeClass
  7. public static void oneTimeSetUp() {
  8. // one-time initialization code
  9. }
  10. @AfterClass
  11. public static void oneTimeTearDown() {
  12. // one-time cleanup code
  13. }
  14. @Before
  15. public void setUp() {
  16. collection = new ArrayList();
  17. }
  18. @After
  19. public void tearDown() {
  20. collection.clear();
  21. }
  22. @Test
  23. public void testEmptyCollection() {
  24. assertTrue(collection.isEmpty());
  25. }
  26. @Test
  27. public void testOneItemCollection() {
  28. collection.add("itemA");
  29. assertEquals(1, collection.size());
  30. }
  31. }

执行顺序如下:

  1. oneTimeSetUp()
  2. setUp()
  3. testEmptyCollection()
  4. tearDown()
  5. setUp()
  6. testOneItemCollection()
  7. tearDown()
  8. oneTimeTearDown()

JUnit 进阶

Javadoc APIhttps://junit.org/junit4/javadoc/latest/index.html

在第二章节,我们介绍了 junit 最常见的用法,涉及到的东西有:@Test 注解、@Before/@After 注解、@BeforeClass/@AfterClass 注解、以及 @Test 注解的 expected、timeout 参数。

我们来简单总结编写测试代码时要注意的几个东西:

我们唯一没有介绍的就是 @Ignore 注解,我们来试一下,注解测试方法:

  1. package com.zfl9;
  2. import org.junit.*;
  3. import static org.junit.Assert.*;
  4. public class SimpleTest {
  5. @Ignore
  6. @Test(timeout = 100)
  7. public void testException() {
  8. try { Thread.sleep(100); } catch (InterruptedException e) {}
  9. }
  10. }

运行结果如下:

  1. $ mvn clean test exec:java -q
  2. -------------------------------------------------------
  3. T E S T S
  4. -------------------------------------------------------
  5. Running com.zfl9.CalculatorTest
  6. Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.051 sec
  7. Running com.zfl9.SimpleTest
  8. Tests run: 1, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 0.001 sec
  9. Results :
  10. Tests run: 5, Failures: 0, Errors: 0, Skipped: 1
  11. 20 + 10 = 30
  12. 20 - 10 = 10
  13. 20 * 10 = 200
  14. 20 / 10 = 2

注意到没,Skipped 的值为 1,表示 Runner 在执行测试时跳过了一个测试方法。
当然我们也可以直接使用 @Ignore 来注解整个类,这样整个测试类就不会运行了:

  1. package com.zfl9;
  2. import org.junit.*;
  3. import static org.junit.Assert.*;
  4. @Ignore
  5. public class SimpleTest {
  6. @Test(timeout = 100)
  7. public void testException() {
  8. try { Thread.sleep(100); } catch (InterruptedException e) {}
  9. }
  10. }

运行结果如下:

  1. $ mvn clean test exec:java -q
  2. -------------------------------------------------------
  3. T E S T S
  4. -------------------------------------------------------
  5. Running com.zfl9.CalculatorTest
  6. Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.103 sec
  7. Running com.zfl9.SimpleTest
  8. Tests run: 1, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 0.002 sec
  9. Results :
  10. Tests run: 5, Failures: 0, Errors: 0, Skipped: 1
  11. 20 + 10 = 30
  12. 20 - 10 = 10
  13. 20 * 10 = 200
  14. 20 / 10 = 2

Assert 工具类的常见方法
JUnit 提供了一些辅助函数,他们用来帮助我们确定被测试的方法是否按照预期正常执行,这些辅助函数我们称之为 断言(Assertion)。JUnit4 所有的断言都在 org.junit.Assert 类中,Assert 类包含了一组 静态 的测试方法,用于验证 期望值 expected实际值 actual 之间的逻辑关系是否正确,如果不符合我们的预期则表示测试未通过。Assert 实用类提供的方法都是静态方法,即 public static void,常见的有:

  1. /**
  2. * @param message 错误信息,字符串
  3. * @param expected 期望值,任意类型(重载)
  4. * @param actual 实际值,任意类型(重载)
  5. * 如果期望值与实际值不相等则表示测试失败,JUnit 会报错
  6. */
  7. assertEquals([message, ]expected, actual) // 是否相等 (标量)
  8. assertNotEquals([message, ]unexpected, actual) // 是否不等 (标量)
  9. assertArrayEquals([message, ]expecteds, actuals) // 是否相等 (数组)
  10. assertSame([message, ]expected, actual) // 引用的内存地址相同
  11. assertNotSame([message, ]expected, actual) // 引用的内存地址不同
  12. assertNull([message, ]object) // 断言 object 为 null
  13. assertNotNull([message, ]object) // 断言 object 不为 null
  14. assertTrue([message, ]condition) // 断言 condition 为真
  15. assertFalse([message, ]condition) // 断言 condition 为假

你可能会对 assertEquals()assertSame() 方法产生疑惑,其实很好分辨,一个是通过调用 equals() 方法进行判断是否“相等”,一个是通过调用 == 运算符进行判断是否“相等”,后者判断的其实是对象的内存地址是否相同,这和前者的判断是不一样的,这样说估计你就能记住它们的区别了。

除了上面这些方法之外,还有两个 fail() 方法,用来手动导致测试失败:

  1. public static void fail() // 没有消息
  2. public static void fail(String message)

什么是"手动导致测试失败"?很简单,比如:

  1. package com.zfl9;
  2. import org.junit.*;
  3. import static org.junit.Assert.*;
  4. public class SimpleTest {
  5. @Test
  6. public void testFail() {
  7. fail("test failed");
  8. }
  9. }

运行结果:

  1. $ mvn clean test exec:java -q
  2. -------------------------------------------------------
  3. T E S T S
  4. -------------------------------------------------------
  5. Running com.zfl9.CalculatorTest
  6. Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.063 sec
  7. Running com.zfl9.SimpleTest
  8. Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.001 sec <<< FAILURE!
  9. testFail(com.zfl9.SimpleTest) Time elapsed: 0.001 sec <<< FAILURE!
  10. java.lang.AssertionError: test failed
  11. at org.junit.Assert.fail(Assert.java:88)
  12. at com.zfl9.SimpleTest.testFail(SimpleTest.java:9)
  13. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  14. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  15. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  16. at java.lang.reflect.Method.invoke(Method.java:498)
  17. at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
  18. at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
  19. at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
  20. at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
  21. at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
  22. at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
  23. at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
  24. at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
  25. at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
  26. at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
  27. at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
  28. at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
  29. at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
  30. at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
  31. at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
  32. at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
  33. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  34. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  35. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  36. at java.lang.reflect.Method.invoke(Method.java:498)
  37. at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
  38. at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
  39. at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
  40. at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
  41. at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
  42. Results :
  43. Failed tests: testFail(com.zfl9.SimpleTest): test failed
  44. Tests run: 5, Failures: 1, Errors: 0, Skipped: 0

思考:测试类是如何运行的?
大家刚开始使用 JUnit 的时候,可能会跟我一样有一个疑问,JUnit 没有 main() 方法,那它是怎么开始执行的呢?众所周知,不管是什么程序,都必须有一个程序执行入口,而这个入口通常是 main() 方法。显然,JUnit 能直接执行某个测试方法,那么它肯定会有一个程序执行入口。没错,其实在 org.junit.runner 包下,有个 JUnitCore.java 类,这个类有一个标准的 main() 方法,这个其实就是 JUnit 程序的执行入口,其代码如下:

  1. public static void main(String... args) {
  2. Result result = new JUnitCore().runMain(new RealSystem(), args);
  3. System.exit(result.wasSuccessful() ? 0 : 1);
  4. }

通过分析里面的runMain()方法,可以找到最终的执行代码如下:

  1. public Result run(Runner runner) {
  2. Result result = new Result();
  3. RunListener listener = result.createListener();
  4. notifier.addFirstListener(listener);
  5. try {
  6. notifier.fireTestRunStarted(runner.getDescription());
  7. runner.run(notifier);
  8. notifier.fireTestRunFinished(result);
  9. } finally {
  10. removeListener(listener);
  11. }
  12. return result;
  13. }

可以看到,所有的单元测试方法都是通过 Runner 来执行的。Runner 只是一个抽象类,它是用来跑测试用例并通知结果的,JUnit 提供了很多 Runner 的实现类,可以根据不同的情况选择不同的 test runner。

当然,我们一般都是使用 maven 或 IDE 来执行测试,你也许注意到了,我们在之前的使用中,并没有手动运行什么 Runner,而是直接使用 mvn test 来跑单元测试,其实 maven 自动运行了对应的 Runner 而已啦。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注