尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
SpringBoot Test分层实战:从单元测试到集成测试的避坑指南
写测试这事团队里一直有一个奇怪的现象一说“要写单元测试”大家第一反应就是给类加上SpringBootTest注解启动整个Spring容器然后调用接口跑通就算完事。这种测试跑得慢、稳定性差而且真正出问题的时候它什么都查不出来。我见过很多项目测试覆盖率报了80%结果每次发版还是靠人肉回归原因就在于这些测试全在同一个层面——它们没有真正测到代码的逻辑分支只是在验证“容器能启动”而已。SpringBoot Test这套体系从底层设计上就比“启动整个应用再调接口”要精细得多。它把测试拆成了多层每一层都有对应的工具和注解从纯单元级的Mock测试到加载部分上下文的切片测试再到贴近生产环境的集成测试粒度完全不同。这篇文章不打算泛泛罗列注解我想结合自己实际维护项目和排查测试问题的经验把SpringBoot Test里真正影响测试质量的关键点讲清楚——从注解机制到分层实战再到版本升级的坑、事务回滚的失效场景一次聊透。1. 为什么SpringBoot Test不只是“SpringBootTest”一个注解很多人对SpringBoot Test的理解就停留在“启动上下文”上这是最大的误区。SpringBoot Test真正的价值是它提供了一套完整的测试骨架从最小的单元测试到全链路的集成测试每一层都有对应的技术方案。1.1 从一次线上故障说起测试覆盖的假象我之前接手过一个老项目代码里写了大几十个SpringBootTest测试类看起来覆盖得挺全。但后来线上出了一个很隐蔽的问题——积分模块在某个用户批次数据下会出现重复发放根因是Service层里一段并发判断逻辑写错了当同一用户的两个请求同时到达时两条线程都通过了“是否已发放”的校验。这个逻辑用单元测试很容易抓出来只需要Mock掉持久层接口分别构造“已发放”和“未发放”两种状态再模拟两次并发调用就能复现。但当时项目里没有Service层的单元测试全是启动容器后直接调接口的“伪单元测试”测试数据被真实地写进了数据库两个请求一前一后执行根本无法同步触发竞态条件bug就这么漏过去了。这个例子说明了什么SpringBootTest加载完整上下文属于集成测试的范畴它验证的是组件之间的协作是否正确。但如果你把所有测试都写成这种形式就牺牲了单元测试的两个核心优势一是速度快能频繁运行二是定位准哪个方法出错立刻就能指出来。单元测试、切片测试、集成测试三者用途不同不该互相替代。1.2 SpringBoot Test的三层体系与选择逻辑在SpringBoot Test这套体系里测试大致可以分成三层单元测试不启动Spring容器直接用JUnit Mockito测某个类的方法逻辑。Service层的业务规则、工具类的算法逻辑、各种if-else分支都该在这一层覆盖。切片测试只加载Web层、数据层或JSON处理层的一部分Bean用WebMvcTest、DataJpaTest、JsonTest等注解实现。它比单元测试多了一些真实组件的协作验证又不需要启动整个上下文。集成测试用SpringBootTest加载完整上下文叠加Testcontainers或真实中间件验证跨模块链路。选型的核心逻辑就一句话**测试的上下文越大跑得越慢定位问题越难。**能用单元测试覆盖的逻辑不要为了“省事”直接丢给集成测试。我在做项目规范时给团队定的红线是Service层核心业务规则必须有单元测试兜底Web层必须有切片测试验证参数绑定和响应结构跨服务或者跨中间件的链路才允许用集成测试。2. 核心注解与运行机制拆解2.1 SpringBootTest完整上下文加载了什么代价是什么SpringBootTest的作用是启动完整的Spring应用上下文让测试直接使用所有的Bean。它背后的机制是SpringBootTestContextBootstrapper通过SpringApplication创建并缓存一个ApplicationContext测试类里的Autowired依赖都能正常注入。这个注解有几个关键参数值得注意webEnvironment控制Web环境类型。MOCK是默认值它会创建一个模拟的ServletWebServerApplicationContext但不启动真实的Web服务器RANDOM_PORT会启动真实的服务器并分配随机端口适合需要测试完整HTTP请求的场景DEFINED_PORT和NONE分别对应指定端口和不启动Web环境。properties通过properties {spring.datasource.urljdbc:h2:mem:testdb}临时覆盖配置作用范围比application-test.yml更精确。classes指定配置类默认会从当前测试类所在的包向上查找SpringBootApplication。用SpringBootTest最容易忽略的问题是上下文数量。SpringBoot默认的测试上下文缓存器SpringBootContextLoader会缓存上下文如果一个项目里有多个不同的配置组合缓存就会失效重建直接导致整体测试时间成倍增加。我之前调整过一个项目的测试启动时间从20多分钟缩短到6分钟核心就是把测试类里的SpringBootTest(properties ...)配置统一收敛到application-test.yml保证同一个上下文尽量复用。2.2 切片测试启动半个容器而不是整个应用切片测试是SpringBoot Test里最被低估的能力。以WebMvcTest为例它只实例化Controller层相关的Bean——Controller、ControllerAdvice、WebMvcConfigurer等同时自动配置MockMvc而Service层和Repository层的Bean不会加载需要手动用MockBean声明。做个对比你就明白差异在哪里特性SpringBootTest MockMvcWebMvcTest上下文加载范围全部BeanController MVC相关配置启动时间秒级到十几秒百毫秒级Service依赖可真实注入也可Mock必须Mock定位能力组件协作问题Web层交互问题同理DataJpaTest只加载JPA相关的配置和Repository Bean默认使用内嵌数据库H2且每个测试方法自动回滚事务。JsonTest只负责测试JSON序列化和反序列化适合测DTO的字段映射。切片测试的意义不在于“快”本身而在于它把问题边界收敛了。如果Web层测试失败了你不需要去排查是不是某个Service里的逻辑出错因为Service层本来就是Mock的。这种隔离性对排错体验的提升非常明显。2.3 MockBean的演进从Maven坐标到弃用风波MockBean一直是SpringBoot Test里用得非常多的注解它可以把指定类型的Bean替换成Mockito的Mock对象在测试时方便地stub方法行为。但从SpringBoot 3.4.0开始官方在MockBean的Javadoc里标注了Deprecated推荐替代方案是org.springframework.test.context.bean.override.mockito.MockitoBean。这个变化很多人没注意到它其实反映了Spring框架的一个新方向MockBean的机制是通过MockitoPostProcessor在BeanPostProcessor阶段替换BeanDefinition实现上有点“重”而且对某些自定义BeanFactory后置处理器会不兼容。新的MockitoBean基于BeanOverrideHandler机制实现更轻量语义也更清晰。实际升级时要注意MockitoBean和MockBean的导入包路径不同// 旧写法 import org.springframework.boot.test.mock.mockito.MockBean; // 新写法 import org.springframework.test.context.bean.override.mockito.MockitoBean;如果你还在用SpringBoot 3.3或更早版本MockitoBean是不存在的只能用MockBean。升级到3.4及以后建议直接切到新注解。2.4 断言神器AssertJ让测试代码像人话SpringBoot项目默认集成了AssertJ我一直觉得这是SpringBoot Test体系里最实用的组件。对比一下// JUnit原生断言 assertEquals(200, response.getStatusCode().value()); assertTrue(result.isSuccess()); assertNotNull(result.getData()); // AssertJ链式断言 assertThat(response.getStatusCode().value()).isEqualTo(200); assertThat(result.isSuccess()).isTrue(); assertThat(result.getData()).isNotNull(); // 更复杂的对象断言 assertThat(order.getItems()) .hasSize(3) .extracting(OrderItem::getSkuId) .containsExactly(sku-001, sku-002, sku-003);我看到很多老项目的测试代码还在用一堆assertTrueif做判断可读性很差断言失败时也看不出具体是哪个字段出了问题。AssertJ的extracting和filteredOn在做集合断言时尤其好用配合as()描述信息跑挂的时候控制台输出一眼就能定位问题。3. 分层测试实战三种注解用法的完整范式3.1 Controller层MockMvc的请求构造与响应断言Controller层的测试重点在HTTP协议交互本身——参数绑定、校验规则、响应状态码、JSON结构。用WebMvcTest配合MockMvc是标准做法。一个典型的Controller测试长这样WebMvcTest(UserController.class) class UserControllerTest { Autowired private MockMvc mockMvc; MockitoBean private UserService userService; Test void shouldReturnUserWhenGetById() throws Exception { UserVO userVO new UserVO(1L, 张三, zhangsanexample.com); when(userService.getById(1L)).thenReturn(userVO); mockMvc.perform(get(/api/users/{id}, 1L) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath($.id).value(1)) .andExpect(jsonPath($.name).value(张三)) .andExpect(jsonPath($.email).value(zhangsanexample.com)); } Test void shouldReturnBadRequestWhenIdInvalid() throws Exception { mockMvc.perform(get(/api/users/{id}, -1L)) .andExpect(status().isBadRequest()); } }关键点有两个。第一jsonPath是MockMvc最强大的断言工具它直接解析响应JSON路径像$.data.items[0].skuId这样的表达式可以精确到嵌套对象。但要注意用JSONPath断言时如果字段值是中文有些低版本依赖会出现编码问题建议在application-test.yml里统一配置server.servlet.encoding.forcetrue。第二校验逻辑的测试用切片测试最合适。比如Validated注解加上NotBlank的DTO字段用MockMvc发送空值请求看是否返回400 Bad Request这比用完整上下文启动再调真实接口要快得多。但需要注意如果Controller的异常处理依赖RestControllerAdvice里的特定逻辑记得在WebMvcTest时把它也带上或者在测试类里Import进来。3.2 Service层不启动容器的单元测试才是核心Service层是业务逻辑最集中的地方也是单元测试性价比最高的地方。这里不需要SpringBootTest直接使用JUnit 5 Mockito就可以了。一个合格的Service层单元测试示例class OrderServiceTest { Mock private OrderRepository orderRepository; Mock private InventoryClient inventoryClient; InjectMocks private OrderService orderService; private static final Long USER_ID 42L; BeforeEach void setUp() { MockitoAnnotations.openMocks(this); } Test void shouldCreateOrderWhenStockEnough() { when(inventoryClient.getStock(1001L)).thenReturn(10); OrderCreateRequest request new OrderCreateRequest(1001L, 2); Order order orderService.createOrder(USER_ID, request); assertThat(order.getStatus()).isEqualTo(OrderStatus.CREATED); verify(orderRepository).save(any(Order.class)); verify(inventoryClient).deductStock(1001L, 2, USER_ID); } Test void shouldThrowExceptionWhenStockNotEnough() { when(inventoryClient.getStock(1001L)).thenReturn(1); OrderCreateRequest request new OrderCreateRequest(1001L, 2); assertThatThrownBy(() - orderService.createOrder(USER_ID, request)) .isInstanceOf(InsufficientStockException.class) .hasMessageContaining(库存不足); verify(orderRepository, never()).save(any(Order.class)); } }关于Mockito的使用我给三点建议when和verify分开看。when是stub定义的是“如果依赖返回什么被测代码走什么分支”verify是行为验证确认某个方法确实按期望被调用了。很多新手只写when不写verify结果方法内部根本走错了分支测试照样能过。用ArgumentCaptor捕获参数。相比any()捕获真实参数再逐字段断言能发现很多参数传递类的bug。比如上面的例子可以捕获传给orderRepository.save()的Order对象断言语状态和金额字段是否符合预期。InjectMocks虽然方便但有隐患。它是通过反射把Mock注入到被测类的字段里如果一个类有多个同类型的依赖或者依赖是通过构造器注入的InjectMocks的行为就不那么可控。我自己更倾向于在BeforeEach里手动new被测类并把Mock传进去虽然代码多了几行但依赖关系一目了然。3.3 Repository层DataJpaTest与内嵌数据库Repository层的测试重点是SQL语义、字段映射和分页排序。DataJpaTest默认扫描Entity和Repository接口自动配置嵌入式数据库每个测试方法在事务内执行并回滚。DataJpaTest class UserRepositoryTest { Autowired private UserRepository userRepository; Test void shouldFindUserByEmail() { User user new User(); user.setName(李四); user.setEmail(lisiexample.com); userRepository.save(user); OptionalUser found userRepository.findByEmail(lisiexample.com); assertThat(found).isPresent(); assertThat(found.get().getName()).isEqualTo(李四); } }这里有几个很容易踩的细节DataJpaTest默认不加载Component类的Bean如果Repository依赖某个自定义的Component比如自定义审计逻辑、加密字段转换器需要通过Import手动引入。内嵌数据库默认是H2但H2的SQL方言和MySQL/Oracle存在差异比如自增主键策略、函数名不同。如果项目用了比较复杂的原生查询用H2测可能会跑通但线上报错。这种情况下用Testcontainers跑真实的MySQL容器更可靠这块后面会展开。DataJpaTest里的测试方法默认是回滚的但如果测试里手动调用了entityManager.flush()或者触发了clear操作数据状态可能会产生影响。测试方法之间一定要保证数据隔离不要依赖某个测试先执行。4. 集成测试的硬骨头数据库、Redis、MQ与外部接口4.1 测试数据库选型H2便宜但Testcontainers更真实很多团队的集成测试用的是H2内存数据库理由是配置简单、启动快。但这种方案在生产环境的数据库是MySQL等到上线的时候才发现H2里验证过没事的SQL在MySQL里因为语法、索引、字符集问题翻车了。我个人的倾向是——能用Testcontainers就不用H2。Testcontainers通过Docker在测试时启一个真实数据库实例测试跑完自动销毁和生产的兼容性基本没有偏差。一个基于Testcontainers的测试配置Testcontainers SpringBootTest class UserServiceIntegrationTest { Container ServiceConnection static MySQLContainer? mysql new MySQLContainer(mysql:8.0.33); Autowired private UserRepository userRepository; Test void shouldSaveAndQueryUser() { // 业务逻辑 } }ServiceConnection是SpringBoot 3.1引入的它可以自动把容器信息映射到spring.datasource.url、spring.datasource.username等配置省去了手写DynamicPropertySource的繁琐代码。如果是低版本SpringBoot则得用DynamicPropertySource手动设置DynamicPropertySource static void datasourceProperties(DynamicPropertyRegistry registry) { registry.add(spring.datasource.url, mysql::getJdbcUrl); registry.add(spring.datasource.username, mysql::getUsername); registry.add(spring.datasource.password, mysql::getPassword); }Testcontainers的劣势也很明显跑测试需要Docker环境CI服务器上必须能拉取镜像而且首次启动镜像会耗时较长。但相对于它在兼容性方面带来的收益这个成本是值得的。我的建议是单元测试不用它但涉及数据库集成测试尽量上。4.2 事务回滚与数据隔离Transactional为什么能回滚到初始状态很多人在集成测试里看到Transactional就以为它只负责“保证测试数据不用清理”但实际上它的机制需要说清楚。在测试方法上标注Transactional后Spring的TransactionalTestExecutionListener会把测试方法包在一个事务里方法结束后直接回滚所以数据库里不会留下测试数据。但这里有一个很容易被忽略的坑你在测试方法里调用的Service方法如果本身也带Transactional它参与的是同一个事务数据写入后会被外层测试事务一起回滚所以看起来测试是“干净”的。但如果你在测试里用了异步线程、或者调用了REQUIRES_NEW传播级别的方法数据就会在独立事务中提交测试事务回滚并不能把那些数据带回来。这时候要么做好清理要么用TestTransaction手动控制——比如TestTransaction.flagForCommit()可以打破自动回滚让测试数据真实提交。还要注意Transactional在测试里的行为影响的是“测试方法”本身。如果某个Service方法逻辑里依赖事务的提交事件比如TransactionalEventListener在测试方法里调用它事件可能不会触发因为事务还没有提交。这种情况下要么在测试里显式把事务提交掉要么针对这类场景单独写集成测试不依赖测试方法级的事务。4.3 外部依赖怎么测WireMock与MockWebServer的取舍集成测试绕不开外部HTTP服务。常见的方案有两种WireMock和MockWebServer。MockWebServer是OkHttp团队出的库轻量、容易上手适合验证“我发出去的请求是否正确”——它可以断言请求路径、请求体、Header。WireMock功能更重支持动态响应、状态化行为、请求匹配等适合模拟完整的接口契约。我在接第三方支付和短信服务时一般会用WireMock做一个本地stub服务。最实用的一个能力是WireMock支持从录制文件自动生成stub可以把生产里的一个真实响应保存下来作为测试的固定预期。简单示例SpringBootTest AutoConfigureWireMock(port 8089) class PaymentClientTest { Autowired private PaymentClient paymentClient; Test void shouldParsePaymentCallback() { stubFor(post(urlEqualTo(/api/payment/callback)) .willReturn(aResponse() .withHeader(Content-Type, application/json) .withBody({\code\:\SUCCESS\,\tradeNo\:\123456\}))); PaymentResult result paymentClient.callback(123456); assertThat(result.isSuccess()).isTrue(); assertThat(result.getTradeNo()).isEqualTo(123456); } }用WireMock要注意stub的匹配规则如果太宽松测试里可能悄悄用了一个旧的响应结构导致代码里字段名已经改了测试却在绿。我习惯在每个stub里加上withId并且在阶段切换时清理掉无用的stub通过WireMock.reset()保证测试独立。Redis和MQ的集成测试我用的方案是Testcontainers下的GenericContainer启动redis镜像MQ则用KafkaContainer或RabbitMQContainer。这里需要强调一下不要用spring.redis.embedded之类的高仿替代品嵌入式Redis和真实的Redis在持久化、内存淘汰策略上的行为差异会在某些边界场景坑到你。5. 版本、配置与上下文测试启动的隐形杀手5.1 SpringBoot 2.x到3.x测试API的破坏性变化SpringBoot 3.x基于Spring Framework 6和Jakarta EE 9包名从javax改成了jakarta。这意味着如果你的测试代码里直接引用了javax.persistence.Entity之类的注解升级后第一件事就是改包名。测试API层面最大的破坏性变化是SpringBoot 2.6之后移除了spring.factories自动配置机制改用AutoConfiguration.imports而SpringBootTest启动上下文时会扫描这些自动配置类如果你的项目中还在用旧方式注册一些测试相关的自动配置类升级后测试会直接失败。还有一点是MockBean在3.4开始标记为废弃。在这之前2.x的MockBean用法在3.x里大体可运行但如果你同时升级了Spring Framework 6.2某些自定义的BeanFactoryPostProcessor场景会出现Mock不生效的问题。版本迁移时最稳妥的做法是先看spring-boot-dependencies的BOM版本对照官方迁移指南逐项核对。我遇到过最隐蔽的问题是SpringBoot 3.x默认启用parameter name反射而某些通过WebMvcTest测试的Controller如果依赖了参数名解析比如RequestParam不带value在2.x里能跑通在3.x里可能直接抛参数名找不到的异常。5.2 多环境配置与配置解密测试环境怎么会读到prod的文件开发者经常在IDE里配置application-test.yml但实际跑测试时SpringBoot的配置优先级是bootstrap.yml如果用了 application-test.ymlapplication.yml。如果你的测试类没有显式指定ActiveProfiles(test)SpringBoot默认使用defaultprofile会加载application.yml和application-default.yml。热搜里那个“IDEA Maven发布时的prod test配置文件”的问题我猜测正是这类场景——测试环境意外激活了prod配置于是数据库地址指向了生产环境。这里有个经验在测试类的基类或者JUnit配置里强制指定profileActiveProfiles(test) SpringBootTest public class BaseIntegrationTest { // 公共配置 }或者更保险一点在src/test/resources下放一个空的application.yml因为测试classpath下的application.yml优先级高于main里的同名文件这样即使不激活testprofile测试环境也不会读到生产配置。另一个坑是配置加密。项目里用了Jasypt对数据库密码做加密jasypt.encryptor.password通常通过环境变量或者启动参数注入。测试时如果忘了注入这个密码所有涉及加密配置的Bean启动就会直接失败。解决方式是在测试配置里放一个测试专用的解密密码jasypt: encryptor: password: test-secret同时把测试环境的spring.datasource.url指向本地测试库避免连到生产。5.3 上下文缓存与循环依赖为什么你的测试启动慢得离谱SpringBoot Test的上下文缓存机制很多人其实没利用好。SpringBootContextLoader默认会对相同配置的上下文做缓存但注意缓存key包含了MockBean的列表、ActiveProfiles、properties等。如果两个测试类一个用了MockBean(UserService.class)另一个没用它们的上下文就不同不会复用。所以如果项目里几十个测试类每个都MockBean不同的类整个测试套件可能启动几十个Spring容器时间必然爆炸。建议方式是把MockBean的声明上提尽量在基类里统一声明公共的MockBean让同模块的测试类共享同一个上下文。循环依赖在测试里也很烦人。比如SpringBootTest启动时检测到Bean之间存在循环依赖SpringBoot 2.6版本开始默认禁止循环依赖启动直接报错。如果是在升级版本时出现的靠修改业务代码解开循环依赖才是正解但如果你实在需要快速验证可以在配置里设置spring.main.allow-circular-referencestrue。注意这只是临时掩盖问题长期还是要把依赖结构理清楚。测试类之间的依赖也是一个大坑。JUnit默认每个测试类都是独立的实例测试方法之间没有顺序保证。如果测试代码写了“先执行A方法才能执行B方法”的隐式依赖那基本就是给CI埋了一颗雷。我见过一个项目因为两个测试类共享同一张表一个写完数据后没有清理另一个跑起来就失败最后靠指定FixMethodOrder才“稳定”下来——这个方案典型的治标不治本数据清理必须在测试方法里自己负责。6. 测试里必须避开的坑事务、乱序、并发与懒加载6.1 Transactional回滚的失效场景前面提到测试方法的Transactional会回滚但下面这几种场景它回滚不了Service方法使用了REQUIRES_NEW传播级别新开的事务独立提交不回滚。异步调用通过Async或新线程执行的逻辑运行在独立事务中。分布式事务涉及XA或Seata之类的方案事务由外部协调器管理JPA的Transactional控制不了。非Spring管理的事务比如直接在测试里操作TransactionTemplate之外的原生连接。针对这类场景正确的应对不是依赖自动回滚而是主动做数据清理。可以在AfterEach里用JdbcTemplate清理测试关联的表或者给测试数据加上唯一标记比如数据里带上test_user_id跑完后按标记删除。6.2 测试间的数据污染与隔离策略测试并行执行越来越常见但并行和数据库测试是天然的敌人。如果两个测试方法同时往同一张表写相同主键的数据就会发生冲突。我的建议是每个测试方法要生成唯一的数据。比如主键用UUID或者时间戳保证唯一尽量少用固定的“id1”这种写法。另外如果测试里查询的数据依赖某个状态比如查询条件里带状态字段可以考虑在测试数据里额外加一个test_scope列所有测试数据都打上标记查询时带上这个标记测试结束后统一清理。如果是DataJpaTest这种自动回滚的测试本身不会有数据残留问题但如果你需要手动验证SQL执行结果回滚机制反而成了障碍。你可以通过Rollback(false)关闭某个方法的事务回滚但最好只在确认需要手动验证的时候才这么干。6.3 懒加载与异步调用测试里最常见的“假通过”还有一个测试失效的经典场景是懒加载异常。JPA实体里的OneToMany集合默认是Lazy的在SpringBoot测试里如果事务边界结束后再去访问集合会抛LazyInitializationException。很多测试跑绿是因为在事务内访问了懒加载集合但由于测试方法本身的Transactional把整个方法包在了事务里从而掩盖了线上接口在无事务环境下会报错的问题。因此如果你真的要在Controller层测试接口返回的DTO里带嵌套集合建议在组装DTO之前就完成关联查询别依赖懒加载。异步调用的问题更隐蔽。Service方法调用了一个Async方法单元测试里如果直接用Mockito把异步Bean Mock掉了测试跑得快但异步逻辑的真实行为完全没有覆盖。如果要验证异步逻辑可以在测试类里显式配置一个同步执行的Executor比如SyncTaskExecutor或者用Awaitility轮询等待异步结果await().atMost(Duration.ofSeconds(5)) .untilAsserted(() - assertThat(asyncService.getResult()).isEqualTo(done));这里有一个面试高频的问题我也想顺带说清楚测试里加了MockBean后上下文会被标记为dirty下一次用到同一个上下文的测试类会重建容器。所以不要每个测试方法都去MockBean同一个类可以在测试类级别统一定义最大程度复用上下文缓存。7. 让测试真正起作用评审清单与习惯养成文章最后我想分享一套在实际项目中沉淀下来的测试评审清单能覆盖90%的常见问题保证测试不是“自嗨”单元测试是否覆盖了核心分支每个Service方法至少有两个测试——正常路径和异常路径复杂的if/else和循环逻辑尽量都走到。是否用了真实数据库涉及复杂SQL的Repository测试优先Testcontainers别用H2凑合。上下文缓存是否命中检查测试日志里的Starting ApplicationContext出现次数太多说明上下文没复用。测试数据是否隔离测试方法之间不能有顺序依赖所有测试数据必须有清理策略。外部服务是否可控HTTP外部接口用WireMock或MockWebServer不能用公网环境。是否有没有断言的测试有些测试跑完整个流程但一个assert都不写失败了也不知道为什么失败。我在团队里还经常说一句话测试代码也是代码要用产品代码的标准来维护它。冗余的测试、断言单薄的测试、依赖环境的测试时间久了都会变成垃圾最后整个测试套件让人失去信心。SpringBoot Test这套体系本身是够用的哪怕不引入额外的框架光靠JUnit 5、SpringBoot Test、AssertJ、Mockito、Testcontainers这几个组合就足以支撑一个中型项目的质量保障。关键是别把测试当成“任务”要当成工程的组成部分。当你真正跑过足够多失败的测试、分析过足够多上下文启动日志和事务回滚异常之后你会慢慢建立起一种感觉——测试不是用完就扔的工具而是你给这个项目留的一份持续可用的技术文档。
RELATED

相关推荐

虚拟机与沙盒:隔离环境避坑指南,每台电脑都该装的“后悔药”

虚拟机与沙盒:隔离环境避坑指南,每台电脑都该装的“后悔药”

你电脑上装过那种“不敢点开”的软件吗?不是不敢用,而是完全不确定它会在系统里干出什么事来。我今天要说的这个软件,是指虚拟机(Virtual Machine)和沙盒这一类隔离环境工具。它每台电脑都值得装一个,因为它…

📅 2026/9/9 16:17:28
CentOS7下Mosquitto MQTT Broker从安装到生产部署全攻略

CentOS7下Mosquitto MQTT Broker从安装到生产部署全攻略

装了无数次mosquitto之后,我总算把CentOS7上那点坑全摸清了。很多人觉得这玩意儿简单, yum install mosquitto 敲完就完事,结果服务起不来、客户端连不上、配置改了没反应、日志还一片空白。这篇我就从换源开始,把CentOS7上安装…

📅 2026/9/9 16:17:28
C#调用海康工业相机SDK:Win32回调实现实时抓拍与触发同步

C#调用海康工业相机SDK:Win32回调实现实时抓拍与触发同步

简介:这是一份基于C#语言、在Visual Studio 2010下实现的Win32海康威视抓拍机回调工程示例,核心目标是调用海康SDK实时抓拍车辆图像,并通过回调机制完成车牌号码的识别与展示。资源内含完整的VS2010解决方案,包含sln工程文件、cs源…

📅 2026/9/9 16:17:28
MORE NEWS

更多资讯

📰

jQuery与Vue3混合开发:大文件秒传与分片上传实战

前阵子接了个老项目升级的活,后台管理系统还是五六年前的jQuery写法,为了长期发展,老板要求整体往vue3迁。但业务不能停,老页面不能一下子推翻重写,最头疼的是一个素材上传模块——用户经常要传几个G的视频、压缩包和设…

📰

Linux服务器部署开源大模型:从环境准备到上线调优全攻略

大模型这个东西,前两年还只是论文里的概念,今年已经变成很多公司和个人开发者手里的常规工具了。尤其是开源模型的崛起,类似Qwen、Llama、DeepSeek这些模型权重全部开放,让"自己部署一个私有大模型"从极客折腾变成了完全…

📰

Vue2项目实战:WebUploader结合AES+RSA实现文件加密上传

1. 项目概述与技术选型思考 1.1 为什么还在vue2里选百度WebUploader 先说结论:在2024年还在维护vue2项目,说明这是个存量系统,大概率是企业后台、政务平台或者金融类管理系统。这类系统最大的特点就是不能随便动底层架构,但安全要…

📰

Telegraf 指标监控最小上手:3 步搭建一个可运行的采集写入实例

Telegraf 指标监控最小上手:3 步搭建一个可运行的采集写入实例 【免费下载链接】telegraf Agent for collecting, processing, aggregating, and writing metrics, logs, and other arbitrary data. 项目地址: https://gitcode.com/GitHub_Trending/te/telegraf …

📰

Vue2+WebUploader文件上传加密存储实战:从数字信封到分片上传

这事要从一个安全整改需求说起。公司内部运维管理系统一直跑在 Vue2 Element UI 这套老架构上,文件上传模块用的是百度 WebUploader,功能没毛病,支持大文件分片、并发、断点续传,甚至连 MD5 秒传都是现成的。但安全部门审计时提出…

📰

用了九年的老Mac居然还能装新版macOS:不到3步走通的0元实操

用了九年的老Mac居然还能装新版macOS:不到3步走通的0元实操 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 2015 年以前的 Mac 被官方升级名单除名…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬