前言
大家好我是聪。相信有不少的小伙伴喜欢写代码,但是对于单元测试这些反而觉得多此一举,想着我都在接口文档测过了!还要写什么单元测试!写不了一点!!
由于本人也是一个小小程序猿🙉,我以前也不喜欢写,最近给扔到了另一个 leader 的项目组里面,刚进去他给我下达的命令就是,你写的代码逻辑都要给我写上单元测试,而且要生成覆盖率报告给我!哇!我顿时就难受了,我之前都没有这么严格的,但是呢最近在我疯狂的写单元测试下,我发现我爱上了写单元测试。
单元测试的成长历程
第一次接触单元测试,还是在大学的测试课程里面。老师巴拉巴拉的讲,我虽然能听懂,但是不知道有啥用,课程作业反正做起来也没啥难度,反正能输出完事。
还有些接触单元测试就是平时学习视频的时候,跟着博主教的敲,这种简单的比如测试一个Mybatis 查询功能的单元测试,在我之前看来,soso 我直接用 swagger 文档请求岂不是更方便,代码都不用写咯。
直到我接触到了公司项目的单元测试,刚来公司看见之前的老项目单元测试都是成堆成堆的写,我也不是很理解,由于老项目的负责员工不知道换了多少代,新负责的都没调试过单元测试,直接代码改动,导致项目现在的单元测试很多环境配置都没及时更新,无法正常启动。当然也没人理会💥。
最近接触单元测试就是来到了新项目组这边的要求,我才重新看清了单元测试的重要性,请听我娓娓道来~
单元测试究竟能干嘛
单元测试究竟能干嘛这个问题时常疑惑着我,毕竟很多时候都是一个人从零到一进行开发或者接收项目能有好同事👋手把手教导,能大概了解整个项目的运行过程,直到我来到了一个用 AI 来生成业务的一个项目组里面,一切都发生了改变🌱。
我总结了单元测试的几点好处🔽:
-
能帮助接手项目的人快速了解项目的流程。
这点真的很重要,我新接手项目的时候,就只有同事简单的说了几句话,我甚至只能知道这项目是 AI 提问回答流程,然后我直接懵逼,不过通过单元测试,我一步步看来下,倒是能大概了解了项目整体的逻辑。
-
降低代码出错的概率。
每次在原有接口进行了一系列改动,在没有单元测试之前都要自己去接口文档手动尝试,甚至引来了测试一大堆指责。在使用单元测试之后,新增完的代码逻辑,直接新增对应功能的单元测试,直接全部执行,如果都能通过,这代码出问题的概率将会大大减少
-
不用担心影响数据库的数据。
运行单元测试对于很多环境配置都可以直接使用独立的数据库,比如说 MySQL 的数据可以使用 H2 数据库,而 Redis 又可以使用 RedisServer 来创建。
单元测试的小技巧
对于单元测试,我总结了一些我自己使用中的小技巧,这些小技巧的使用往往能有奇效!
抽象测试类
@SpringBootTest(
classes = { Application.class, },
webEnvironment = SpringBootTest.WebEnvironment.MOCK,
properties = {"spring.profiles.active=test"}
)
@Slf4j
@AutoConfigureMockMvc
public abstract class TestBase {
protected static final Long TEST_GROUP_ID = 1L;
@Autowired
protected MockMvc mockMvc;
}
这个测试类直接打上了需要使用的注解,后续使用直接继承即可,不用每次写上繁琐的注解,示例代码如下:
class Test extends TestBase {}
在 Spring 项目中使用 H2 代替 MySQL
- 引入 H2 依赖
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
- 通过 application-test.yml 来配置 H2 数据源
spring:
datasource:
driver-class-name: org.h2.Driver
schema: classpath:db/schema.sql #H2 数据库的建表语句
data: classpath:db/data.sql #H2 数据库的插入数据语句
url: jdbc:h2:mem:test #H2 数据库的默认连接地址
username: test
password: test
- 完成上面的步骤后,H2 数据库其实就已经替代掉 MySQL 了,但是要注意的是 MySQL 的 sql 语句与 H2 的有一丢丢的差别,语法可能并不适用,分享一个 IDEA 的插件就可以解决这个问题。
在单元测试中使用 Redis
有不少小伙伴总不喜欢在本地启动一个 Redis ,那么单元测试有什么解决办法呢!当然有!这就用到了 embedded-redis 的一个库,使用超级简单。
- 引入 embedded-redis 依赖
<dependency>
<groupId>it.ozimov</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.7.3</version>
<scope>test</scope>
<!-- 不排除掉slf4j的话 会冲突-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
- 直接在单元测试中编写
private static RedisServer redisServer;
@BeforeAll
public static void beforeAll() {
redisServer = RedisServer.builder().setting("maxheap 200m").port(6379).build();
redisServer.start();
}
Spring 单元测试 mock 数据
在我最近接收这个项目中调用 AI 花费是十分昂贵的,总不能单元每次都要调用 AI,不仅烧钱而且还有不确定性。有些需要 mock 的数据就可以使用 Mockito 这个强大的库来进行操作。
- 引入依赖(虽然说 SpringBoot 中会自带,但有时候我嫌弃他的版本不够高,但我又不能改 SpringBoot 依赖,就使用了覆盖)。
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.12.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>3.12.4</version>
<scope>test</scope>
</dependency>
-
mock 要代理的服务。
举个例子我要 mock 一个类的方法,首先可以直接使用
@MockBean
注解来代理,这个注解跟@Autowired
注解类似,也是让代理创建。接着就是让单元测试执行前,先将这个 Spring 代理对象 mock 创建它,给他指定返回的数据内容。示例代码如下:
@MockBean
private TestAgent testAgent;
@BeforeEach
public void beforeEach() {
Mockito.when(testAgent.answer(any(), any())).thenAnswer(invocation -> {return "预期结果"});
}
这里有几个注意的点:
1==> 这里的 any() 就是对应的输入任何数据,都要返回这个预期结果,其他的还可以自定义。
2==> 这里的代理只能返回一条数据或者里面自定义规则来返回不同数据,如果要每次都按顺序返回可以使用另一种方法 SideEffect 。
- 使用 SideEffect 来进行 mock 返回。
跟上面直接流程一样,但是不一样的是 thenAnswer
的后续处理,输入是一个 List 里面会按 List 内容的顺序依次返回。示例代码如下:
List<String> list = new ArrayList();
list.add("聪1");
list.add("聪2");
list.add("聪3");
Mockito.when(testAgent.answer(Mockito.any(), Mockito.any())).thenAnswer(new SideEffect<>(list));
不想启动 Spring 容器
不想启动 Spring 容器,但是代码中用到了 Spring 的代理,SpringBoot 容器的启动十分的慢对于我的电脑,但是我要进行单元测试的部分其实并不涉及 Spring ,我直接进行 AI 请求访问而已,接着就可以使用我以下的方法,直接启动。
-
在单元测试中有移除 Spring 相关注解。
移除掉注解后,在类上面打上
@ExtendWith(MockitoExtension.class)
注解,示例代码如下:
@ExtendWith(MockitoExtension.class)
class Test {
....
}
-
对于交由 Spring 创建的对象进行手动创建。
示例代码如下:
// 不用使用注解
private TestAgent testAgent;
@BeforeEach
void setUp() {
testAgent = new TestAgent();
}
- 后面直接使用对象直接请求即可。
最后
我想我大抵是疯了,我喜欢上了写单元测试,单元测试写完根本停不下来,再未来我会总结更多的单元测试小方法,如果你们有自己总结的一些关于单元测试的方法,欢迎大家一起分享学习~ 最后最后!我是聪希望可以跟大家一起学习,我的 Github:github.com/lhccong 如果里面有你感兴趣的项目不妨给我点个星星⭐和关注🔥,未来我还会持续写新的好玩的小项目。