JUnit教程:SpringBoot项目单元测试从入门到实战(附代码示例)

admin 综合编程开发技术 3


做 Spring Boot 开发的朋友,是不是总被老大催 “写完功能记得加单元测试”?但拿起 JUnit 就发懵,不知道怎么下手?测试类怎么写?Service 层和 Controller 层测试有啥区别?别愁,今天兔子哥就带大家从入门到实战,手把手教你在 Spring Boot 项目里用 JUnit 写单元测试,新手跟着做就能学会,还附代码示例呢!

一、先明白:为啥 Spring Boot 项目非要写单元测试?


可能有朋友觉得,功能跑通了就行,写测试纯粹是浪费时间。但你想啊,要是改了一段代码,之前的功能突然崩了都不知道,上线后才发现问题,那麻烦可就大了。单元测试就像个 “报警器”,能帮你提前发现这些问题。
尤其在 Spring Boot 项目里,用 JUnit 写测试好处多:
  • 保证代码质量,改代码时心里有底
  • 团队协作时,别人改了你的代码,测试通不过就会提醒
  • 很多公司 CI/CD 流程里,测试不过根本不让上线,躲都躲不掉

所以啊,别再抵触单元测试了,学会了反而能省不少事。咱们今天用的是 JUnit 5,这是目前最常用的版本,Spring Boot 2.2 以上默认支持,不用额外配太多东西,很方便。


二、环境搭起来:3 步搞定 JUnit 5 配置


其实 Spring Boot 项目里加 JUnit 测试特别简单,几乎不用自己瞎折腾,跟着这几步来:
第一步:新建 Spring Boot 项目时,勾选 “Spring Boot Starter Test” 依赖。如果是老项目,就在 pom.xml 里加这段(Maven 项目):
xml
<dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-testartifactId><scope>testscope>dependency>

这个依赖里已经包含了 JUnit 5、Mockito 这些常用工具,不用单独引了,省事儿吧?
第二步:项目结构里,src/test/java 目录就是放测试类的地方,包名最好和 src/main/java 里的对应,比如 main 里是 com.example.demo.service,test 里就建 com.example.demo.service,找起来方便。
第三步:新建测试类,类名一般是 “被测试类名 + Test”,比如 UserService 的测试类就叫 UserServiceTest,一目了然。
有朋友会问:不用配别的了?真不用,Spring Boot 的自动配置已经帮咱们搞定了大部分东西,新手直接写测试方法就行。


三、最基础的测试:先从 Service 层下手


Service 层是业务逻辑核心,测试起来也相对简单,咱们先拿它练手。假设咱们有个 UserService,里面有个根据 ID 查用户的方法:
java运行
@Servicepublic class UserService {public User getUserById(Long id) {// 模拟查询逻辑,实际项目里可能调数据库if (id == 1L) {return new User(1L, "张三");}return null;}}

对应的测试类该怎么写呢?看示例:
java运行
// 标记这是Spring Boot测试@SpringBootTestpublic class UserServiceTest {// 注入要测试的Service@Autowiredprivate UserService userService;// 标记这是一个测试方法@Testpublic void testGetUserById() {// 调用要测试的方法User user = userService.getUserById(1L);// 断言结果是否符合预期,这步很重要!// 确保用户不为空assertNotNull(user);// 确保用户名正确assertEquals("张三", user.getName());}}

这里面有几个关键点要记牢:
  • @SpringBootTest:告诉 Spring Boot 这是个测试类,会加载上下文
  • @Test:标记这是个测试方法,JUnit 会自动执行
  • 断言方法:比如 assertNotNull、assertEquals,用来判断结果对不对,测试过不过全看这个

写完后,右键点 “Run Test”,如果看到绿色对勾,说明测试通过;要是红色叉号,就说明哪出问题了,得检查代码。


四、Controller 层测试:稍微复杂点,但有套路


Controller 层要处理 HTTP 请求,测试起来比 Service 层多一步:模拟接口调用。咱们用 MockMvc 来做,这东西能模拟发送 GET、POST 请求,不用启动服务器。
假设有个 UserController:
java运行
@RestController@RequestMapping("/users")public class UserController {@Autowiredprivate UserService userService;@GetMapping("/{id}")public ResponseEntity<User> getUser(@PathVariable Long id) {User user = userService.getUserById(id);if (user == null) {return ResponseEntity.notFound().build();}return ResponseEntity.ok(user);}}

测试类这么写:
java运行
@SpringBootTest// 启用MockMvc@AutoConfigureMockMvcpublic class UserControllerTest {@Autowiredprivate MockMvc mockMvc;// 测试GET请求@Testpublic void testGetUser() throws Exception {// 模拟发送GET请求到/users/1mockMvc.perform(get("/users/1").contentType(MediaType.APPLICATION_JSON))// 期望返回200状态码.andExpect(status().isOk())// 期望返回的JSON里,name字段是“张三”.andExpect(jsonPath("$.name").value("张三"));}// 测试查询不存在的用户@Testpublic void testGetUserNotFound() throws Exception {mockMvc.perform(get("/users/99").contentType(MediaType.APPLICATION_JSON))// 期望返回404状态码.andExpect(status().isNotFound());}}

这里的 **@AutoConfigureMockMvc** 是关键,能自动配置 MockMvc。测试方法里,perform () 用来发请求,andExpect () 用来验证响应,比如状态码、返回内容啥的。
有朋友会问:Controller 调用了 Service,要是 Service 还没写完,测试不就卡壳了?这时候可以用 Mockito 模拟 Service 的返回,不用依赖真实的 Service 实现,这个后面咱们再细讲。


五、JUnit 4 和 5 的注解别弄混,一张表分清


很多教程里还在用 JUnit 4,新手容易把注解搞混,兔子哥整理了常用注解的对比,记牢了别弄错:
功能JUnit 4JUnit 5说明
测试方法标记@Test@Test作用一样,但 JUnit 5 的 @Test 没属性
测试前执行@Before@BeforeEach每个测试方法前都执行
测试后执行@After@AfterEach每个测试方法后都执行
类加载前执行@BeforeClass@BeforeAll整个测试类只执行一次,方法要静态
类销毁后执行@AfterClass@AfterAll整个测试类只执行一次,方法要静态
忽略测试@Ignore@Disabled标记某个测试方法不执行

比如 JUnit 4 里的 @Before,在 JUnit 5 里要写成 @BeforeEach,别写错了,不然测试跑不起来,还不知道问题出在哪。


六、实战中常遇到的问题,兔子哥教你怎么解


写测试时肯定会遇到各种小问题,这些都是新手常踩的坑,提前知道怎么解能省不少时间:

问题 1:测试方法里注入的 Service 是 null?


这大概率是没加 @SpringBootTest 注解,或者测试类的包名和主程序差太远,Spring 找不到要注入的 Bean。解决办法:检查 @SpringBootTest 有没有加,包名尽量和 main 里的保持一致,比如 main 是 com.example.demo,test 就别写成 com.test,差太远 Spring 扫不到。

问题 2:想测试私有方法,怎么调用?


其实单元测试一般不建议测私有方法,应该通过调用公有方法来间接测试。实在要测,可以用反射,但太麻烦了。兔子哥的建议是:如果私有方法逻辑复杂,不如把它抽到一个工具类里,写成公有方法,这样就好测试了,代码也更清晰。

问题 3:测试通过了,但实际运行有问题?


这说明测试没写到位,可能只测了正常情况,没考虑异常场景。比如查询用户,不仅要测 ID 存在的情况,还要测 ID 为 null、ID 不存在的情况,多测几种场景,才能保证代码靠谱。


七、兔子哥的一点心得,新手可以听听


写单元测试这事儿,刚开始确实觉得麻烦,尤其是赶项目的时候,总想着先把功能写完再说。但后来吃过几次上线后出 bug 的亏,就明白测试的重要性了。
其实练熟了之后,写测试挺快的,甚至能帮你提前发现代码里的漏洞。比如写测试时发现某个方法依赖太多外部资源,就知道该优化代码结构了。
新手朋友别一开始就追求测试覆盖率 100%,先从核心业务逻辑开始,比如 Service 层的主要方法,Controller 的核心接口,慢慢积累经验。用 JUnit 5 配合 Spring Boot 的测试支持,真的没那么难,多写几个例子就顺手了。
希望这篇教程能帮到正在学单元测试的你,别害怕,动手写起来,你会发现测试其实挺有意思的,还能让你的代码更靠谱。

标签: org.springframework.boot spring-boot-starter-test

发布评论 1条评论)

  • Refresh code

评论列表

2025-10-25 03:50:15

实用易懂,系统全面,指导实践。