Java单元测试知识总结(基于JUnit)

在日常开发中离不开进行代码的测试,因此很有必要学会如何进行规范的单元测试


单元测试的好处:

1、提升软件质量

2、促进代码优化

3、提升研发效率

4、增加重构自信


单元测试基本原则:(AIR原则,必须用断言式来检测,符合BCDE原则)

1、Automatic(自动化)

2、Independent(独立性)

3、Repeatable(可重复)

4、Border,边界值测试,包含循环边界、特殊值、特殊值时间点、数据顺序

5、Correct,正确的输入,并得到预期的结果

6、Design,与设计文档像结合,来编写单元测试

7、Error,单元测试用于证明程序是有错的,需要强制进行错误的输入,来检测是否能达到预期的结果。


单元测试覆盖率

粗粒度覆盖率:类覆盖和方法覆盖(只要类和参数和方法都调用和执行到了,就说这个类被测试覆盖了)

细粒度覆盖率:

1、行覆盖:是否每行都被执行到了。

2、分支覆盖:是否每个分支都被覆盖到了

3、条件覆盖:判定中是否每一个条件的所有可能情况至少被执行一次。注意一些&&和||的短路执行,要覆盖到。


单元测试编写:

工具:

1、主流的为JUnit和TestNG(这里主要介绍JUnit)

2、JUnit注解:

@Test 注明这个方法是测试方法,JUnit在测试阶段能检测到使用该注解的方法,标明它们可以测试运行

@TestFactory 注明一个方法是基于数据驱动的动态测试数据源

@ParameterizedTest 注明是测试方法,可以让一个测试方法使用不同的入参运行多次

@RepeatedTest 注明自定义运行次数

@BeforeEach 指定每个测试方法运行前先执行该方法,一般用于数据准备

@AfterEach 每个测试方法运行后都执行该方法,一般用于删除数据(组成测试回环,新增的数据,测试完后删除)

@BeforeAll 每个测试类运行前,都运行一个指定方法

@AfterAll 每个测试类运行后,都运行一个指定方法

@Disabled 注明该测试方法不再运行

@Nested 为测试添加嵌套层级,以便组织用例结构

@Tag 为测试类或方法添加标签,以便有选择性的执行


常用的断言式:

1、fail()    断言测试失败

2、assertTrue() /assertFalse()    断言条件为真或假

3、assertNull() / assertNotNull()    断言条件为非空

4、assertEquals() / assertNotEquals() 断言指定两个值相等或不相等,使用equals方法比较

5、assertArrayEquals()    断言数组元素全部相等

6、assertSame() / assertNotSame()    断言两个对象是否相等

7、assertThrows() / assertDoesNotThrow()    断言是否抛出了一个特定类型的异常

8、assertTimeout() / assertTimeoutPreemptively()    断言是否执行超时,区别在于测试程序是否在同一个线程内执行

9、assertIterableEquals()    断言迭代器元素是否都相等

10、assertLinesMatch()    断言字符串列表中全部元素都正则匹配

11、assertAll()    断言多个条件同时满足

还有一种AssertJ断言方式(流式断言),这种方式可以进行像Java8流式编程一样,进行流式的断言处理,大家可以自行搜素了解一下,当需要断言的条件有多个的时候,流式断言十分简便。


测试实例代码(规范代码):

需要被测试的类:


/**
 * 需要被测试的类
 */
public class TestClass {
    public String getName(){
        return "TestClass";
    }

    public int getInt(int i){
        return i;
    }
    public int getSum(int a, int b){
        return a+b;
    }
    public boolean getFalse(){
        return false;
    }
    public boolean getTrue(){
        return true;
    }
}

测试类:


import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

@DisplayName("学习JUnit")
public class TestJUnit {
    //定义待测试类的实例
    private TestClass testClass ;

    /**
     * 定义在整个测试类开始执行前的操作
     * 通过包括全局和外部资源的创建和初始化
     */
    @BeforeAll
    public static void init(){

    }

    /**
     * 定义在整个测试类执行完之后执行的操作
     * 通过包括全局和外部资源的释放和销毁(各种连接的注销,释放等)
     */
    @AfterAll
    public static void cleanup(){

    }

    /**
     * 在每个测试用例开始前执行的操作
     * 通过包括基础数据和运行环境的准备(给数据库添加数据测试等等)
     */
    @BeforeEach
    public void create(){

        this.testClass = new TestClass();
    }

    /**
     * 在每个测试用例完成后执行的操作
     * 通常包括运行环境的清理
     */
    @AfterEach
    public void destory(){

    }

    /**
     * 测试用例,测试类的求和功能
     */
    @Test
    @DisplayName("测试求和功能")
    public void getSum(){
        int a = 1;
        int b = 2;
        int c = testClass.getSum(a,b);
        assertEquals(3,c);
    }

    /**
     * 输入多组参数,重复测试
     * @param a
     * @param b
     * @param ans
     */
    @ParameterizedTest
    @DisplayName("测试重复求和功能")
    @CsvSource({
            "1, 2, 3",
            "1, 3, 3"
    })
    public void getSums(int a, int b, int ans){
        int c = testClass.getSum(a,b);
        assertEquals(ans,c);
    }

    /**
     * 禁用该测试用例,该测试用例不会被执行
     * 会出现在最终的报告中
     */
    @Disabled
    @Test
    @DisplayName("测试类名是否正确")
    public void getName(){
        assertTrue("TestClass".equals(testClass.getName()));
    }

    @Nested
    @DisplayName("表示属于TestJunit测试的子级测试(如:交易服务测试下的用户交易测试,一般建议不超过三层)")
    class NestTest{

        @DisplayName("嵌套测试")
        public void nestTest(){
            assertTrue(true);
        }
    }

    /**
     * 标注运行频率,
     * 可以使用maven添加插件,先执行所有为fast的,后执行slow的测试用例
     */
    @Test
    @Tag("fast")  //slow
    @DisplayName("返回True")
    public void getTrue(){
        assertTrue(testClass.getTrue());
    }

}

猜你喜欢

转载自blog.csdn.net/aa792978017/article/details/89314222