Featured image of post java单元测试

java单元测试

本文阅读量

单元测试

就是针对最小的功能单元(方法),编写测试代码对其进行正确性测试

Junit单元测试框架

可以用来对方法进行测试,它是第三方公司开源出来的

优点

  • 可以灵活的编写测试代码,可以针对某个方法进行测试,也支持一键完成对全部方法的自动化测试,且各自独立
  • 不需要程序员去分析测试的结果,会自动生成测试报告出来

junit单元测试具体步骤

  1. 将Junit框架的jar包导入到项目中(ida集成了junit框架,不需要单词导入了)
  2. 为需要测试的业务类,定义对应的测试类,并为每个业务方法,编写对应的测试方法(必须:公共,无参,无返回值)
  3. 测试方法上必须声明@Test注解,然后在测试方法中,编写代码调用被测试的业务方法进行测试
  4. 开始测试:选中测试方法,右键选择“JUnit运行”,如果测试通过则是绿色,测试失败则是红色

被测试方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class StringUtil {
    public static void printNumber(String name){
        if(name == null){
            System.out.println("参数不能为null");
            return;
        }
        System.out.println("名字长度是:" + name.length());
    }

    public static int getMaxIndex(String data){
        if(data==null){
            return -1;
        }
        return data.length() - 1;
    }
}

测试案例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.junit.Assert;
import org.junit.Test;

public class StringUtilTest {
    @Test
    public void testPrintNumber(){
        StringUtil.printNumber(null);
        StringUtil.printNumber("");
        StringUtil.printNumber("admin");
    }

    @Test
    public void testGetMaxIndex(){
        // 断言:预言
        int i1 = StringUtil.getMaxIndex(null);
        Assert.assertEquals("null测试失败",-1,i1);

        int i2 = StringUtil.getMaxIndex("");
        Assert.assertEquals("空字符串测试失败",-1,i2);

        int i3 = StringUtil.getMaxIndex("admin");
        Assert.assertEquals("admin测试失败",4,i3);
    }
}

常用注解

注解(4.xxx) 5.xxx 说明
@Test @Test 测试类中的方法必须用它修饰才能成为测试方法,才能启动执行
@Before @BeforeEach 用来修饰一个实例方法,该方法会在每一个测试方法执行之前执行一次
@After @AfterEach 用来修饰一个实例方法,该方法会在每一个测试方法执行之后执行一次
@BeforeClass @BeforeAll 用来修饰一个静态方法,该方法会在所有测试方法之前执行一次
@AfterClass @AfterAll 用来修饰一个静态方法,该方法会在所有测试方法之后执行一次
  • 在测试方法执行前执行的方法,常用于:初始化资源
  • 在测试方法执行完后再执行的方法,常用于:释放资源

单元测试

介绍

  • **测试:**是一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程。

  • **阶段划分:**单元测试、集成测试、系统测试、验收测试。

1). 单元测试

介绍:对软件的基本组成单位进行测试,最小测试单位。

目的:检验软件基本组成单位的正确性。

测试人员:开发人员

2). 集成测试

介绍:将已分别通过测试的单元,按设计要求组合成系统或子系统,再进行的测试。

目的:检查单元之间的协作是否正确。

测试人员:开发人员

3). 系统测试

介绍:对已经集成好的软件系统进行彻底的测试。

目的:验证软件系统的正确性、性能是否满足指定的要求。

测试人员:测试人员

4). 验收测试

介绍:交付测试,是针对用户需求、业务流程进行的正式的测试。

目的:验证软件系统是否满足验收标准。

测试人员:客户/需求方

  • **测试方法:**白盒测试、黑盒测试 及 灰盒测试。

1). 白盒测试

清楚软件内部结构、代码逻辑。

用于验证代码、逻辑正确性。

2). 黑盒测试

不清楚软件内部结构、代码逻辑。

用于验证软件的功能、兼容性、验收测试等方面。

3). 灰盒测试

结合了白盒测试和黑盒测试的特点,既关注软件的内部结构又考虑外部表现(功能)。

Junit快速入门

单元测试

  • 单元测试:就是针对最小的功能单元(方法),编写测试代码对其正确性进行测试。

  • JUnit:最流行的Java测试框架之一,提供了一些功能,方便程序进行单元测试(第三方公司提供)。

在之前的课程中,我们进行程序的测试 ,都是main方法中进行测试 。如下图所示:

通过main方法是可以进行测试的,可以测试程序是否正常运行。但是main方法进行测试时,会存在如下问题:

  1. 测试代码与源代码未分开,难维护。
  2. 一个方法测试失败,影响后面方法。
  3. 无法自动化测试,得到测试报告。

而如果我们使用了JUnit单元测试框架进行测试,将会有以下优势:

  1. 测试代码与源代码分开,便于维护。

  2. 可根据需要进行自动化测试。

  3. 可自动分析测试结果,产出测试报告。

入门程序

需求:使用JUnit,对UserService中的业务方法进行单元测试,测试其正确性。

  1. pom.xml中,引入JUnit的依赖。

    1
    2
    3
    4
    5
    6
    7
    
    <!--Junit单元测试依赖-->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.9.1</version>
        <scope>test</scope>
    </dependency>
    
  2. 在test/java目录下,创建测试类,并编写对应的测试方法,并在方法上声明@Test注解。

    1
    2
    3
    4
    5
    
    @Test
    public void testGetAge(){
        Integer age = new UserService().getAge("110002200505091218");
        System.out.println(age);
    }
    
  3. 运行单元测试 (测试通过:绿色;测试失败:红色)。

    测试通过显示绿色

​ 测试失败显示红色

注意:

1. 测试类的命名规范为:XxxxTest 1. 测试方法的命名规范为:public void xxx(){…}

常见注解

在JUnit中还提供了一些注解,还增强其功能,常见的注解有以下几个:

注解 说明 备注
@Test 测试类中的方法用它修饰才能成为测试方法,才能启动执行 单元测试
@BeforeEach 用来修饰一个实例方法,该方法会在每一个测试方法执行之前执行一次。 初始化资源(准备工作)
@AfterEach 用来修饰一个实例方法,该方法会在每一个测试方法执行之后执行一次。 释放资源(清理工作)
@BeforeAll 用来修饰一个静态方法,该方法会在所有测试方法之前只执行一次。 初始化资源(准备工作)
@AfterAll 用来修饰一个静态方法,该方法会在所有测试方法之后只执行一次。 释放资源(清理工作)
@ParameterizedTest 参数化测试的注解 (可以让单个测试运行多次,每次运行时仅参数不同) 用了该注解,就不需要@Test注解了
@ValueSource 参数化测试的参数来源,赋予测试方法参数 与参数化测试注解配合使用
@DisplayName 指定测试类、测试方法显示的名称 (默认为类名、方法名)

演示 @BeforeEach@AfterEach@BeforeAll@AfterAll 注解:

 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
public class UserServiceTest {

    @BeforeEach
    public void testBefore(){
        System.out.println("before...");
    }

    @AfterEach
    public void testAfter(){
        System.out.println("after...");
    }

    @BeforeAll //该方法必须被static修饰
    public static void testBeforeAll(){ 
        System.out.println("before all ...");
    }

    @AfterAll //该方法必须被static修饰
    public static void testAfterAll(){
        System.out.println("after all...");
    }

    @Test
    public void testGetAge(){
        Integer age = new UserService().getAge("110002200505091218");
        System.out.println(age);
    }
    
    @Test
    public void testGetGender(){
        String gender = new UserService().getGender("612429198904201611");
        System.out.println(gender);
    }
 }   

输出结果如下:

演示 @ParameterizedTest @ValueSource@DisplayName 注解:

 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
package com.itheima;

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

@DisplayName("测试-学生业务操作")
public class UserServiceTest {

    @DisplayName("测试-获取年龄")
    @Test
    public void testGetAge(){
        Integer age = new UserService().getAge("110002200505091218");
        System.out.println(age);
    }

    @DisplayName("测试-获取性别")
    @Test
    public void testGetGender(){
        String gender = new UserService().getGender("612429198904201611");
        System.out.println(gender);
    }

    @DisplayName("测试-获取性别3")
    @ParameterizedTest
    @ValueSource(strings = {"612429198904201611","612429198904201631","612429198904201626"})
    public void testGetGender3(String idcard){
        String gender = new UserService().getGender(idcard);
         System.out.println(gender);
    }
}

输出结果如下:

断言

JUnit提供了一些辅助方法,用来帮我们确定被测试的方法是否按照预期的效果正常工作,这种方式称为断言

断言方法 描述
assertEquals(Object exp, Object act, String msg) 检查两个值是否相等,不相等就报错。
assertNotEquals(Object unexp, Object act, String msg) 检查两个值是否不相等,相等就报错。
assertNull(Object act, String msg) 检查对象是否为null,不为null,就报错。
assertNotNull(Object act, String msg) 检查对象是否不为null,为null,就报错。
assertTrue(boolean condition, String msg) 检查条件是否为true,不为true,就报错。
assertFalse(boolean condition, String msg) 检查条件是否为false,不为false,就报错。
assertSame(Object exp, Object act, String msg) 检查两个对象引用是否相等,不相等,就报错。

示例演示:

 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
package com.itheima;

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

@DisplayName("测试-学生业务操作")
public class UserServiceTest {
    @DisplayName("测试-获取年龄2")
    @Test
    public void testGetAge2(){
        Integer age = new UserService().getAge("110002200505091218");
        Assertions.assertNotEquals(18, age, "两个值相等");
//        String s1 = new String("Hello");
//        String s2 = "Hello";
//        Assertions.assertSame(s1, s2, "不是同一个对象引用");
    }

    @DisplayName("测试-获取性别2")
    @Test
    public void testGetGender2(){
        String gender = new UserService().getGender("612429198904201611");
        Assertions.assertEquals("男", gender);
    }

    @DisplayName("测试-获取性别3")
    @ParameterizedTest
    @ValueSource(strings = {"612429198904201611","612429198904201631","612429198904201626"})
    public void testGetGender3(String idcard){
        String gender = new UserService().getGender(idcard);
        Assertions.assertEquals("男", gender);
    }
}

测试结果输出:

依赖范围

依赖的jar包,默认情况下,可以在任何地方使用。

在maven中,如果希望限制依赖的使用范围,可以通过 <scope>…</scope> 设置其作用范围。

作用范围:

  • 主程序范围有效。(main文件夹范围内)

  • 测试程序范围有效。(test文件夹范围内)

  • 是否参与打包运行。(package指令范围内)

可以在pom.xml中配置 <scope></scope> 属性来控制依赖范围。

如果对Junit单元测试的依赖,设置了scope为 test,就代表,该依赖,只是在测试程序中可以使用,在主程序中是无法使用的。所以我们会看到如下现象:

如上图所示,给junit依赖通过scope标签指定依赖的作用范围。 那么这个依赖就只能作用在测试环境,其他环境下不能使用。

scope的取值常见的如下:

scope****值 主程序 测试程序 打包(运行) 范例
compile(默认) Y Y Y log4j
test - Y - junit
provided Y Y - servlet-api
runtime - Y Y jdbc驱动
使用 Hugo 构建
主题 StackJimmy 设计