我自己接触到的一些商业或是开源的基于 SpringBoot 项目,它们大部分是没有测试代码的,test 文件夹只有脚手架初始化生成的那个测试类,跟不同的开发聊到这个话题,发现他们中的大部分没有写测试的习惯,或者是觉得写测试代码麻烦,主要还是依赖测试工程师做黑盒的测试。只做黑盒测试的话有一定的的局限性,一些比较***钻的案例可能就覆盖不到,而且相对来说人也比较容易出错、遗漏。而测试代码能解决其中很大一部分的问题,利用好单元测试和集成测试在某些情况下相对于直接通过 UI 进行测试是要更方便、节省时间的,所以想通过几篇博客来分享一下自己的测试实践

为什么要写测试(优点)

  1. 覆盖更多的边界条件,且随时都可以运行测试代码(一劳永逸)
  2. 缩小测试范围:测试某个方法只需要运行对应的测试代码,而不需要运行整个项目通过请求接口进行测试
  3. 对重构更友好,可以随时重构有集成测试的代码,不用担心打破原有的代码
  4. 其他人也可以通过测试快速地理清楚对应被测代码的主线逻辑(类似文档的作用,特别是复杂代码,通过测试能快速理解上手)
  5. 写测试的过程,给自己一个新的视角去审视代码结构的设计,有助于改善代码设计

当然代码方式的测试也并非完美无缺:测试代码增加编写和维护的成本,同时一些外部依赖也需要通过 Mock 的方式实现,这些都提高了整个测试编写的门槛。也倒逼我们思考更好地组织代码,减少依赖

另一个方面:测试对于重构也是至关重要的,随着对业务的理解越来越深刻,可以重构代码,抽象出了一些共性的逻辑,优化代码结构,但是如果没有相关测试,面对着旧代码就只能望而却步了

测试工具:JUnit 5, AssertJ,Mockito

spring-boot-starter-test 自带常用的测试工具:JUnit5AssertjMockito,可以直接使用

JUnit5

Junit 5 包含:

  • JUnit Platform:Test Engine
  • Jupiter:编程模型和拓展模型
  • Vintage:兼容老版本

JUnit 4 和 5 使用的包有所不同

// JUnit 4
import org.junit.Test;
import static org.junit.Assert.assertEquals; // JUnit 5
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

如果不考虑兼容 JUnit 4 的测试,我们可以直接在依赖中直接排除 JUnit 4 的依赖,这样也可以避免在使用的时候错误地引入 JUnit 4 的包

<dependencies>

    <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>{spring-boot-starter-test-version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency> </dependencies>

还有一点值得注意的是:JUnit 5 中 @RunWith 被 @ExtendWith 代替

AssertJ

AssertJ 是一种支持链式编程的断言库,相对于 JUnit 自带的断言,它提供了更多的方法,也提供了更好的断言不匹配时的信息展示

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; public class MyTest {
// 变量断言
@Test
public void test() {
String name = "Alice";
int age = 30; assertThat(name).isEqualTo("Alice");
assertThat(age).isGreaterThan(18).isLessThan(60);
} // List 断言
@Test
public void testList() {
List<String> list = Arrays.asList("foo", "bar", "baz")
assertThat(list).containsExactly("foo", "bar", "baz").hasSize(3);
} // Map 断言
@Test
public void testMap() {
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("orange", 3); assertThat(map).containsEntry("banana", 2); assertThat(map).containsKey("banana"); assertThat(map).containsValue(2);
} // hasNoNullFieldsOrProperties 来断言测试对象的每个属性都不为 null
@Test
public void testHasNoNullFieldsOrProperties() {
Person person = new Person("Alice", 30);
assertThat(person).hasNoNullFieldsOrProperties();
} // 异常断言
@Test
public void testDivideByZeroThrowsException() {
assertThatThrownBy(() -> {
int result = 1 / 0;
}).isInstanceOf(ArithmeticException.class)
.hasMessageContaining("/ by zero");
}
}

Mockito

Mockito 是一个 Java Mock 框架,用于创建各种类型的 Mock 对象,并设置 Mock 对象的行为

import static org.mockito.Mockito.*;

import org.junit.jupiter.api.Test;

public class MyTest {
// 创建 mock 对象
@Test
public void testCreateMock() {
List<String> list = mock(List.class);
} // 设置 mock 对象的行为
@Test
public void testMockBehavior() {
List<String> list = mock(List.class);
when(list.get(0)).thenReturn("foo");
when(list.size()).thenReturn(1);
} // 验证 mock 对象的方法调用
@Test
public void testMockVerification() {
List<String> list = mock(List.class);
list.add("foo");
list.add("bar");
verify(list).add("foo");
verify(list).add("bar");
// 验证调用方法的次数
verify(list, times(2)).add(anyString());
} // 模拟方法抛出异常
@Test
public void testMockException() {
List<String> list = mock(List.class);
doThrow(new RuntimeException()).when(list).clear();
assertThrows(RuntimeException.class, () -> list.clear());
}
}

也可以用注解来声明 Mock 对象,这样更清晰

import static org.mockito.Mockito.*;

import java.util.List;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; // 启用 Mockito 扩展
@ExtendWith(MockitoExtension.class)
public class MyMockitoTest { // 创建一个 List 类型的 Mock 对象
@Mock
List<String> mockList; // 使用 @InjectMocks,将会自动注入被测试类中所声明的 Mock 对象到这个对象中
@InjectMocks
MyService myService; @Test
public void testMock() {
// 模拟 Mock 对象的行为
when(mockList.get(0)).thenReturn("foo");
when(mockList.size()).thenReturn(1); String result = myService.doSomething(); assertEquals("foo", result); verify(mockList).get(0);
}
} class MyService { private List<String> list; public MyService(List<String> list) {
this.list = list;
} public String doSomething() {
return list.get(0);
}
}

参考资料

《重构 改善既有代码的设计》

有哪个开源项目,单元测试用例覆盖的比较全的?

业务代码写单元测试的最佳姿势是什么?

单元测试有落地效果好的团队吗?

Modern Best Practices for Testing in Java

Thoughts on efficient enterprise testing (1/6) - Sebastian Daschner

SpringBoot 测试实践 - 1:常用的工具的更多相关文章

  1. Docker与自动化测试及其测试实践

    Docker 与自动化测试 对于重复枯燥的手动测试任务,可以考虑将其进行自动化改造.自动化的成本在于自动化程序的编写和维护,而收益在于节省了手动执行用例的时间.简而言之,如果收益大于成本,测试任务就有 ...

  2. Golang项目的测试实践

    Golang项目的测试实践 最近有一个项目,链路涉及了4个服务.最核心的是一个配时服务.要如何对这个项目进行测试,保证输出质量,是最近思考和实践的重点.这篇就说下最近这个实践的过程总结. 测试金字塔 ...

  3. 9-MySQL DBA笔记-测试实践

    第9章 测试实践 在第8章中介绍了测试所需要的理论知识,本章将为读者讲述实际的测试过程.实际测试一般包括硬件测试.MySQL基准测试及应用服务压力测试,下面将分别讲述这三方面的内容.此外,测试工具的选 ...

  4. 携程酒店DevOps测试实践

    作者简介 王幸福,携程酒店研发部高级测试经理,负责无线自动化测试相关工作.在测试框架和平台研发.移动测试.DevOps等领域有着丰富的经验. 如今很多大型互联网公司.创新型企业都在积极地进行DevOp ...

  5. SharePoint 2013常用开发工具分享

    众所周知,一款好的开发工具不仅能提高项目开发效率,而且能够协助开发人员简化开发流程.本文汇总几款SharePoint 2013开发常用开发工具,希望能够对大家有所帮助.如果您有更好的工具,没有包含在本 ...

  6. 常用自动化测试工具介绍(支持B/S、C/S)

    一.功能测试工具1.QTP测试工具 全名HP QuickTest Professional software ,最新的版本为HP QuickTest Professional 11.0 QTP是qui ...

  7. Drupal常用开发工具(一)——Devel模块

    进行 Drupal 开发时有许多模块和工具可供使用,其中最常用的两项便是 Devel 及 Drupal for Firebug.本文和<Drupal常用开发工具(二)——Drupal for F ...

  8. SharePoint 2013常用开发工具

    SharePoint 2013常用开发工具分享 2014-04-01 00:59 by jv9, 589 阅读, 1 评论, 收藏, 编辑 众所周知,一款好的开发工具不仅能提高项目开发效率,而且能够协 ...

  9. springboot测试、打包、部署

    本文使用<springboot集成mybatis(一)>项目,依次介绍springboot测试.打包.部署. 大多数朋友是做后端的,也就是为其他系统或者前端UI提供Rest API服务. ...

  10. Linux常用网络工具:路由扫描之mtr

    除了上一篇<Linux常用网络工具:路由扫描之traceroute>介绍的traceroute之外,一般Linux还内置了另一个常用的路由扫描工具mtr. mtr在某些方面比tracero ...

随机推荐

  1. 2021-07-05:股票问题2。给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖

    2021-07-05:股票问题2.给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格.设计一个算法来计算你所能获取的最大利润.你可以尽可能地完成更多的交易(多次买卖 ...

  2. Abaqus结构仿真软件的非线性问题与解决方案

    ​无论是什么FEA 软件,想要获得非线性问题的一些解决方法始终没有那么简单.遇到问题是很常见的,那么下面就来看看Abaqus用户克服这一类问题的解决方法吧. 1. 简化模型 从简化模型开始,通过逐渐添 ...

  3. 如何在.net6webapi中实现自动依赖注入

    IOC/DI IOC(Inversion of Control)控制反转:控制反正是一种设计思想,旨在将程序中的控制权从程序员转移到了容器中.容器负责管理对象之间的依赖关系,使得对象不再直接依赖于其他 ...

  4. go语言字符与字符串相关

    ASCII ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁 字母的一套单字节编码系统 字符 本质上来 ...

  5. 基于Spark的大规模日志分析

    摘要:本篇文章将从一个实际项目出发,分享如何使用 Spark 进行大规模日志分析,并通过代码演示加深读者的理解. 本文分享自华为云社区<[实战经验分享]基于Spark的大规模日志分析[上进小菜猪 ...

  6. CMU15445 (Fall 2020) 数据库系统 Project#3 - Query Execution 详解

    前言 经过前两个实验的铺垫,终于到了执行 SQL 语句的时候了.这篇博客将会介绍 SQL 执行计划实验的实现过程,下面进入正题. 总体架构 一条 SQL 语句的处理流程可以归纳为: SQL 被 Par ...

  7. 一次Mybaits查询的源码分析

    很好奇Mybaits是怎么将xml和mapper对应起来的,用一段比较简单的demo去debug追踪一下源码看看 先用xml配置的方式,看懂了再去看注解的方式是怎么实现的 获取Mapper Mybai ...

  8. docker-compose单服务器部署ELK

    docker-compose 部署ELK 本项目采用的ELK版本为6.5.3,7.0+ 以上的版本部分配置不适合,请查看docker-compose多服务器部署ELK文章 目录结构 elk |--do ...

  9. [ARM 汇编]高级部分—性能优化与调试—3.4.1 性能分析与优化策略

    性能优化是嵌入式系统开发中的一个重要环节,尤其是在资源受限的环境下.性能优化的目标是提高代码执行速度.降低功耗和减少内存占用.在本章节中,我们将讨论性能分析与优化策略,并通过实例来学习如何应用这些策略 ...

  10. MongoDB从入门到实战之.NET Core使用MongoDB开发ToDoList系统(8)-Ant Design Blazor前端框架搭建

    前言 前面的章节我们介绍了一些值得推荐的Blazor UI组件库,通过该篇文章的组件库介绍最终我选用Ant Design Blazor这个UI框架作为ToDoList系统的前端框架.因为在之前的工作中 ...