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常用于测试方法名,如ordersShouldBeCreated
,menuShouldGetActive
,以增强可读性。
总之,测试名应该尽量表明测试进行的内容,而不必去看代码。
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
public static void method ()
@AfterClass
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)
来标注方法。
注意缺省情况会使用固定但不可预期的顺序,对应参数为`M ethodSorters.DEFAULT`, 也可以使用`M ethodSorters.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);
}
}