你好,我是柳岸花开。
一、单元测试说明
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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
</plugin>
</plugins>
</build>
三、编写单元测试用例
1 JUnit 5 核心API
JUnit 5 官网
JUnit 5 User Guide
2 Mockito
2.1 简介
Mockito 是当前最流行的 单元测试 Mock 框架。采用 Mock 框架,我们可以 虚拟 出一个 外部依赖,降低测试 组件 之间的 耦合度,只注重代码的 流程与结果,真正地实现测试目的。
代码示例
public class ArticleManager {
private ArticleDatabase database;
private ArticleCalculator calculator;
private UserProvider userProvider;
....
}
@ExtendWith({MockitoExtension.class})
public class ArticleManagerTest extends SampleBaseTestCase {
@Mock
private ArticleCalculator calculator;
// note the mock name attribute
@Mock(name = "database")
private ArticleDatabase dbMock;
@Spy
private UserProvider userProvider = new ConsumerUserProvider();
@InjectMocks
private ArticleManager manager;
@Test
public void shouldDoSomething() {
//mock method behave for @Mock Object
when(calculator.someMethod(anyInt())).thenReturn("A value");
when(dbMock.someMethod(anyInt())).thenReturn("A value");
//mock method behave for @Spy Object
doReturn("foo").when(userProvider).getById(100001);
//call target test method 并断言执行结果
assertEquals(1,manager.initiateArticle());
// 验证Mock的方法被调用到了
verify(userProvider).getById(100001);
}
}
官方说明及文档:
Mockito framework site
doc:Mockito - mockito-core 5.6.0 javadoc
2.2 核心API
1 Mockito.mock() / @Mock: 创建 mock 对象
//Let's import Mockito statically so that the code looks clearer
import static org.mockito.Mockito.*;
//mock creation
List mockedList = mock(List.class);
//using mock object
mockedList.add("one");
mockedList.clear();
//verification
verify(mockedList).add("one");
verify(mockedList).clear();
**推荐使用@Mock注解方式**
public class ArticleManagerTest {
@Mock
private ArticleCalculator calculator;
@Mock
private ArticleDatabase database;
@Mock
private UserProvider userProvider;
}
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.
👇关注我,下期了解👇 SpringMVC源码 回复 222,获取Java面试题合集 关于我 一枚爱折腾的Java程序猿,专注Spring干货。把路上的问题记录下来,帮助那些和我一样的人。 好奇心强,喜欢并深入研究古天文。 崇尚 个人系统创建,做一些时间越长越有价值的事情。思考 把时间留下来 又 每刻都是新的。
本文由 mdnice 多平台发布