springboot企业级项目开发之项目测试——单元测试!
ccwgpt 2025-07-06 09:38 2 浏览 0 评论
项目测试
项目测试是对项目的需求和功能进行测试,由测试人员写出完整的测试用例,再按照测试用例执行测试。项目测试是项目质量的保证,项目测试质量直接决定了当前项目的交付质量。
测试人员在开展测试之前,首先需要进行测试的需求分析,测试需求分析包括:
测试内容:需要进行哪些方面的测试,包括功能测试、性能测试、可靠性测试、易用性测试和安全性测试等;
测试环境:测试环境的配置;
测试工具:选择测试工具,包括缺陷管理工具和自动化测试工具等;
测试轮数:包括冒烟测试、功能测试、并发测试和弱网测试等。
测试需求分析完成后再开始项目测试。测试的步骤包括:冒烟测试、单元测试、集成测试、性能测试等,最后由产品经理完成产品的验收。
单元测试
项目测试的第一步就是单元测试,单元测试也称为模块测试或组件测试。在项目开发过程中,单元测试用来检查项目的单个单元或模块是否正常工作,它是由开发人员在开发后立即在开发环境中完成的。
为什么要做单元测试
单元测试通常是软件测试中基础的测试类型,用于测试单独模块的功能是否有错。它与功能测试的不同之处是,单元测试更加关注的是代码内部的逻辑,而非功能的完整性。
根据上述描述,在项目中实施单元测试有以下5个目标:
隔离各部分代码的功能;
确保单个模块功能逻辑的正确性;
在开发的过程中及时发现代码缺陷并修改;
发现产品开发早期逻辑中的Bug,以降低测试成本;
允许开发人员后续重构或升级代码。
基于单元测试的目标,在项目中使用单元测试有以下4个优点:
能在产品开发周期的早期发现问题,可以大大地降低测试成本,因为早发现一个缺陷的成本要比晚期发现它的成本低得多。
在改变现有功能(回归测试)的同时,可以减少缺陷。
简化了调试过程(测试驱动开发就是基于测试用例来完成功能开发)。调试是在程序中发现并解决妨碍软件正确运行缺陷的过程。当进行单元测试时,如果发现测试失败,则只需要在调试代码中做一下的更改,就可以快速定位错误。
进行单元测试,能够保证代码质量。
单元测试有哪些内容
单元测试的内容主要包括以下两点:
单元测试的方法:通常使用白盒测试;
单元测试的类型:可以选择手动测试或自动测试。
在对代码进行单元测试时,可以使用手动的方式,也可以使用一些自动化工具。手动测试和自动化测试的区别主要体现在执行效率和操作等方面,如表7.1所示。
在实际开发中流行DevOps(Development+Operations)开发模式,因此建议读者在项目中能使用自动化测试完成的任务尽量采用自动化测试来完成,以提升开发效率。
想要精通单元测试,还需要了解单元测试的几个关键点:
(1)执行单元测试的时间:一般在开发完成后立即进行。
(2)谁做单元测试:开发人员进行自测。
(3)明确单元测试的具体任务,包括两个方面:
首先,准备单元测试的计划,包括:准备测试计划;
回顾测试计划;
修订测试计划;
定义单元测试计划的基准数据。
其次,准备测试用例和脚本,包括:
准备测试环境、测试用例和脚本;
回顾测试用例和脚本;
修订测试用例和脚本。
(4)定义单元测试用例和脚本的基准数据。
(5)执行单元测试,完成后出具单元测试报告。
常规的JUnit测试
JUnit是Java应用开发中使用最广泛的单元测试框架。因为Java 8发布了Lambda表达式,使得Java的编码风格发生了巨大的变化,所以JUnit团队适时推出了新的框架——JUnit 5。JUnit 5能够适应Lambda风格的编码,建议在JDK 1.8及之后版本的项目中使用JUnit 5来创建和执行单元测试。本书中的单元测试以JUnit 5为例。
至于什么是JUnit,看以下官方的定义:
JUnit 5 is the next generation of JUnit. The goal is to create
an up-to-date foundation for developer-side testing on the JVM.
This includes focusing on Java 8 and above, as well as enabling
many different styles of testing.
官方提示JUnit 5是新一代的JUnit,它的目标是为JVM上的开发人员做测试创建一个最新的基础,为Java 8及以上版本创建不同的测试风格。JUnit5框架=JUnit Platform+JUnit Jupiter+JUnit Vintage,其各部分框架的含义如下:
JUnit Platform是在JVM上启动测试框架的基础。
JUnit Jupiter是JUnit 5扩展的新的编程模型和扩展模型,用来编写测试用例。Jupiter子项目为在平台上运行Jupiter的测试提供了一个TestEngine(测试引擎)。
JUnit Vintage提供了一个在平台上运行JUnit 3和JUnit 4的TestEngine。
JUnit 5的架构如图7.1所示。
第一层测试用例:开发人员使用junit-jupiter-api等测试框架的API编写业务代码的单元测试。第二层测试引擎,JUnit测试框架实现引擎API的框架,jupiterengine和vintage- engine分别是JUnit 4和JUnit 5对测试引擎API的实现。
第三层junit-platform-engine:junit-platform-engine平台引擎是对第二层中两种不同引擎实现的抽象,是测试引擎的接口标准。
第四层IDEA:启动器通过ServiceLoader发现测试引擎的实现并安排其执行。它为IDE和构建工具提供了API接口,因此在IDE中可以直接执行测试,例如,启动测试并显示其结果。
在项目中使用JUint 5进行单元测试时需要用到一些注解,如表7.2所示。此外,表中对JUnit 4和JUnit 5的注解使用进行了对比,供读者参考。
根据以上的注解,下面使用JUint 5完成一个单元测试示例。
(1)新建一个项目,在pom.xml中添加JUnit 5的依赖,代码如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository --></parent>
<groupId>com.example</groupId>
<artifactId>junit5-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>junit5-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--加入JUnit 5的版本测试;如果想用JUnit 4进行测试,把exclusions去除-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
(2)完成一些简单的业务代码。新建一个用户服务类的接口
UserService:package com.example.junit5demo.service;
import java.util.IllegalFormatException;
/**
* 测试接口
*/
public interface UserService {
/**
* 登录
* @param userName
* @param password
* @return
* @throws IllegalFormatException
*/
boolean login(String userName,String password) throws
IllegalFormat Exception;
/**
* 查询数量
* @return
*/
int countNum();
}
(3)新建一个类来实现UserService接口,这里使用的是最简单的实现方式,暂时先不连接数据库。
package com.example.junit5demo.service;
/**
* 测试接口的实现
*/
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl implements UserService {
@Override
public boolean login(String userName, String password) throws
Illegal
ArgumentException {
if (userName == null || password == null
|| userName.isEmpty() || password.isEmpty()) {
throw new IllegalArgumentException("不能为空");
}
if ("cc".equals(userName) && "123".equals(password)) {
return true;
}
return false;
}
@Override
public int countNum() {
return 18;
}
}
(4)实现UserService接口的方法后,在类名上右击,依次选择Go To |Test | Create New Test...命令,具体的过程如图7.2和图7.3所示。按照上述操作会跳转到选择测试方法的页面,如图7.4所示。选择要测试的方法,选中写的两个方法,单击OK按钮就能自动生成测试类和要测试的方法。
(5)添加Spring Boot的启动类,代码如下:
package com.example.junit5demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Junit5DemoApplication {
public static void main(String[] args) {
SpringApplication.run(Junit5DemoApplication.class, args);
}
}
(6)修改自动生成的测试类代码,完成两个业务方法的单元测试工作。
完成后的代码如下:
package com.example.junit5demo.service;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
@Slf4j
@SpringBootTest //SpringBot测试类注解
class UserServiceImplTest {
//输入要测试的类
@Autowired
private UserService userService;
//在所有测试方法之前执行
@BeforeAll
public static void beforeAll() {
log.info("before all");
}
//在每个测试方法执行之前执行
@BeforeEach
public void beforeEach() {
log.info("before each");
}
@AfterEach
public void afterEach() {
log.info("after each");
}
@AfterAll
public static void afterAll() {
log.info("after all");
} //测试countNum方法
@Test
void countNum() {
int i = userService.countNum();
assertEquals(18, i);
assertNotEquals(1, i);
}
// 测试login方法
@Test
void login() {
boolean cc1 = userService.login("cc", "123");
assertEquals(cc1, true);
boolean cc2 = userService.login("cc2", "123");
assertEquals(cc2, false);
assertThrows(IllegalArgumentException.class, () ->
userService.login("", "123"));
assertThrows(IllegalArgumentException.class, () ->
userService.login("123", null));
}
}
(7)运行这个测试类,在IDEA的控制台打印结果如图7.5所示。可以看到左侧的两个方法都显示绿色的勾,说明已经通过测试用例。至此,我们已经完成了UserService类中所有方法的单元测试,可以看出,相关代码逻辑正确。
提示:在完成单元测试后,如果开发人员在测试过程中优化了代码,对自己的代码进行了重构,则需要对重构后的代码再次进行单元测试,以确保其逻辑的正确性,千万不能忽略这一点。
Mock测试
Mock测试是指在单元测试的过程中对一些不容易构造的对象模拟一个对象进行使用的过程。一些对象只能在特定的环境中产生,例如HttpServletRequest对象必须从Servlet容器中才能构造出来,ResultSet对象必须依赖JDBC的实现才能构造,ProceedingJoinPoint对象的构建必须依赖AOP的实现。在遇到这种复杂对象的构建时,使用一个虚拟的对象(Mock对象)来替代,使用一个“假”的对象,便于在测试时顺利检测复杂对象(虚拟对象)的使用逻辑,以便快速、准确地测试自己的代码逻辑。
Mock的出现是为了解决不同的单元之间由于耦合而难于开发与测试的问题,因此在单元测试和集成测试中都会用到Mock。Mock最大的作用是把单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,则可以排除依赖,只验证所调用依赖的行为。例如,需要测试UserService中的方法,但是它依赖UserDao,这时就直接模拟一个用户的数据库对象,且只测试UserService中的方法,来验证USerService中的逻辑正确与否。
在进行单元测试时,以下的几个场景需要用到Mock对象:
需要将当前被测单元和依赖模块分离,构造一个独立的测试环境,不关注被测单元的依赖对象,而只关注被测单元的功能逻辑。例如,被测代码中需要依赖第三方接口的返回值进行逻辑处理,可能因为网络或者其他环境因素,调用第三方平台经常会中断或者失败,而无法对被测单元进行测试,这时就可以使用Mock技术来将被测单元和依赖模块独立开,使得测试可以进行下去。
被测单元依赖的模块尚未开发完成,而被测单元需要依赖模块的返回值进行后续处理。包括前后端分离项目中,后端接口开发完成之前,前端接口需要测试;依赖的上游项目的接口尚未开发完成,需要接口联调测试;service层的代码包含对Dao层的调用,但是Dao层的代码还没完成,需要模拟Dao层的对象。
被测单元依赖的对象较难模拟或者构造比较复杂。例如,HttpServletRequest对象和数据库的连接对象Connection都非常难以构造,则可以直接使用模拟后的对象。
在Java项目开发中有很多Mock框架,常见的有Mockito和PowerMock。
Mockito是一个在项目中最常用的优秀的单元测试Mock框架,它能满足大部分业务的测试要求;PowerMock框架可以解决Mockito框架不能解决的更难的问题,如业务代码中的静态方法、私有方法和Final方法等。PowerMock框架是在EasyMock和Mockito的基础上进行扩展的,它通过提供定制的类进行加载器并进行一些字节码的修改,从而实现更强大的测试功能。
本书使用Java开发中常用的Mockito作为Mock框架。下面介绍如何在Spring Boot项目中使用Mock对象进行测试,从而完成单元测试。
(1)在7.1.3小节中的项目文件pom.xml中添加Mockito依赖,代码如下:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.11.2</version>
</dependency>
(2)新建一个产品服务类接口ProductService及其实现类,代码如下:
package com.example.junit5demo.service;
public interface ProductService {
int countNum(); boolean productExists(String name);
}
(3)新建上述ProductService接口的实现类,代码如下:
package com.example.junit5demo.service;
import com.example.junit5demo.dao.ProductDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductDao productDao;
@Override
public int countNum() {
return productDao.countNum();
}
@Override
public boolean productExists(String name) {
if (name == null || name.isBlank()) {
return false;
}
return productDao.productExists(name);
}
}
(4)新建一个ProductDao类,直接使用类的实现返回数据,代码如下:
package com.example.junit5demo.dao;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class ProductDao {
public int countNum() {
return 2;
}
public boolean productExists(String name) {
/**
* 模拟Dao的方法
*/
List<String> apple = List.of("cc","apple", "orgage", "banana");
return apple.contains(name);
}
}
(5)生成ProductService的测试类,代码如下:
package com.example.junit5demo.service;
import com.example.junit5demo.dao.ProductDao;
import org.junit.Assert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.mockito.Mockito.when;// 使用Spring的测试框架
@ExtendWith(SpringExtension.class)
@SpringBootTest
class ProductServiceImplTest {
/**
* 输入要测试的对象
*/
@InjectMocks
ProductServiceImpl productService;
/**
* Mock对象的依赖对象
*/
@Mock
ProductDao productDao;
@BeforeEach
public void setUp() {
/**
* 初始化
*/
MockitoAnnotations.openMocks(this);
}
@Test
void coutNum() {
/**
* 当执行这个方法的时候直接返回5
*/
when(productDao.countNum()).thenReturn(5);
int num = productService.countNum();
/**
* 验证返回值
*/
Assert.assertEquals(num,5);
}
@Test void productExists() {
/**
* 这里本来应该返回true,但是故意设置为false,再查看返回值
*/
when(productDao.productExists("cc")).thenReturn(false);
boolean cc = productService.productExists("cc");
Assert.assertEquals(cc,false);
}
@Test
void productExists3() {
when(productDao.productExists("apple")).thenReturn(false);
boolean cc = productService.productExists("apply");
Assert.assertEquals(cc,false);
}
}
(6)在本次测试用例中,需要测试ProductServiceImpl中的两个业务方法。Product- ServiceImpl依赖ProductDao,这时模拟一个对象到测试对象中,当调用Dao的方法时就会返回设定的值,不会真正地执行Dao,而只测试ProductServiceImpl中的方法。执行本测试类ProductServiceImplTest中的所有测试方法,完成测试后的结果如图7.6所示。可以看到,3个测试用例都已通过,说明已经完成了Mock单元测试。
以上就是使用Mock进行单元测试的过程。Mock测试是单元测试的一大利器,它能帮助开发人员更快地完成单元测试、业务代码的检测和Bug的修复。
相关推荐
- Java七大热门技术框架源码解析(25章) 完结
-
获课》aixuetang.xyz/5699/Hibernate与MyBatis源码级PK:ORM框架的两种哲学在Java持久层框架领域,Hibernate与MyBatis代表了两种截然不同的设计哲学。...
- 【25章】Java七大热门技术框架源码解析
-
获课》aixuetang.xyz/5699/Java高级面试:七大框架源码精讲与实战解析在当今Java技术生态中,对主流框架源码的深入理解已成为高级开发者面试的核心竞争力。掌握Spring、MyBat...
- 饿了么董事长吴泽明兼任CEO,韩鎏分管即时物流中心
-
饿了么调整组织架构。2月11日,饿了么董事长吴泽明(花名:范禹)通过公司全员信宣布饿了么最新组织调整:即日起,吴泽明将兼任饿了么CEO,韩鎏(花名:昊宸)专注分管即时物流中心,继续向吴泽明汇报。吴泽明...
- 饿了么100%迁至阿里云,快速扩容可支持1亿人同时点单
-
来源:环球网6月17日,记者获悉,饿了么已完成100%上云,所有业务系统、数据库设施等均已迁移至阿里云。高峰期,饿了么可在阿里云上快速扩容,可以支持1亿人同时在线点单,这意味着饿了么的服务能力再次全面...
- 饿了么组织架构调整:董事长吴泽明兼任CEO 韩鎏专注即时物流中心管理
-
近日,饿了么董事长吴泽明(花名:范禹)通过公司全员信宣布饿了么最新组织调整:即日起,吴泽明将兼任饿了么CEO,韩鎏(花名:昊宸)专注分管即时物流中心,继续向吴泽明汇报。吴泽明在内部信中表示,考虑即时物...
- 饿了么组织架构调整:董事长吴泽明兼任CEO
-
Tech星球2月11日消息,据新浪科技报道,今日饿了么董事长吴泽明(花名:范禹)通过公司全员信宣布饿了么最新组织调整:即日起,吴泽明将兼任饿了么CEO,韩鎏(花名:昊宸)专注分管即时物流中心,继续向吴...
- 饿了么又调整了组织架构,董事长吴泽明兼任CEO
-
2月11日,饿了么董事长,花名为范禹的吴泽明,通过公司全员信宣布最新组织调整:从即日起,吴泽明将兼任饿了么CEO。公司原CEO,花名为昊宸的韩鎏今后专注分管即时物流中心,继续向吴泽明汇报。在内部信中,...
- SpringBoot项目快速开发框架JeecgBoot——Web处理!
-
Web处理JeecgBoot框架主要用于Web开发领域。下面介绍JeecgBoot在Web开发中的常用功能,如控制器、登录、系统菜单、权限模块的角色管理和用户管理。首先启动后台项目,将其导入IDE...
- 腾讯即将开源Kuikly:基于Kotlin的纯原生跨端解决方案
-
IT之家3月4日消息,腾讯日前在端服务网站发布预告,即将开源Kuikly跨端开发框架。预告海报介绍称,Kuikly是基于KotlinKMM技术、客户端开发友好的全新跨端解决方案,可...
- Python构建MCP服务器完整教程:5步打造专属AI工具调用系统
-
模型控制协议(ModelControlProtocol,MCP)是一种专为实现AI代理与工具解耦而设计的通信协议,为AI驱动应用程序的开发提供了高度的灵活性和模块化架构。通过MCP服务器,AI代...
- Python3使用diagrams生成架构图(python模块制作)
-
目录技术背景diagrams的安装基础逻辑关系图组件簇的定义总结概要参考链接技术背景对于一个架构师或者任何一个软件工程师而言,绘制架构图都是一个比较值得学习的技能。这就像我们学习的时候整理的一些Xmi...
- Python 失宠!Hugging Face 用 Rust 新写了一个 ML框架,现已低调开源
-
大数据文摘受权转载自AI前线整理|褚杏娟近期,HuggingFace低调开源了一个重磅ML框架:Candle。Candle一改机器学习惯用Python的做法,而是Rust编写,重...
- Python Web 框架(Python Web 框架)
-
Tornado、Flask、Django三个PythonWeb框架的主要区别和适用场景:特点/框架TornadoFlaskDjango类型异步非阻塞Web服务器和框架轻量级微框架全功能...
- 构建并发布你的自定义 Python 包(python如何创建自定义模块)
-
Python让你可以重用代码,并将代码分享给他人以节省时间和精力。所以,当你编写了一些方便的脚本,希望你的同事或其他人也能使用时,接下来该怎么做呢?这篇文章就来解决打包和分发的问题。我们将专注于将你...
- Python 应用开发框架 BeeWare 简明实用教程
-
1.BeeWare简介BeeWare是一个Python框架,用于开发跨平台原生应用。它支持Android、iOS、Windows、macOS和Linux,并提供原生用户体验。2.安装B...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- MVC框架 (46)
- spring框架 (46)
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- laravel框架 (46)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- JAVA集合框架 (47)
- mfc框架 (52)
- abb框架断路器 (48)
- grpc框架 (55)
- ppt框架 (48)
- 内联框架 (52)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)