所有代码上线之前开发人员都要做单元测试的,很重要!!!
本文就来学习学习使用JUnit编写单元测试。

概述

文中例子用的是Eclipse,对于IntelliJ IDEA也一样,万变不离其宗。

JUnit下载

JUnit 是一个 Java 编程语言的单元测试框架。JUnit 在测试驱动的开发方面有很重要的发展,是起源于 JUnit 的一个统称为 xUnit 的单元测试框架之一。

JUnit 促进了“先测试后编码”的理念,强调建立测试数据的一段代码,可以先测试,然后再应用。

特点:

  • JUnit 是一个开放的资源框架,用于编写和运行测试。
  • 提供注释来识别测试方法。
  • 提供断言来测试预期结果。
  • 提供测试运行来运行测试。
  • JUnit 测试允许你编写代码更快,并能提高质量。
  • JUnit 优雅简洁。没那么复杂,花费时间较少。
  • JUnit 测试可以自动运行并且检查自身结果并提供即时反馈。所以也没有必要人工梳理测试结果的报告。
  • JUnit 测试可以被组织为测试套件,包含测试用例,甚至其他的测试套件。
    JUnit 测试分类

测试分类是在编写和测试 JUnit 的重要分类。几种重要的分类如下:

  • 包含一套断言方法的测试断言
  • 包含规定运行多重测试工具的测试用例
  • 包含收集执行测试用例结果的方法的测试结果

基本用法(简单示例)

需要测试的类

需要测试的类(LengthOfLongestSubstring),是LeetCode一个算法实现类。后面的所有用例都会用这个类来进行测试

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
36
//3. Longest Substring Without Repeating Characters
public class LengthOfLongestSubstring {

/*
* Given a string, find the length of the longest substring without repeating
* characters.
*
* Examples:
*
* Given "abcabcbb", the answer is "abc", which the length is 3.
*
* Given "bbbbb", the answer is "b", with the length of 1.
*
* Given "pwwkew", the answer is "wke", with the length of 3. Note that the
* answer must be a substring, "pwke" is a subsequence and not a substring.
*/
public int lengthOfLongestSubstring(String s) {

if (s == null || s.length() == 0)
return 0;
LinkedList<Integer> linkedList = new LinkedList<>();
int length = 0;
for (int i = 0, len = s.length(); i < len; i++) {
int number = Integer.valueOf(s.charAt(i));
if (linkedList.contains(number)) {
length = linkedList.size() > length ? linkedList.size() : length;
linkedList.subList(0, linkedList.indexOf(number) + 1).clear();

}
linkedList.addLast(number);

}

return linkedList.size() > length ? linkedList.size() : length;
}
}

编写测试用例

File->New->JUnit Test Case新建后会显示下面窗口
1
点击Next。
2
里面实现算法的一个方法和main方法,如果有很多方法需要测试,直接勾上就行。点击Finish完成。编写测试用例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import junit.TestLeetCodeItem;

public class LengthOfLongestSubstringTest {
@Test
public void testLengthOfLongestSubstring() {
assertEquals(3, new LengthOfLongestSubstring().lengthOfLongestSubstring("abcabcbb"));
}

public static void main(String[] args) {

Result result = JUnitCore.runClasses(LengthOfLongestSubstringTest.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());
}
}

这里用到了断言和JUnitCore测试器。断言可以输入预期结果跟真实结果做比较来测试,JUnitCore。runClasses方法会执行传参进去的LengthOfLongestSubstringTest.class类的所有测试类。更多详情看API部分。

JUnit中的重要的 API

JUnit 中的最重要的程序包是 junit.framework 它包含了所有的核心类。一些重要的类列示如下:

  • Assert : assert方法的集合
  • TestCase : 一个定义了运行多重测试的固定装置
  • TestResult : TestResult集合了执行测试样例的所有结果
  • TestSuite : TestSuite 是测试的集合

Assert类

下面介绍的是 org.junit.Assert 类:

1
public class Assert extends java.lang.Object

这个类提供了一系列的编写测试的有用的声明方法。只有失败的声明方法才会被记录。Assert 类的重要方法列式如下:

method desc
void assertEquals(boolean expected, boolean actual) 检查两个变量或者等式是否平衡
void assertFalse(boolean condition) 检查条件是假的
void assertNotNull(Object object) 检查对象不是空的
void assertNull(Object object) 检查对象不是空的
void assertTrue(boolean condition) 检查条件为真
void assertNotNull(Object object) 检查对象不是空的
void fail() 在没有报告的情况下使测试不通过

TestCase 类

下面介绍的是 org.junit.TestCaset 类:

1
public abstract class TestCase extends Assert implements Test

测试样例定义了运行多重测试的固定格式。TestCase 类的一些重要方法列式如下:

method desc
int countTestCases() 创建一个默认的 TestResult 对象
TestResult createResult() 创建一个默认的 TestResult 对象
String getName() 获取 TestCase 的名称
TestResult run() 一个运行这个测试的方便的方法,收集由TestResult 对象产生的结果
void run(TestResult result) 在 TestResult 中运行测试案例并收集结果
void setName(String name) 设置 TestCase 的名称
void setUp() 创建固定装置,例如,打开一个网络连接
void tearDown() 拆除固定装置,例如,关闭一个网络连接
String toString() 返回测试案例的一个字符串表示

附上一个例子:

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
36
37
38
39
40
41
42
43
44
45
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import junit.framework.TestCase;

public class MyTestCase extends TestCase {

protected double fValue1;
protected double fValue2;

@Before
public void setUp() {
fValue1 = 2.0;
fValue2 = 3.0;
}

@Test
public void testAdd() {
// count the number of test cases
System.out.println("No of Test Case = " + this.countTestCases());
// test getName
String name = this.getName();
System.out.println("Test Case Name = " + name);
// test setName
this.setName("testNewAdd");
String newName = this.getName();
System.out.println("Updated Test Case Name = " + newName);
//print fValue1
System.out.println(fValue1);
}

// tearDown used to close the connection or clean up activities
public void tearDown() {
}

public static void main(String[] args) {
Result result = JUnitCore.runClasses(MyTestCase.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());
}
}

输出结果:

1
2
3
4
No of Test Case = 1
Test Case Name = testAdd
Updated Test Case Name = testNewAdd
2.0

TestResult 类

下面定义的是 org.junit.TestResult 类:

1
public class TestResult extends Object

TestResult 类收集所有执行测试案例的结果。它是收集参数层面的一个实例。这个实验框架区分失败和错误。失败是可以预料的并且可以通过假设来检查。错误是不可预料的问题就像 ArrayIndexOutOfBoundsExceptio
n。TestResult 类的一些重要方法列式如下:
| method | desc |
| ——————————————————– |:————————-:|
| void addError(Test test, Throwable t) | 在错误列表中加入一个错误 |
| void addFailure(Test test, AssertionFailedError t) | 在失败列表中加入一个失败 |
| void endTest(Test test) | 显示测试被编译的这个结果 |
| int errorCount() | 获取被检测出错误的数量 |
| Enumeration errors() | 返回错误的详细信息 |
| int failureCount() | 获取被检测出的失败的数量 |
| void run(TestCase test) | 运行 TestCase |
| int int runCount() | 获得运行测试的数量 |
| void startTest(Test test) | 声明一个测试即将开始 |
| void stop() | 标明测试必须停止 |

TestSuite 类

下面定义的是 org.junit.TestSuite 类:

1
public class TestSuite extends Object implements Test

TestSuite 类是测试的组成部分。它运行了很多的测试案例。TestSuite 类的一些重要方法列式如下:
|方法和描述|
|—|
|void addTest(Test test)|在套中加入测试。|
|void addTestSuite(Class<? extends TestCase> testClass)|将已经给定的类中的测试加到套中。|
|int countTestCases()|对这个测试即将运行的测试案例进行计数。|
|String getName()|返回套的名称。|
|void run(TestResult result)|在 TestResult 中运行测试并收集结果。|
|void setName(String name)|设置套的名称。|
|Test testAt(int index)|在给定的目录中返回测试。|
|int testCount()|返回套中测试的数量。|
|static Test warning(String message)|返回会失败的测试并且记录警告信息。|

更多API请看JUnit API在线查看

注解

注释就好像你可以在你的代码中添加并且在方法或者类中应用的元标签。JUnit 中的这些注释为我们提供了测试
方法的相关信息,哪些方法将会在测试方法前后应用,哪些方法将会在所有方法前后应用,哪些方法将会在执行中被忽略。
JUnit 中的注释的列表以及他们的含义:
|注解和描述|
|—|
|@Test|这个注释说明依附在 JUnit 的 public void 方法可以作为一个测试案例。|
|@Before|有些测试在运行前需要创造几个相似的对象。在 public void 方法加该注释是因为该方法需要在 test 方法前运行|
|@After|如果你将外部资源在 Before 方法中分配,那么你需要在测试运行后释放他们。在 public void 方法加该注释是因为该方法需要在 test 方法后运行。|
|@BeforeClass|在 public void 方法加该注释是因为该方法需要在类中所有方法前运行。|
|@AfterClass|它将会使方法在所有测试结束后执行。这个可以用来进行清理活动。|
|@Ignore|这个注释是用来忽略有关不需要执行的测试的。|

执行过程

新建一个JUnit Test Case,如图:

过程

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import com.dalai.pra.LengthOfLongestSubstring;

public class LengthOfLongestSubstringTest2 {

@Test
public void test_LengthOfLongestSubstring() {
assertEquals(3, new LengthOfLongestSubstring().lengthOfLongestSubstring("abcabcbb"));
System.out.println("in test01");
}

@Test
public void test() {
System.out.println("in test02");
}

// execute before class
@BeforeClass
public static void beforeClass() {
System.out.println("in before class");
}

// execute after class
@AfterClass
public static void afterClass() {
System.out.println("in after class");
}

// execute before test
@Before
public void before() {
System.out.println("in before");
}

// execute after test
@After
public void after() {
System.out.println("in after");
}

// test case ignore and will not execute
@Ignore
public void ignoreTest() {
System.out.println("in ignore test");
}

public static void main(String[] args) {

Result result = JUnitCore.runClasses(LengthOfLongestSubstringTest2.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());
}
}

执行结果:

1
2
3
4
5
6
7
8
in before class
in before
in test01
in after
in before
in test02
in after
in after class

可以在需要测试方法前后都可以执行一些方法。

时间测试

只要在写注解Test时:

1
2
3
4
5
@Test(timeout=1000)
public void testPrintMessage() {
System.out.println("Inside testPrintMessage()");
messageUtil.printMessage();
}

异常测试

1
2
3
4
5
@Test(expected = ArithmeticException.class)
public void testPrintMessage() {
System.out.println("Inside testPrintMessage()");
messageUtil.printMessage();
}

参数化测试

Junit 4 引入了一个新的功能参数化测试。参数化测试允许开发人员使用不同的值反复运行同一个测试。你将遵循5个步骤来创建参数化测试:

  1. 用 @RunWith(Parameterized.class) 来注释 test 类。
  2. 创建一个由 @Parameters 注释的公共的静态方法,它返回一个对象的集合(数组)来作为测试数据集合。
  3. 创建一个公共的构造函数,它接受和一行测试数据相等同的东西。
  4. 为每一列测试数据创建一个实例变量。
  5. 用实例变量作为测试数据的来源来创建你的测试用例。

一旦每一行数据出现测试用例将被调用。让我们看看活动中的参数化测试。

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
36
37
38
39
40
41
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.RunWith;
import org.junit.runner.notification.Failure;
import org.junit.runners.Parameterized;
import com.dalai.pra.LengthOfLongestSubstring;

@RunWith(Parameterized.class)
public class LengthOfLongestSubstringTest {

private String inputString;
private int expectedResult;

@Test
public void testLengthOfLongestSubstring() {
assertEquals(expectedResult, new LengthOfLongestSubstring().lengthOfLongestSubstring(inputString));
}

public LengthOfLongestSubstringTest(String inputStr, int expectedResult) {
this.inputString = inputStr;
this.expectedResult = expectedResult;
}

@Parameterized.Parameters
public static Collection primeNumbers() {
return Arrays.asList(new Object[][] { { "abcabcbb", 3 }, { "bbbbbb", 1 }, { "asdfga", 5 } });
}

public static void main(String[] args) {

Result result = JUnitCore.runClasses(LengthOfLongestSubstringTest.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());
}
}

其它问题

StackOverFlow上JUnit问题排行