Spring单元测试(一)入门与实践
问题:如何快速测试,而不是每次测试都要重启应用?
目标:尽量只测试局部代码
不同的测试
软件工程中分为:单元测试、集成测试、功能测试、系统测试。其中功能测试和系统测试一般是测试人员的责任,但单元测试和集成测试则必须由开发人员保证。
- 功能测试:检查是否满足需求说明书中确定的各项需求。
- 系统测试:与系统其他功能组合在一起进行测试
- 单元测试:检查开发者编写的一小段代码,以确认改动是否正确。
一个功能往往会调用多个接口方法实现,在单元测试时,需要屏蔽掉这些外在接口方法的依赖,将测试焦点集中到目标功能代码上。比如改动了底层工具类 DateUtils 的公共方法。
这时模拟就是最有力的工具,模拟的是功能的使用过程,假定外在接口方法正常返回的情况下验证目标代码的正确性。常见的模拟工具有 EasyMock、JMock、Mockito。
单元测试最重要的是可重复性,不可重复性的单元测试是没有价值的。
单元测试和开发工作是同时进行的,有时候甚至是在开发工作之前。
- 集成测试则是在功能开发代码开发完成之后,验证功能模块之间是否能工正常调用。
比如,当对 UserService 这个业务层类进行单元测试时,可以通过创建 UserDao 模拟对象,假设 DAO 层接口方法正常工作的情况下对 UserService 进行局部单元测试。
而对 UserService 进行集成测试时,则应该注入真实的 UserDao 对象进行。
所以一般来讲,集成测试面向的层面更高一些,一般对业务层和 Web 层进行集成测试;单元测试则面向一些功能单一的类,比如字符串格式化工具类、数据计算类等。
测试框架
Spring-Test 是 springframework 中的一个模块,是 Spring 提供的单元测试套件,可以方便的测试基于 Spring 框架的代码。
Spring-Test 是 Spring 框架的扩展,它专门用于帮助测试 Spring 应用,构建在 Junit 基础上,提供 Spring 容器相关的功能,比如测试 Spring Bean 的生命周期、Spring 配置加载等。
代码实践
1、引入依赖
<!-- 单元测试 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.10.RELEASE</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>3.11.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>3.11.2</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency>
2、接口测试
/** * Favor接口单元测试 */ @RunWith(MockitoJUnitRunner.class) public class FavorControllerTest { private MockMvc mockMvc; @Mock private FavorService favorService; @Mock private PersonService personService; @InjectMocks private FavorController favorController; @Before public void setup() { mockMvc = MockMvcBuilders.standaloneSetup(favorController).build(); // 模拟登录用户 MockedStatic<SessionUtils> sessionUtils = mockStatic(SessionUtils.class); User mockUser = new User(); mockUser.setId(1L); mockUser.setUserName("testUser"); sessionUtils.when(SessionUtils::currentUser).thenReturn(mockUser); } /** * 测试请求报文是否合法 * * @throws Exception */ @Test public void listPage_ValidQuery_ShouldReturnFavorList() throws Exception { // 模拟查询结果 List<Favor> favorList = new ArrayList<>(); Favor favor = new Favor(); favor.setId(1L); favor.setHoldPersonId(1L); favorList.add(favor); // 模拟业务层分页查询 when(favorService.findPage(any(PageQuery.class))).thenReturn(favorList); // 模拟查询人员 Person mockPerson = new Person(); mockPerson.setId(1L); when(personService.findById(1L)).thenReturn(mockPerson); // 模拟Http请求 String requestBody = "{\"terms\":{\"title\":\"someValue\"},\"page\":{\"pageNo\":1,\"pageSize\":10}}"; mockMvc.perform(post("/favor/listPage") .contentType(MediaType.APPLICATION_JSON) .content(requestBody)) .andExpect(status().isOk()); } }
为了行文简洁,忽略的 FavorController、FavorService 等部分代码的实现,不影响说明
注意:此代码是为了测试请求报文的合法性,即请求的响应码是否是 200,前提是接口在验证请求报文不合法时,会返回 200 以外的响应码。
不同的测试目标,代码也会不同。
3、DAO 层测试
/** * DAO层测试 */ @Slf4j @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:applicationContext.xml"}) @Transactional public class FavorItemDaoTest { @Autowired private FavorItemDao favorItemDao; @Before public void setUp() { // 如果需要,可以在这里进行任何通用设置 } /** * 测试查询条件 */ @Test public void findByCondition_related() { Map<String, Object> map = ImmutableMap.of("receiveGiveRelated", true, "receivePersonIdList", Arrays.asList(1L, 2L, 3L, 4L), "givePersonIdList", Arrays.asList(11L, 12L)); List<FavorItem> byCondition = favorItemDao.findByCondition(map); assertNotEquals(0, byCondition.size()); } }
这里的 Spring 配置文件建议尽量简洁,以加快测试方法执行。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)