一. 测试
1. 测试:是一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程
2. 阶段划分:单元测试、集成测试、系统测试、验收测试。
① 单元测试:对软件的基本组成单位进行测试,最小测试单位;目的检验软件基本组成单位的正确性,测试人员:开发人员
② 集成测试:将已分别通过测试的单元,按设计要求组合成系统或子系统,在进行的测试;目的检验单元之间的协作是否正确;测试人员:开发人员
③ 系统测试:对已经集成好的软件进行彻底的测试;目的验证软件系统的正确性、性能是否满足指定的要求;测试人员:测试人员
④ 验收测试(交付测试):是针对用户需求、业务流程进行的正式的测试;目的验证软件系统是否满足验收标准;测试人员:客户/需求方
3. 测试方法:白盒测试、黑盒测试 及 灰盒测试
① 白盒测试:清楚软件内部结构、代码逻辑;用于验证代码、逻辑的正确性;
② 黑盒测试:不清楚软件内部结构、代码逻辑;用于验证软件的功能、兼容性等;
③ 灰盒测试:结合了白盒和黑盒的特点,既关注软件的内部结构又考虑外部功能
二. 单元测试快速入门
1. 单元测试:就是针对最小的功能单元(方法),编写测试代码对其正确性进行测试
2. JUnit:最流行的Java测试框架之一,提供了一些功能,方便程序进行单元测试(第三方公司)
3. main方法测试存在的问题:测试代码与源代码未分开,难维护;一个方法测试失败,影响后面方法;无法自动化测试,得到测试报告;
4. JUnit单元测试:测试代码与源代码分开,便于维护;可根据需要进行自动化测试;可自动分析测试结果,产出测试报告
三. 使用JUnit
1. 在pom.xml中,引入Junit的依赖
2. 在test/Java目录下,创建测试类,并编写对应的测试方法,并在方法上声明@Test注解
注意:JUnit单元测试类命名规范为:XxxxTest【规范】;Junit单元测试的方法,必须声明为 public void【规定】
3. 运行单元测试(测试通过:绿色;测试失败:红色)
package org.example;import java.time.LocalDate;
import java.time.Period;
import java.time.format.DateTimeFormatter;public class UserService {/*** 给定一个身份证号, 计算出该用户的年龄* @param idCard 身份证号*/public Integer getAge(String idCard){if (idCard == null || idCard.length() != 18) {throw new IllegalArgumentException("无效的身份证号码");}String birthday = idCard.substring(6, 14);LocalDate parse = LocalDate.parse(birthday, DateTimeFormatter.ofPattern("yyyyMMdd"));return Period.between(parse, LocalDate.now()).getYears();}/*** 给定一个身份证号, 计算出该用户的性别* @param idCard 身份证号*/public String getGender(String idCard){if (idCard == null || idCard.length() != 18) {throw new IllegalArgumentException("无效的身份证号码");}return Integer.parseInt(idCard.substring(16,17)) % 2 == 1 ? "男" : "女";}}
/*
* 测试类
* */
public class UserServiceTest {@Testpublic void testGetAge(){UserService userService = new UserService();Integer age = userService.getAge("112223199701011234");System.out.println(age);}}
四. 单元测试-断言
单元测试运行不报错(绿色),并不代表代码没问题,测试通过;
JUnit提供了一些辅助方法,用来帮助我们确定被测试的方法是否安装预期的效果正常工作,这种方式称为断言
断言方法 | 说明 |
Assertions.assertEquals(object exp, Object act, String msg) | 检查两个值是否相等,不相等就报错。 |
Assertions.assertNotEquals(object unexp, Object act, String msg) | 检查两个值是否不相等,相等就报错。 |
Assertions.assertNull(object act, String msg) | 检查对象是否为null,不为null,就报错。 |
Assertions.assertNotNull(object act, String msg) | 检查对象是否不为null,为null,就报错。 |
Assertions.assertTrue(boolean condition, String msg) | 检查条件是否为true,不为true,就报错。 |
Assertions.assertFalse(boolean condition, String msg) | 检查条件是否为false,不为false,就报错。 |
Assertions. assertThrows(class expType, Executable exec, String msg) | 检查两个对象引用是否相等,不相等,就报错 |
上述方法形参中的最后一个参数msg,表示错误提示信息,可以不指定(有对应的重载方法)
package org.example;import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;/*
* 测试类
* */
public class UserServiceTest {@Testpublic void testGetAge(){UserService userService = new UserService();Integer age = userService.getAge("112223199701011234");System.out.println(age);}@Testpublic void testGetGender(){UserService userService = new UserService();String gender = userService.getGender("112223199701011234");System.out.println(gender);}/** 断言* */@Testpublic void testGenderWithAssert(){UserService userService = new UserService();String gender = userService.getGender("112223199701011234");System.out.println(gender);//断言机制Assertions.assertEquals("女",gender,"性别获取异常");}/** 断言2* */@Testpublic void testGenderWithAssert2(){UserService userService = new UserService();//断言Assertions.assertThrows(NullPointerException.class, ()->{//预期抛出的异常IllegalArgumentExceptionuserService.getGender(null);});}}
五. 单元测试-常见注解
Junit中还提供了一些注解,增强其功能,常见的注解有:
注解 | 说明 | 备注 |
@Test | 测试类中的方法用它修饰才能成为测试方法,才能启动执行 | 单元测试 |
@ParameterizedTest | 参数化测试的注解(可以让单个测试运行多次,每次运行时仅参数不同) | 用了该注解,就不需要@Test注解了 |
@Valuesource | 参数化测试的参数来源,赋予测试方法参数 | 与参数化测试注解配合使用 |
@DisplayName | 指定测试类、测试方法显示的名称(默认为类名、方法名) | |
@BeforeEach | 用来修饰一个实例方法,该方法会在每一个测试方法执行之前执行一次。 | 初始化资源(准备工作) |
@AfterEach | 用来修饰一个实例方法,该方法会在每一个测试方法执行之后执行一次。 | 释放资源(清理工作) |
@BeforeAll | 用来修饰一个静态方法,该方法会在所有测试方法之前只执行一次。 | 初始化资源(准备工作) |
@AfterAll | 用来修饰一个静态方法,该方法会在所有测试方法之后只执行一次。 | 释放资源(清理工作) |
package org.example;import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;/*
* 测试类
* */
@DisplayName("测试用户信息测试类")
public class UserServiceTest {@BeforeAll //该方法会在所有测试方法之前只执行一次。public static void beforeAll(){System.out.println("Before All");}@AfterAll //该方法会在所有测试方法之后只执行一次。public static void afterAll(){System.out.println("After All");}@BeforeEach //该方法会在每一个测试方法执行之前执行一次。public void beforeEach(){System.out.println("Before Each");}@AfterEach //该方法会在每一个测试方法执行之后执行一次。public void afterEach(){System.out.println("after All");}@Testpublic void testGetAge(){UserService userService = new UserService();Integer age = userService.getAge("112223199701011234");System.out.println(age);}@Testpublic void testGetGender(){UserService userService = new UserService();String gender = userService.getGender("112223199701011234");System.out.println(gender);}/** 断言* */@Testpublic void testGenderWithAssert(){UserService userService = new UserService();String gender = userService.getGender("112223199701011234");System.out.println(gender);//断言机制Assertions.assertEquals("女",gender,"性别获取异常");}/** 断言2* */@Testpublic void testGenderWithAssert2(){UserService userService = new UserService();//断言Assertions.assertThrows(NullPointerException.class, ()->{//预期抛出的异常IllegalArgumentExceptionuserService.getGender(null);});}/** 参数化测试* */@DisplayName("测试用户性别")@ParameterizedTest@ValueSource(strings = {"112223199701011234", "112223199801011254", "112223199901011274"})public void testGetGender2(String idCards){UserService userService = new UserService();String gender = userService.getGender(idCards);//断言Assertions.assertEquals("男", gender);}}
六.单元测试-企业开发规范
1. 原则:编写测试方法时,要尽可能的覆盖业务方法中所有可能得情况(尤其是边界值)
package org.example;import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;/*
* 测试类
* */
@DisplayName("测试用户信息测试类2")
public class UserServiceTest2 {private UserService userService;@BeforeEachpublic void setUserService() {userService = new UserService();}/** 测试获取性别 -null* */@Test@DisplayName("获取性别-null值")public void testGender1(){Assertions.assertThrows(IllegalArgumentException.class, () -> {userService.getGender(null);});}/** 测试获取性别 -“ ”* */@Test@DisplayName("获取性别-空字符串")public void testGender2(){Assertions.assertThrows(IllegalArgumentException.class, () -> {userService.getGender("");});}/** 测试获取性别 -字符长度不够18位* */@Test@DisplayName("获取性别-字符长度不够18位")public void testGender3(){Assertions.assertThrows(IllegalArgumentException.class, () -> {userService.getGender("123654");});}/** 测试获取性别 -字符长度超过18位* */@Test@DisplayName("获取性别-字符长度超过18位")public void testGender4(){Assertions.assertThrows(IllegalArgumentException.class, () -> {userService.getGender("12345678901234567893");});}/** 测试获取性别 -正常男* */@Test@DisplayName("获取性别-正常男")public void testGender5(){String gender = userService.getGender("130001199712211234");Assertions.assertEquals("男", gender);}/** 测试获取性别 -正常女* */@Test@DisplayName("获取性别-正常女")public void testGender6(){String gender = userService.getGender("130001199712211224");Assertions.assertEquals("女", gender);}}
2. 测试覆盖率
七. 基于AI自动生成单元测试
1. 安装插件
上述方式下载巨慢,建议官网下载后导入
下载安装-通义灵码
八. 单元测试-Maven依赖范围
1. 在Maven项目中,test目录存放单元测试的代码,可以在main目录中编写单元测试,但是不规范
2. 依赖的jar包,默认情况下,可以在任何地方使用,可以通过<scope>...</scope>设置其作用范围
作用范围:主程序范围有效;(main文件夹范围内)
测试程序范围有效;(test文件夹范围内)
是否参与打包运行;(pagekage指令范围内)
scope值 | 主程序 | 测试程序 | 打包 | 范例 |
compile(默认) | Y | Y | Y | log4j |
test | - | Y | - | JUnit |
provided | Y | Y | - | servlet-api |
runtime | - | Y | Y | JDBC驱动 |
<!--Junit依赖 --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.9.1</version><scope>test</scope></dependency>
九. Maven常见问题解决方法
然后重新加载Maven