Java单元测试如何写
单元测试的理念其实⼀直是编程的⼀部分。我们第⼀次编写计算机程序时,肯定会输⼊⼀些样本数据,查看其是否按照你的期望执⾏。如果结果不符合预期,你肯定在代码⾥穿插过⼤量的System.out.println,确保每个原⼦节点都符合预期。这个过程其实就是把复杂问题拆解成原⼦化的问题、逐⼀攻破的过程。单元测试的⽬的也⼀样,是保障软件程序中每个最⼩单位的正确性,从⽽保障由最⼩单位构建起来的复杂系统的正确性。
元测试
一、什么是单元测试
维基百科是这样描述的: 在计算机编程中,单元测试又称为模块测试,是针对程序模块来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类、抽象类、或者派生类中的方法。
单元测试的理念其实⼀直是编程的⼀部分。我们第⼀次编写计算机程序时,肯定会输⼊⼀些样本数据,查看其是否按照你的期望执⾏。如果结果不符合预期,你肯定在代码⾥穿插过⼤量的System.out.println,确保每个原⼦节点都符合预期。这个过程其实就是把复杂问题拆解成原⼦化的问题、逐⼀攻破的过程。单元测试的⽬的也⼀样,是保障软件程序中每个最⼩单位的正确性,从⽽保障由最⼩单位构建起来的复杂系统的正确性。
二、单元测试的意义
2.1. 验证代码逻辑
对于一个代码模块,编写单元测试的过程,就是对代码逻辑进行重新 Review 的过程;而执行单元测试的过程,就是验证代码是否按既定逻辑运行的过程。
2.2 减少代码缺陷
我们的工程都是分层分模块的,每个模块都是独立的逻辑部分。通过单元测试保障工程各个“零件”按“规格”(需求)执行,就能保证整个“机器”(项目)运行正确,最大限度地减少 bug。
2.3. 促进代码设计
在编写单测的过程中,如果发现单测代码非常难写,一般表明被测试的代码包含了太多的依赖或职责,需要反思代码的合理性,进而促进代码设计的优化。
2.4. 便于缺陷定位
由于单元规模较小,复杂性较低,因而发现错误后容易隔离和定位,有利于代码调试工作。
2.5. 增强代码信心
写完代码,单元测试通过,虽说单元测试并不能百分之百保证代码完全正确运行,但起码大部分测过的逻辑都是可用的,这会增强我们的信心,也会增加工作成就感。
三、测试分类
-
单元测试:针对的是软件设计的最小单元--程序模块(面向过程中是函数、过程;面向对象中是类。)
-
集成测试:针对的是通过了单元测试的各个模块所集成起来的组件进行检验 (各个模块集成后所实现的功能. )
开发同学自己编写的,对外部环境(数据库、文件系统、外部系统、消息队列等)有真实调用的测试就是集成测试。
-
系统测试:针对的是集成好的软件系统,作为整个计算机系统的一个元素,与计算机硬件\外设\某些支持软件\数据和人员等其他系统元素结合在一起,要在实际的运行环境中,对计算机系统进行一系列的集成测试和确认测试.
3.1 各种测试对比
单元测试 | 集成测试 | 系统级别测试 | |
---|---|---|---|
编写人员 | 开发 | 开发 | 开发 / 测试 |
编写场地 | 生产代码仓库内 | 生产代码仓库内 | 生产代码仓库内 / 生产代码仓库外 |
编写时间 | 代码发布前 | 代码发布前 | 代码发布前 / 代码发布后 |
编写成本 | 低 | 中 | 高 |
编写难度 | 低 | 中 | 高 |
反馈速度 | 极快,秒级 | 较慢,分钟级 | 慢,天级别 |
覆盖面积 | 代码行覆盖60-80% 分支覆盖40-60% | 功能级别覆盖HappyPath | 核心保障链路 |
环境依赖 | 代码级别,不依赖环境 | 依赖日常或本地环境 | 依赖预发或生产环境 |
外部依赖模拟 | 全部模拟 | 部分模拟 | 不模拟,完全使用真实环境 |
四、Java 单元测试基础知识
4.1 前言
单元测试的理念其实⼀直是编程的⼀部分。我们第⼀次编写计算机程序时,肯定会输⼊⼀些样本数据,查看其是否按照你的期望执⾏。如果结果不符合预期,你肯定在代码⾥穿插过⼤量的System.out.println,确保每个原⼦节点都符合预期。这个过程其实就是把复杂问题拆解成原⼦化的问题、逐⼀攻破的过程。单元测试的⽬的也⼀样,是保障软件程序中每个最⼩单位的正确性,从⽽保障由最⼩单位构建起来的复杂系统的正确性。
4.2 Java 单元测试技巧之 PowerMock
4.2.1 准备环境
PowerMock 是一个扩展了其它如 EasyMock 等 mock 框架的、功能更加强大的框架。PowerMock 使用一个自定义类加载器和字节码操作来模拟静态方法、构造方法、final 类和方法、私有方法、去除静态初始化器等等。
引入 PowerMock 包 为了引入 PowerMock 包,需要在 pom.xml 文件中加入下列 maven 依赖:
4.2.2 引入 PowerMock 包
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.3.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
4.3.3 集成 SpringBoot 项目
在 SpringBoot 项目中,需要在 pom.xml 文件中加入 JUnit 的 maven 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
单元测试案例
接口
@Controller
public class DbController {
@Resource
private DbService dbService;
@GetMapping("/db")
public String getResult(String request) {
return dbService.getDate(request);
}
}
业务代码
@Service
public class DbServiceImpl implements DbService {
@Override
public String getDate(String request) {
return "hello world";
}
}
@RunWith(MockitoJUnitRunner.class)
class SpringTestApplicationTests {
@Mock
private DbService dbService;
@Mock
DbMapper dbMapper;
@InjectMocks
DbController demoController;
@Test
public void testGetDate_success_getFrom() {
MockitoAnnotations.openMocks(this);
// Arrange - 定义 mock 行为
Mockito.doReturn("ExpectedResults").when(dbService).getDate("Request");
// Act - 调用被测试方法
String result = demoController.getResult("Request");
Mockito.verify(dbService).getDate("Request");
Mockito.verifyNoMoreInteractions(dbService);
// Assert - 验证结果
Assert.assertEquals("ExpectedResults", result);
}
}
4.3.5 mock 语句
public static <T> T mock(Class<T> classToMock)
用途:可以用于模拟指定类的对象实例。
4.3.6 spy 语句
public static <T> T spy(T... reified)
如果一个对象,我们只希望模拟它的部分方法,而希望其它方法跟原来一样,可以使用 PowerMockito.spy 方法代替 PowerMockito.mock 方法。于是,通过 when 语句设置过的方法,调用的是模拟方法;而没有通过 when 语句设置的方法,调用的是原有方法。
4.3.7 when 语句
用途:用于模拟对象方法,先执行原始方法,再返回期望的值、异常、应答,或调用真实的方法。
返回期望值
when(dbService.getDate("Request")).thenReturn("hello world");
返回期望异常
when(dbService.getDate("some arg"))
.thenThrow(new RuntimeException())
.thenReturn("foo");
返回期望应答
when(dbService.getDate("Request")).thenAnswer(invocation -> "ExpectedResults");
调用真是方法
when(dbService.getDate("Request")).thenCallRealMethod();
doReturn().when()模式
用用于模拟对象方法,直接返回期望的值、异常、应答,或调用真实的方法,无需执行原始方法。
返回期望值
Mockito.doReturn("ExpectedResults").when(dbService).getDate("Request");
返回期望异常
Mockito.doThrow(new RuntimeException()).when(dbService).getDate("some arg");
返回期望应答
Mockito.doAnswer(invocation -> "foo").when(dbService).getDate("some arg");
模拟无返回值
Mockito.doNothing().when(dbService).getDate("some arg");
调用真实方法
Mockito.doCallRealMethod().when(dbService).getDate("some arg");
两种模式的主要区别
两种模式都用于模拟对象方法,在 mock 实例下使用时,基本上是没有差别的。但是,在 spy 实 例 下使用时, when().thenReturn() 模 式 会 执 行 原 方法,而doReturn().when()模式不会执行原方法。
4.3.8 参数匹配器
在执行单元测试时,有时候并不关心传入的参数的值,可以使用参数匹配器。
参数匹配器(any)
Mockito 提 供 Mockito.anyInt() 、 Mockito.anyString 、Mockito.any(Classclazz)等来表示任意值。
参数匹配器(eq)
当我们使用参数匹配器时,所有参数都应使用匹配器。 如果要为某一参数指定特定值时,就需要使用 Mockito.eq()方法。
更多推荐
所有评论(0)