Contents
  1. 1. 单元测试,一致性测试和性能测试
    1. 1.1. 单元测试
    2. 1.2. 一致性测试
    3. 1.3. 性能测试
  2. 2. 构建高效的测试
  3. 3. 术语
    1. 3.1. Fixture
    2. 3.2. [行为|状态]测试
  4. 4. 测试的结构
    1. 4.1. Java测试的结构
    2. 4.2. 测试哪些内容
    3. 4.3. 对旧有代码测试
  5. 5. 如何使用JUnit
  6. 6. Junit例程
  7. 7. JUnit命名规范
  8. 8. Junit在Maven中的命名规范
  9. 9. JUnit测试集
  10. 10. 从命令行运行测试
  11. 11. 构建基本的测试代码
    1. 11.1. JUnit中的Annotation
  12. 12. 断言语句
  13. 13. 测试执行顺序
  14. 14. Maven中JUnit练习
    1. 14.1. 创建Maven工程
    2. 14.2. 创建一个Java类
    3. 14.3. 创建JUnit测试
    4. 14.4. maven test
  15. 15. 附:完整代码
    1. 15.1. POM文件
    2. 15.2. 被测试类
    3. 15.3. 测试类

JUnit是xUnit在Java中的应用,是一种单元测试框架。JUnit在编译时以Jar的形式链接,根据2013年对github 10,000个项目的调查,Junit和slf4j并列为最受欢迎的库,出现率为30.7%。

单元测试,一致性测试和性能测试

单元测试

单元测试是一段会执行目标工程中指定功能(测试对象)的代码,并验证它的行为。

被测试的代码比例也叫做覆盖率。

单元测试只针对一小段代码(类,方法),外部依赖应该被移除,例如用测试框架模拟外部依赖。

单元测试并不适合复杂的UI或组件交互,这部分工作属于一致性测试。

一致性测试

旨在测试组件或组件间的一致性,有时也称为功能测试。

这种测试将用例转化为测试集,例如模拟一个用户与应用交互。

性能测试

反复进行基准测试。

构建高效的测试

单元测试可以让你方便地了解单元逻辑是否正常。每次build或者修改之后运行单元测试可以帮助你检测修改可能引入的regression/退化(bug或性能下降)。高的测试覆盖率可以让你避免开发过程中的许多人工测试。

一致性测试可确保系统的工作正常,也能减少密集的手工测试。性能测试能保证系统在高负荷下正常工作。

术语

Fixture

测试时软件的设定状态,作为运行测试的基准

[行为|状态]测试

行为测试不检查返回值,只关注在特定输入时相关方法的运行情况。

状态测试 关注返回结果的正确性。

如果是算法或者系统功能测试,一般需要状态测试。典型的测试环境是尽量模拟相关变量,抽离测试对象与环境的交互,并检查对象的状态。

测试的结构

Java测试的结构

典型的单元测试是独立于源文件的单独目录甚至单独的项目。

测试哪些内容

关于此并无定论,有人认为所有语句都该被测试。

通常忽略琐碎的代码是无妨的,比如getter and settter这类简单的赋值和返回。应该为项目中关键、复杂的代码进行测试。单元测试也能让你避免在引入新特性时对原有功能产生影响。

对旧有代码测试

如果接手的工程中没有测试代码,最好为错误经常发生的部分写测试,这样就能集中精力去做项目的关键部分。

如何使用JUnit

测试类中包含一个只用于测试的方法,测试方法用@org.junit.Test标注。在这个方法中使用Junit框架提供的方法对目标代码的实际运行结果与期望值进行确认。

Junit例程

1
2
3
4
5
6
7
8
9
10
11
@org.junit.Test
public void multiplicationOfZeroIntegersShouldReturnZero() {
// MyClass is tested
MyClass tester = new MyClass();
// Tests
assertEquals("10 x 0 must be 0", 0, tester.multiply(10, 0));
assertEquals("0 x 10 must be 0", 0, tester.multiply(0, 10));
assertEquals("0 x 0 must be 0", 0, tester.multiply(0, 0));
}

JUnit命名规范

测试类一般用被测试类+Tests来命名。

should常用于测试方法名,如ordersShouldBeCreatedmenuShouldGetActive,以增强可读性。

总之,测试名应该尽量表明测试进行的内容,而不必去看代码。

Junit在Maven中的命名规范

Maven自动生成Test后缀的测试类。

JUnit测试集

如果你有几个测试类,你可以将其组合成一个测试集,测试集会运行其下的所有测试类。

以下演示了一个包含两个测试类的测试集,如果想增加测试类,可以添加至@Suite.SuiteClasses statement.

1
2
3
4
5
6
7
8
9
10
11
package com.vogella.junit.first;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({ MyClassTest.class, MySecondClassTest.class })
public class AllTests {
}

测试集可以嵌套。

从命令行运行测试

You can also run your JUnit tests outside Eclipse via standard Java code. Build frameworks like Apache Maven or Gradle in combination with a Continuous Integration Server (like Hudson or Jenkins) are typically used to execute tests automatically on a regular basis.

org.junit.runner.JUnitCore类提供runClasses()方法,允许运行一个或多个测试类。返回值为org.junit.runner.Result对象,包含测试结果信息。

以下为演示代码,它会执行测试类并在console打印可能的错误信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package de.vogella.junit.first;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
public class MyTestRunner {
public static void main(String[] args) {
Result result = JUnitCore.runClasses(MyClassTest.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
}
}

以上类可以像任何Java类一样从命令行运行,你只需要添加相应Jar到classpath即可。

构建基本的测试代码

JUnit中的Annotation

Junit4以上版本使用annotation标识方法和配置测试的运行,以下是常用相关annotation的简要说明。

@Test   //此方法为测试方法. 
public void method()

@Test (expected = Exception.class) //标识可能抛出的异常

@Test(timeout=100)  //超过指定时间,测试失败。

@Before             //该方法在每次测试前运行,用来准备测试环境,如读取输入数据,初始化类等。 
public void method()

@After              //方法在测试后运行,用来清理,如删除临时文件,恢复初始设置,释放内存等。 
public void method()

@BeforeClass            //方法在所有测试前运行一次,用来执行费时任务,如连接数据库。必须是Static方法。 
public static void method()    

@AfterClass             //方法在所有测试完成后执行一次,用来清理,如断开数据库连接,必须是Static方法。 
public static void method()    

@Ignore or @Ignore("禁用原因")      //忽略指定测试方法,用于测试类尚未准备好等情况,使用时最好标明忽略的原因。    

断言语句

JUnit的Assert类提供一些静态方法用于测试某些条件。一般以assert开头,参数分别为错误信息期望值实际值。如果期望与实际不符,将抛出AssertionException异常。

以下给出这类方法的概览,[]中的参数为可选,类型为String。

fail(message) 让方法失败。可用于确认某部分代码未被运行,或在测试类完成前人为返回失败结果,message参数可选。

assertTrue([message,] boolean condition)    确认布尔为真。

assertFalse([message,] boolean condition)    确认布尔为假

assertEquals([message,] expected, actual)    确认返回=期望,数组等比较的是地址而非内容。

assertEquals([message,] expected, actual, tolerance)    测试浮点数相等。

assertNull([message,] object)    对象为NULL

assertNotNull([message,] object)    对象非NULL

assertSame([message,] expected, actual)    变量指向同一个目标.

assertNotSame([message,] expected, actual)    变量指向不同目标

注意在message中提供可读的信息,以帮助识别错误和之后的修正,特别是测试与开发是不同人负责时。

测试执行顺序

JUnit会假定所有测试方法可以任意顺序执行,优良的测试设计应该可以任意顺序执行,也就是说,一个测试不应该依赖其它测试。

Junit 4.11允许你用Annoation以字母顺序对测试方法进行排序,使用方法为用@FixMethodOrder(MethodSorters.NAME_ASCENDING)来标注方法。

注意缺省情况会使用固定但不可预期的顺序,对应参数为`MethodSorters.DEFAULT`,也可以使用`MethodSorters.JVM`,代表JVM的默认方式,每次运行顺序会不同。

Maven中JUnit练习

创建Maven工程

使用mvn archetype:generate创建一个简单工程,可以将自动生成的类名重命名,如果使用IDE,也会将测试类一起重命名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
├── pom.xml
└── src
├── main
│   └── java
│   └── io
│   └── github
│   └── azyet
│   └── Calculator.java
└── test
└── java
└── io
└── github
└── azyet
└── CalculatorTest.java

创建一个Java类

将模块类更名,添加内容

1
2
3
4
5
6
7
public int multiply(int x, int y) {
// the following is just an example
if (x > 999) {
throw new IllegalArgumentException("X should be less than 1000");
}
return x / y; //故意留出错误
}

创建JUnit测试

Maven会自动创建测试类,我们只需在类中添加方法即可,注意测试方法必须以小写的test开头,否则mvn test不会调用 该方法。

1
2
3
4
5
6
7
8
9
10
11
@Test(expected = IllegalArgumentException.class)
public void testExceptionIsThrown() {
Calculator tester = new Calculator();
tester.multiply(100, 5);
}
@Test
public void testMultiply() {
Calculator tester = new Calculator();
assertEquals("10 x 5 must be 50", 50, tester.multiply(10, 5));
}

maven test

运行mvn test,以下是部分输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Picked up JAVA_TOOL_OPTIONS: -javaagent:/usr/share/java/jayatanaag.jar
Running io.github.azyet.CalculatorTest
Tests run: 3, Failures: 1, Errors: 1, Skipped: 0, Time elapsed: 0.146 sec <<< FAILURE!
……
Results :
Failed tests: testMultiply(io.github.azyet.CalculatorTest): 10 x 5 must be 50 expected:<50> but was:<2>
Tests in error:
testExceptionIsThrown(io.github.azyet.CalculatorTest): X should be less than 1000
Tests run: 3, Failures: 1, Errors: 1, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.107 s
[INFO] Finished at: 2015-05-19T16:40:52+08:00
[INFO] Final Memory: 15M/229M
[INFO] ------------------------------------------------------------------------

测试没有通过,错误原因及错误栈都打印在日志中。

修改源文件,让参数小于1000,将multiply中的return x / y;换成return x * y;,再次运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Picked up JAVA_TOOL_OPTIONS: -javaagent:/usr/share/java/jayatanaag.jar
Running io.github.azyet.CalculatorTest
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.151 sec
Results :
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.842 s
[INFO] Finished at: 2015-05-19T16:45:44+08:00
[INFO] Final Memory: 14M/200M
[INFO] ------------------------------------------------------------------------

这一次测试通过了。

附:完整代码

POM文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.azyet</groupId>
<artifactId>JUnitLearning</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>JUnitLearning</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

被测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package io.github.azyet;
/**
* Hello world!
*
*/
public class Calculator
{
public int multiply(int x, int y) {
// the following is just an example
if (x > 999) {
throw new IllegalArgumentException("X should be less than 1000");
}
return x * y;
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package io.github.azyet;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.junit.Test;
/**
* Unit test for simple Calculator.
*/
public class CalculatorTest extends TestCase {
/**
* Create the test case
*
*/
@Test(expected = IllegalArgumentException.class)
public void testExceptionIsThrown() {
Calculator tester = new Calculator();
tester.multiply(100, 5);
}
@Test
public void testMultiply() {
Calculator tester = new Calculator();
assertEquals("10 x 5 must be 50", 50, tester.multiply(10, 5));
}
/**
* @return the suite of tests being tested
*/
public static TestSuite suite() {
return new TestSuite(CalculatorTest.class);
}
}
Contents
  1. 1. 单元测试,一致性测试和性能测试
    1. 1.1. 单元测试
    2. 1.2. 一致性测试
    3. 1.3. 性能测试
  2. 2. 构建高效的测试
  3. 3. 术语
    1. 3.1. Fixture
    2. 3.2. [行为|状态]测试
  4. 4. 测试的结构
    1. 4.1. Java测试的结构
    2. 4.2. 测试哪些内容
    3. 4.3. 对旧有代码测试
  5. 5. 如何使用JUnit
  6. 6. Junit例程
  7. 7. JUnit命名规范
  8. 8. Junit在Maven中的命名规范
  9. 9. JUnit测试集
  10. 10. 从命令行运行测试
  11. 11. 构建基本的测试代码
    1. 11.1. JUnit中的Annotation
  12. 12. 断言语句
  13. 13. 测试执行顺序
  14. 14. Maven中JUnit练习
    1. 14.1. 创建Maven工程
    2. 14.2. 创建一个Java类
    3. 14.3. 创建JUnit测试
    4. 14.4. maven test
  15. 15. 附:完整代码
    1. 15.1. POM文件
    2. 15.2. 被测试类
    3. 15.3. 测试类