如果您有任何问题或想要讨论的话题,欢迎在评论区留言,我将尽快回复。
欢迎扫描下方二维码,阅读更多成体系的干货文章!
概述
所谓单元测试是测试应用程序的功能是否能按需要正常运行,确保软件质量。单元测试是一个对单一实体(类或方法)的测试。
什么是 Junit 测试框架?
Junit 是一个 Java 编程语言理想的单元测试框架,是起源于 Junit 的一个统称为 xUnit 的单元测试框架之一。JUnit 是一个回归测试框架,被开发者用于实施对应用程序的单元测试,加快程序编制速度,同时提高编码的质量。
JUnit 促进了 “先测试后编码” 的理念,就是:先编写测试代码,再编写功能代码,测试一点,编码一点,测试一点,编码一点......提升了程序的稳定性,看似花费了时间在写测试代码上,其实大大减少了程序员花费在排错的时间,并且保证了程序的代码质量,未来在重构代码的时候因为有单元测试进行兜底,可以放心大胆的进行重构,这方面内容可以了解 TDD (测试驱动开发)。
JUnit 特点
- JUnit 是开放的资源框架,用于编写和运行测试。
- JUnit 优雅简洁,没那么复杂,花费时间较少。
- 提供断言来测试预期结果。
- 提供测试运行来运行测试。
- JUnit 测试可以自动运行并且检查自身结果并提供即时反馈。
- JUnit 测试可以被组织为测试套件,包含测试用例,甚至其他的测试套件。
- JUnit 在一个条中显示进度。如果运行良好则是绿色;如果运行失败,则变成红色。
单元测试用例是什么?
单元测试用例是编写一部分代码,可以保证另一端代码按预期工作 ,即:已知输入和预期输出,在测试执行前就已知输出结果,已知输入是测试的先决条件,预期输出是需要测试的后置条件,每一项需求至少需要两个单元测试用例:一个正检验,一个负检验,如果这个需求有子需求,也要求每个子需求必须至少有正检验和负检验两个测试用例。
JUnit 测试框架重要特性
- 测试工具
- 测试套件
- 测试运行器
- 测试分类
测试工具
测试工具是一整套固定的工具用于基线测试,目的是为了确保测试能够在共享且固定的环境中运行,因此保证测试结果的可重复性,包括:
- 在所有测试调用指令发起前的 setUp() 方法;
- 在测试方法运行后的 tearDown() 方法;
一起看一个例子:
/**
* 实现一个 TestCase 子类
*
* @author qianlian.wj
* @date 2021/12/26
*/
public class JunitTest1 extends TestCase {
private int iValue1;
private int iValue2;
@Override
protected void setUp() throws Exception {
this.iValue1 = 1;
this.iValue2 = 2;
System.out.println("setUp(): initialize the value.");
}
public void testAdd() {
int result = this.iValue1 + this.iValue2;
assertEquals(result, 3);
}
@Override
protected void tearDown() throws Exception {
System.out.println("tearDown(): clean-up after a test.");
}
}
测试套件
如果有很多 TestCase 希望它们一键同时运行,在 Junit 中,@RunWith 和 @Suite 被用作运行测试套件。比如有 JunitTest1 和 JunitTest2 使用测试套件:
package com.java.junit;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
/**
* Junit Suite Test
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({
MathTest.class,
ConcatTest.class
})
public class JunitSuiteTest {
}
package com.java.junit;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class MathTest {
@Test
public void testAdd() {
assertEquals(3, 1 + 2);
}
}
package com.java.junit;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ConcatTest {
@Test
public void testConcat() {
assertEquals("Hello Junit", "Hello" + " Junit");
}
}
测试运行器
测试运行器用于执行测试用例:
package com.java.junit;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
/**
* 测试运行器
*/
public class RunnerTest {
public static void main(String[] args) {
Result result = JUnitCore.runClasses(JunitTest1.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());
}
}
测试分类
测试分类是在编写和测试 JUnit 的重要分类。重要的分类如下:
- 包含一套断言方法的测试断言,参考:org.junit.Assert、junit.framework.Assert
- 包含规定运行多重测试工具的测试用例,参考:junit.framework.Test、junit.framework.TestCase
- 包含收集执行测试用例结果的方法的测试结果,参考:org.junit.runner.Result、junit.framework.TestResult
JUnit - API
JUnit 中的最重要的程序包是 junit.framework 它包含了所有的核心类。重要的列示如下:
| 类的名称 | 类的功能 | 备注 |
|---|---|---|
| junit.framework.Assert | assert 方法的集合 | 已废弃,使用:org.junit.Assert |
| junit.framework.TestCase | 一个定义了运行多重测试的固定装置 | setUp()、tearDown() |
| junit.framework.TestResult | TestResult 集合了执行测试样例的所有结果 | wasSuccessful() |
| junit.framework.TestSuite | TestSuite 是测试的集合 | run() |
org.junit.Assert
定义
public class Assert {
/**
* Protect constructor since it is a static only class
*/
protected Assert() {
}
/**
* Asserts that a condition is true. If it isn't it throws an
* {@link AssertionError} with the given message.
*
* @param message the identifying message for the {@link AssertionError} (<code>null</code>
* okay)
* @param condition condition to be checked
*/
static public void assertTrue(String message, boolean condition) {
if (!condition) {
fail(message);
}
}
//...
}
重要方法
这个类提供了一系列的编写测试的有用的声明方法,只有失败的声明方法才会被记录。
| 方法 | 描述 |
|---|---|
| void assertEquals(Object expected, Object actual) | 检查两个变量或者等式是否平衡 |
| void assertFalse(boolean condition) | 检查条件是假的 |
| void assertNotNull(Object object) | 检查对象不是空的 |
| void assertNull(Object object) | 检查对象是空的 |
| void assertTrue(boolean condition) | 检查条件为真 |
| void fail() | 在没有报告的情况下使测试不通过 |
package com.java.junit;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* 测试断言方法
*/
public class TestAssert {
//expected 指定抛出一个 Throwable ,验证测试方法成功
@Test(expected = java.lang.AssertionError.class)
public void testAssertMethod() {
int num = 1;
String str1 = "null";
String str2 = null;
assertEquals(1, num);
assertFalse(str1 == str2);
assertNotNull(str1);
assertNull(str2);
assertTrue(str2 == null);
//抛出 java.lang.AssertionError
fail("fail...");
}
}
junit.framework.TestCase
定义
public abstract class TestCase extends Assert implements Test {
/**
* the name of the test case
*/
private String fName;
public TestCase() {
fName = null;
}
public TestCase(String name) {
fName = name;
}
/**
* Counts the number of test cases executed by run(TestResult result).
*/
public int countTestCases() {
return 1;
}
/**
* Creates a default TestResult object
*/
protected TestResult createResult() {
return new TestResult();
}
/**
* A convenience method to run this test, collecting the results with a
* default TestResult object.
*/
public TestResult run() {
TestResult result = createResult();
run(result);
return result;
}
//...
/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed.
*/
protected void setUp() throws Exception {
}
/**
* Tears down the fixture, for example, close a network connection.
* This method is called after a test is executed.
*/
protected void tearDown() throws Exception {
}
//...
}
重要方法
测试样例定义了运行多重测试的固定格式,TestCase 类的重要方法如下:
| 方法 | 描述 |
|---|---|
| public int countTestCases() | 为被run(TestResult result) 执行的测试案例计数 |
| protected TestResult createResult() | 创建一个默认的 TestResult 对象 |
| public String getName() | 获取 TestCase 的名称 |
| public TestResult run() | 一个运行这个测试的方便的方法,收集由TestResult 对象产生的结果 |
| public void run(TestResult result) | 在 TestResult 中运行测试案例并收集结果 |
| public void setName(String name) | 设置 TestCase 的名称 |
| void setUp() throws Exception | 创建固定装置,例如,打开一个网络连接 |
| void tearDown() throws Exception | 拆除固定装置,例如,关闭一个网络连接 |
| public String toString() | 返回测试案例的一个字符串表示 |
public class MathDivideTest extends TestCase {
private int iValue1;
private int iValue2;
@Override
protected void setUp() throws Exception {
this.iValue1 = 0;
this.iValue2 = 2;
System.out.println("setUp(): initialize the value.");
}
public void testDivideByZero() {
int result = iValue2 / iValue1;
assertEquals(result, 2);
}
@Override
protected void tearDown() throws Exception {
System.out.println("tearDown(): clean-up after a test.");
}
}
junit.framework.TestResult
定义
TestResult 类收集所有执行测试案例的结果,它是收集参数层面的一个实例,区分失败和错误。失败是可以预料的并且可以通过假设来检查。错误是不可预料的问题,比如 ArrayIndexOutOfBoundsException。
/**
* A <code>TestResult</code> collects the results of executing a test case.
*/
public class TestResult {
protected List<TestFailure> fFailures;
protected List<TestFailure> fErrors;
protected List<TestListener> fListeners;
protected int fRunTests;
private boolean fStop;
public TestResult() {
fFailures = new ArrayList<TestFailure>();
fErrors = new ArrayList<TestFailure>();
fListeners = new ArrayList<TestListener>();
fRunTests = 0;
fStop = false;
}
//...
}
重要方法
TestResult 类的重要方法列式如下:
| 方法 | 描述 |
|---|---|
| public synchronized void addError(Test test, Throwable e) | 在错误列表中加入一个错误 |
| public synchronized void addFailure(Test test, AssertionFailedError e) | 在失败列表中加入一个失败 |
| public void endTest(Test test) | 显示测试被编译的这个结果 |
| public synchronized int errorCount() | 获取被检测出错误的数量 |
| public synchronized Enumeration errors() | 返回错误的详细信息 |
| public synchronized int failureCount() | 获取被检测出的失败的数量 |
| protected void run(final TestCase test) | 运行 TestCase |
| public synchronized int runCount() | 获得运行测试的数量 |
| public void startTest(Test test) | 声明一个测试即将开始 |
| public synchronized void stop() | 标明测试必须停止 |
| public synchronized boolean wasSuccessful() | 返回整个测试是否成功 |
junit.framework.TestSuite
TestSuite 类是测试的组成部分,它可以运行很多的测试案例。
定义
/**
* A <code>TestSuite</code> is a <code>Composite</code> of Tests.
* It runs a collection of test cases. Here is an example using
* the dynamic test definition.
*
* <pre>
* TestSuite suite= new TestSuite();
* suite.addTest(new MathTest("testAdd"));
* suite.addTest(new MathTest("testDivideByZero"));
* </pre>
*
* @see Test
*/
public class TestSuite implements Test {
//...
/**
* Runs the tests and collects their result in a TestResult.
*/
public void run(TestResult result) {
for (Test each : fTests) {
if (result.shouldStop()) {
break;
}
runTest(each, result);
}
}
//...
}
重要方法
| 方法 | 描述 |
|---|---|
| public void addTest(Test test) | 在suite中加入测试。 |
| public void addTestSuite(Class<? extends TestCase> testClass) | 将已经给定的类中的测试加到suite中。 |
| public int countTestCases() | 对这个测试即将运行的测试案例进行计数。 |
| public String getName() | 返回suite的名称。 |
| public void run(TestResult result) | 在 TestResult 中运行测试并收集结果。 |
| public void setName(String name) | 设置suite的名称。 |
| public Test testAt(int index) | 在给定的目录中返回测试。 |
| public int testCount() | 返回suite中测试的数量。 |
| public static Test warning(final String message) | 返回会失败的测试并且记录警告信息。 |
注解
JUnit 中的这些注解为我们提供了测试方法的相关信息,哪些方法将会在测试方法前后应用,哪些方法将会在所有方法前后应用,哪些方法将会在执行中被忽略。
| 注解 | 描述 |
|---|---|
| @Test | 这个注解说明依附在 JUnit 的 public void 方法可以作为一个测试案例。 |
| @Before | 有些测试在运行前需要创造几个相似的对象。在 public void 方法加该注解是因为该方法需要在 test 方法前运行。 |
| @After | 如果你将外部资源在 Before 方法中分配,那么你需要在测试运行后释放他们。在 public void 方法加该注解是因为该方法需要在 test 方法后运行。 |
| @BeforeClass | 在 public void 方法加该注解是因为该方法需要在类中所有方法前运行。 |
| @AfterClass | 它将会使方法在所有测试结束后执行。这个可以用来进行清理活动。 |
| ** | 这个注解是用来忽略有关不需要执行的测试的。 |
| @RunWith | 调用它引用的类来运行该类中的测试 |
JUnit - 案例
package com.java.junit;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
/**
* 验证常用注解执行顺序
*
* @author qianlian.wj
* @date 2021/12/26
*/
public class DemoTest {
@BeforeClass
public static void beforeClass() {
System.out.println("this is beforeClass().");
}
@Before
public void before() {
System.out.println("this is before().");
}
@Test
public void test1() {
System.out.println("this is test1().");
}
@Test
public void test2() {
System.out.println("this is test2().");
}
@Test
@Ignore
public void test3() {
System.out.println("this is ignore test3().");
}
@After
public void after() {
System.out.println("this is after().");
}
@AfterClass
public static void afterClass() {
System.out.println("this is afterClass().");
}
}
结果:
在下一篇文章中,我们将继续探讨关于单元测试的更多知识,敬请期待!
感谢您的阅读,如果您觉得这篇文章对你有帮助,欢迎点赞和分享!