单元测试-Junit

227 阅读9分钟

如果您有任何问题或想要讨论的话题,欢迎在评论区留言,我将尽快回复。

欢迎扫描下方二维码,阅读更多成体系的干货文章!

qrcode_for_gh_e39063348296_258.jpg

概述

所谓单元测试是测试应用程序的功能是否能按需要正常运行,确保软件质量。单元测试是一个对单一实体(类或方法)的测试。

什么是 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.Assertassert 方法的集合已废弃,使用:org.junit.Assert
junit.framework.TestCase一个定义了运行多重测试的固定装置setUp()、tearDown()
junit.framework.TestResultTestResult 集合了执行测试样例的所有结果wasSuccessful()
junit.framework.TestSuiteTestSuite 是测试的集合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().");
    }
}

结果: image.png

在下一篇文章中,我们将继续探讨关于单元测试的更多知识,敬请期待!

感谢您的阅读,如果您觉得这篇文章对你有帮助,欢迎点赞和分享!