风雨同行 发表于 2024-7-21 09:18:26

一文说透Springboot单位测试

你好,我是柳岸花开。
一、单位测试阐明

1 单位测试的优点与基本原则

一个好的单位测试应该具备以下FIRST 原则和AIR原则中的任何一条:
单位测试的FIRST 规则
Fast 快速原则,测试的速度要比力快,
Independent 独立原则,每个测试用例应该互不影响,不依赖于外部资源。
Repeatable 可重复原则,同一个测试用例多次运行的效果应该是相同的
Self-validating 自我验证原则,单位测试可以自动验证,并不需要手工干预
Thorough 及时原则 单位测试必须纵然进行编写,更新,维护。包管测试用例随着业务动态变化
AIR原则
Automatic 自动化原则 单位测试应该是自动运行,自动校验,自动给出效果。
Independent 独立原则 单位测试应该独立运行,吧相互之间无依赖,对外无依赖,多次运行之间无依赖。
Repeatable 可重复原则 单位测试是可重复运动的,每次的效果都稳固可靠。
一个整套美满的单位测试可以保障后续的增添功能时,步伐迭代过程中,代码的逻辑准确性。验证步伐的输入和输出与最初设计一致。这对后续的集成测试等会提供巨大的帮助。同时也会有利于集成测试的顺利进行。
2 单位测试的粒度应该如何选择

关于单位测试的粒度,可以总结以下几点:
DAO层的单位测试: 对于基本CRUD,可以考虑跳过这一部门单位测试,而一些比力复杂的动态更新、查询等操纵,发起用使用H2去做模仿单位测试。
Service层的单位测试:基本上一个Service内里肯定会依赖很多其他的service(此处也发起将成员变量通过构造方法进行注入,以便于单位测试去Mock),此时发起我们将依赖其他service的方法用Mock替代,Service内里的一些数据库的操纵也进行Mock。如许可以包管service测试的独立性,不外对于逻辑复杂的方法大概要花很多时间在Mock上面。 假如发现需要Mock的方法过多,那么大概就需要考虑将要测试的方法是不是需要重构。
Controller(API)层的单位测试:主要偏重测试HTTP status在 200,400,500 等环境下的异常处置惩罚,request及response的转换等。由于其余部门的代码测试都已经在其对应的单位测试覆盖,那么此时可以Mock绝大部门Serivce层中的方法。
一样平常工具类的单位测试:一些工具类内里包含了比力多的逻辑,所以需要尽大概考虑多种环境下测试用例。
How to write good tests · mockito/mockito Wiki · GitHub
二、引入单位测试组件

pom.xml
<dependency><br /><br /><groupId>org.springframework.boot</groupId><br /><br /><artifactId>spring-boot-starter-test</artifactId><br /><br /><scope>test</scope><br /><br /></dependency><br /><br /><build><br /><br /><plugins><br /><br /><plugin><br /><br /><groupId>org.apache.maven.plugins</groupId><br /><br /><artifactId>maven-surefire-plugin</artifactId><br /><br /></plugin><br /><br /><plugin><br /><br /><groupId>org.apache.maven.plugins</groupId><br /><br /><artifactId>maven-failsafe-plugin</artifactId><br /><br /></plugin><br /><br /></plugins><br /><br /></build><br />三、编写单位测试用例

1 JUnit 5 核心API

JUnit 5 官网
JUnit 5 User Guide
2 Mockito

2.1 简介

Mockito 是当前最流行的 单位测试 Mock 框架。采用 Mock 框架,我们可以 假造 出一个 外部依赖,低落测试 组件 之间的 耦合度,只注意代码的 流程与效果,真正地实现测试目标。
代码示例
<br />public class ArticleManager {<br /><br />private ArticleDatabase database;<br /><br />private ArticleCalculator calculator;<br /><br />private UserProvider userProvider;<br /><br />....<br /><br />}<br /><br />@ExtendWith({MockitoExtension.class})<br /><br />public class ArticleManagerTest extends SampleBaseTestCase {<br /><br />@Mock<br /><br />private ArticleCalculator calculator;<br /><br />// note the mock name attribute<br /><br />@Mock(name = "database")<br /><br />private ArticleDatabase dbMock;<br /><br />@Spy<br /><br />private UserProvider userProvider = new ConsumerUserProvider();<br /><br />@InjectMocks<br /><br />private ArticleManager manager;<br /><br />@Test<br /><br />public void shouldDoSomething() {<br /><br />//mock method behave for @Mock Object<br /><br />when(calculator.someMethod(anyInt())).thenReturn("A value");<br /><br />when(dbMock.someMethod(anyInt())).thenReturn("A value");<br /><br />//mock method behave for @Spy Object<br /><br />doReturn("foo").when(userProvider).getById(100001);<br /><br />//call target test method 并断言执行结果<br /><br />assertEquals(1,manager.initiateArticle());<br /><br />// 验证Mock的方法被调用到了<br /><br />verify(userProvider).getById(100001);<br /><br />}<br /><br />}<br />官方阐明及文档:
Mockito framework site
doc:Mockito - mockito-core 5.6.0 javadoc
2.2 核心API

1 Mockito.mock() / @Mock: 创建 mock 对象
<br />//Let's import Mockito statically so that the code looks clearer<br /><br />import static org.mockito.Mockito.*;<br /><br />//mock creation<br /><br />List mockedList = mock(List.class);<br /><br />//using mock object<br /><br />mockedList.add("one");<br /><br />mockedList.clear();<br /><br />//verification<br /><br />verify(mockedList).add("one");<br /><br />verify(mockedList).clear();<br /><br />**推荐使用@Mock注解方式**<br /><br />public class ArticleManagerTest {<br /><br />@Mock<br /><br />private ArticleCalculator calculator;<br /><br />@Mock<br /><br />private ArticleDatabase database;<br /><br />@Mock<br /><br />private UserProvider userProvider;<br /><br />}<br />2 Mockito.when() / BDDMockito.given() mock 方法效果
具体用法,看这个两个类上的注释
3 Mockito.spy() / @Spy 仿造真实对象的部门方法
具体阐明及用法看 Mockito.spy() 方法的注释
4 @InjectMocks 将 @Moc @Spy 仿造的对象注入到被测试对象
具体用法,看InjectMocks类上的注释
注意:
有些依赖没法用@InjectMocks来自动注入,可以通过引入ReflectionTestUtils,解决依赖注入的问题。
ReflectionTestUtils.setField(controller,"service",service);
5 Mockito.verify() 验证仿造举动被执行
看verify系列方法注释
3 spring test

Testing :: Spring Framework
为基于spring框架开发的应用的测试提供了一系列工具封装,让我们能更轻便编写测试用例。
3.1 Unit Testing

Unit Testing :: Spring Framework
真正的单位测试通常运行得非常快,由于不需要设置运行时底子设施。强调真正的单位测试作为开发方法的一部门可以进步您的生产力。您大概不需要测试章节的这一部门来帮助您为基于ioc的应用步伐编写有用的单位测试。然而,对于某些单位测试场景,Spring框架提供了模仿对象和测试支持类,这些将在本章中形貌。
Mock Objects
Spring包含很多专门用于mock的包,模仿这些对象:
Environment
JNDI
Servlet API
Spring Web Reactive
测试支持类
AopTestUtils 用于获取被署理对象
ReflectionTestUtils 用于对 非 public的属性等设置值
TestSocketUtils is a simple utility for finding available TCP ports on localhost for use in integration testing scenarios.
3.2 Integration Testing

Spring集成测试模块的目标
Spring的集成测试支持有以下主要目标:
To manage Spring IoC container caching between tests.
To provide Dependency Injection of test fixture instances.
To provide transaction management appropriate to integration testing.
To supply Spring-specific base classes that assist developers in writing integration tests.
管理测试之间的Spring IoC容器缓存。
提供测试fixture实例的依赖注入。
提供得当集成测试的事件管理。
提供特定于spring的基类,帮助开发人员编写集成测试。
Context Management and Caching
Spring TestContext框架提供了Spring ApplicationContext实例和WebApplicationContext实例的一致加载,以及这些上下文的缓存。对加载上下文的缓存支持很重要,由于启动时间大概成为一个问题。
Dependency Injection of Test Fixtures 测试装置的依赖注入
当TestContext框架加载应用步伐上下文时,它可以通过使用依赖注入选择性地配置测试类的实例。这提供了一种方便的机制,可以通过使用应用步伐上下文中的预配置bean来设置测试fixture。这里的一个强大的好处是,您可以跨各种测试场景重用应用步伐上下文(比方,用于配置spring管理的对象图、事件署理、数据源实例等),从而克制了为单个测试用例复制复杂测试fixture设置的需要。
Transaction Management
在访问真实数据库的测试中,一个常见的问题是它们对持久性存储状态的影响。纵然在使用开发数据库时,对状态的更改也大概影响将来的测试。此外,很多操纵(比方插入或修改持久数据)不能在事件之外执行(或验证)。
TestContext框架解决了这个问题。默认环境下,框架为每个测试创建并回滚一个事件。您可以编写可以假定存在事件的代码。假如在测试中调用事件署理对象,则根据其配置的事件语义,它们的举动准确。另外,假如一个测试方法在为测试管理的事件中运行时删除了所选表的内容,那么该事件在默认环境下回滚,并且数据库返回到执行测试之前的状态。事件性支持是通过使用在测试的应用步伐上下文中界说的PlatformTransactionManager bean提供给测试的。
假如您想要提交事件(不常见,但在您想要填充或修改数据库的特定测试时偶尔有用),您可以告诉TestContext框架使用@Commit注释导致事件提交,而不是回滚。
3.3 JDBC Testing Support

JDBC 测试支持提供了如下两种:
JdbcTestUtils工具类
JdbcTestUtils provides the following static utility methods.
countRowsInTable(..): Counts the number of rows in the given table.
countRowsInTableWhere(..): Counts the number of rows in the given table by using the provided WHERE clause.
deleteFromTables(..): Deletes all rows from the specified tables.
deleteFromTableWhere(..): Deletes rows from the given table by using the provided WHERE clause.
dropTables(..): Drops the specified tables.
Embedded Databases
The spring-jdbc module provides support for configuring and launching an embedded database, which you can use in integration tests that interact with a database. For details, see Embedded Database Support and Testing Data Access Logic with an Embedded Database.
页: [1]
查看完整版本: 一文说透Springboot单位测试