FEATURED · 精选文章

Java单元测试利器Mockito:核心玩法、实战套路与避坑指南

发布时间 / 2026/9/10 0:50:55
来源 / 创域科博编辑部
栏目 / 资讯中心
Java单元测试利器Mockito:核心玩法、实战套路与避坑指南 我做了这么多年Java后端Mockito这个库几乎每天都在用。它是Java生态里最流行的单元测试mock框架解决的问题很直白让你在测试里替掉那些不好构造、不稳定、或者压根还没实现的依赖对象。比如Service层要查数据库、调第三方HTTP接口、发MQ消息你不想在单测里真的去连数据库、真的发请求那就可以用Mockito把这些依赖“换成假的”然后指定它们的行为、验证它们有没有被正确调用。这篇把Mockito的核心玩法、实战套路和踩坑记录完整梳理一遍。适合刚开始写单元测试的人也适合已经用了很久但想系统补齐细节的人包括static方法怎么mock、私有方法怎么处理、Spring Boot项目里怎么写Service和Controller测试这类实际问题都会聊到。1. 准备工作先搞懂Mockito的定位和选型1.1 它解决的问题到底是什么先明确一个概念Mockito本身不是一个测试框架它只是一个“打桩工具”。真正的测试运行还是靠JUnitJUnit 4或JUnit 5来驱动的。两者的关系可以理解成JUnit是舞台Mockito是后台的工作人员——你写好测试剧本测试方法JUnit负责按顺序把每个剧本跑起来Mockito则在后台帮你把那些难搞的配角替换成替身演员。替身演员的价值在哪里举个例子你写了一个UserService.saveUser()方法方法里需要调用UserRepository.save()去写数据库。如果你直接做集成测试就必须保证数据库可用、表结构正确、测试数据不污染环境。而用Mockito之后UserRepository被mock掉你告诉Mockito“当save方法被调用时不管传什么参数都返回一个id1的User对象”。这样测试就完全脱离了外部环境运行速度快、结果稳定、可重复。Mockito官方对其定位的描述是“Tasty mocking framework for unit tests in Java”它主要面向单元测试场景帮助你验证行为和隔离依赖。这里的核心关键词是“行为验证”Behavior Verification意思是它侧重于检查某个方法是否被调用、被调用了几次、用什么样的参数调用的。这和传统的“状态验证”不同——传统测试更关注被测方法执行之后系统的状态变成了什么样而Mockito更关心被测对象和它的协作者之间产生了怎样的交互。以实际项目为例我维护过一个电商系统的订单模块。OrderService.createOrder()方法要做的事情很多校验库存、扣减库存、生成订单号、发送通知。如果按照传统方式写测试需要准备数据库、准备消息队列环境、甚至要mock一个远程库存接口。这样的测试不仅慢而且会因为环境问题频繁失败。把库存服务、消息发送器mock掉之后测试只需要关心一件事给定有效的订单请求OrderService是否正确调用了库存扣减接口是否保存了订单是否触发了通知。这个隔离测试的思路就是Mockito在单元测试里的核心价值。1.2 为什么选择Mockito而不是别的工具在Java生态里和Mockito同类的东西还有几个EasyMock、PowerMock、MockK、JMockit。我在不同的项目阶段都用过最终长期保留的是Mockito 少量PowerMock或Mockito的inline mock maker扩展来处理特殊情况。选Mockito的核心原因有几点。第一API设计非常符合人类心智when(x).thenReturn(y)、verify(x).method()这种链式写法接近自然语言团队协作时看测试代码几乎不需要额外解释。第二它和JUnit 5、Spring Boot Test的集成非常顺滑ExtendWith(MockitoExtension.class)加一个注解就能搞定注入。第三社区活跃、资料多、问题容易搜到踩坑时大概率不是第一个遇到的人。对比一下常见选项框架核心特点维护状态适用场景Mockito行为验证API简洁生态好活跃日常Java单元测试首选EasyMock老牌框架录制-回放模式维护少遗留项目PowerMock可mock静态方法、final类、私有方法维护慢且高版本JDK兼容差仅处理Mockito搞不定的场景MockKKotlin优先支持协程、内联函数活跃Kotlin项目JMockit功能很强但API偏复杂维护降级少见于新项目所以我给你的选型建议很简单新项目一律用Mockito真遇到static方法、final类这类Mockito原生不支持的东西再用Mockito的inline mock maker或者局部引入PowerMock。不要一上来就用PowerMock它的字节码增强机制在不同JDK版本上容易踩雷。2. 核心代码细节解析Mockito要怎么用才不跑偏2.1 Mock、InjectMocks和手动mock的区别用Mockito有两种最基本的创建mock对象的方式手动mock和注解注入。手动方式就是UserRepository userRepository Mockito.mock(UserRepository.class);。这种方式直白但每个mock对象都要手动创建、手动手动传入被测类在测试类字段变多之后代码会很啰嗦。注解方式更优雅。在JUnit 5环境下测试类上加上ExtendWith(MockitoExtension.class)然后在字段上标注Mock和InjectMocks。Mock是创建mock对象InjectMocks是创建被测的真实对象并自动把mock对象注入进去。例如ExtendWith(MockitoExtension.class) class UserServiceTest { Mock private UserRepository userRepository; Mock private MessageSender messageSender; InjectMocks private UserService userService; Test void shouldSaveUserAndSendMessage() { // ...测试逻辑 } }InjectMocks的注入策略是按照构造器注入优先、setter注入其次、字段注入兜底的顺序来处理的。这个优先级很重要我见过有同事用字段注入导致mock没生效排查半天才发现是因为类里同时存在构造器和字段注入构造器注入优先了而构造器里引用的依赖没有被mock到。所以在写被测类的时候最好统一用构造器注入这样Mockito的处理最干净、最可预期。2.2 打桩的几种姿势以及为什么要慎用打桩stubbing就是用when(...).thenReturn(...)来指定mock对象的行为。常见的有这么几种// 固定返回值 when(userRepository.findById(1L)).thenReturn(Optional.of(user)); // 根据参数返回不同值 when(userRepository.findById(anyLong())).thenAnswer(invocation - { Long id invocation.getArgument(0); return id 0 ? Optional.of(new User(id)) : Optional.empty(); }); // 抛出异常 when(userRepository.save(any())).thenThrow(new DataAccessException(db error)); // 无返回值方法的打桩void方法 doNothing().when(messageSender).send(anyString()); // 或让void方法抛异常 doThrow(new RuntimeException(send failed)).when(messageSender).send(anyString());这里有一个经验性的忠告不要对不关心的方法调用也去打桩。Mockito有一个特性叫strict stubbing默认开启的情况下如果某个打桩在测试执行过程中从未被调用它会抛UnnecessaryStubbingException。这个设计看着挺严格实际上是在帮你维护测试质量——如果打桩没被调用说明你的测试逻辑没有覆盖到那条路径或者被测代码改了、不再走那个分支了测试需要跟着调整。同时要注意打桩的时候参数匹配要精确。when(userRepository.findById(1L)).thenReturn(...)和when(userRepository.findById(2L)).thenReturn(...)是两个完全独立的桩。如果用anyLong()这种匹配器它会匹配任何Long参数此时如果你又对具体值打了桩会收到InvalidUseOfMatchersException之类的报错因为混用了精确值和匹配器。2.3 verify验证行为才是Mockito的杀手锏Mockito和普通mock工具最大的不同就是有一套非常完备的交互验证机制。通俗来说verify可以检查某个mock对象的方法到底有没有被调用、被调了几次、用什么参数调用的。// 验证被调用1次默认 verify(userRepository).save(any(User.class)); // 验证被调用2次 verify(userRepository, times(2)).save(any(User.class)); // 验证从未被调用 verify(userRepository, never()).deleteById(anyLong()); // 验证至少/至多调用次数 verify(userRepository, atLeastOnce()).save(any(User.class)); verify(userRepository, atMost(3)).save(any(User.class)); // 验证没有更多交互发生 verifyNoMoreInteractions(userRepository); // 验证调用顺序 InOrder inOrder inOrder(userRepository, messageSender); inOrder.verify(userRepository).save(any(User.class)); inOrder.verify(messageSender).send(anyString());我在Code Review里经常强调一件事写verify不是目的目的是通过verify来表达业务预期。比如下单场景里先扣库存再发订单事件这个顺序是业务规则就得用InOrder来保护。单测如果只验证返回值是抓不住这种顺序逻辑的回归的。但也别过度使用verify——一个测试方法里塞了五六个verify每个方法都去验证内部细节测试和实现耦合度会很高后面重构实现代码时即使行为没变测试也会挂。我的原则是对外部依赖的交互必须verify对内部协作者的无意义调用不强求verify。2.4 spy保留真实行为的半mock工具除了mockMockito还提供spy。spy对象的特点是默认调用真实方法只有在显式打桩的地方才走桩逻辑。用大白话说就是“默认真做指定位置假做”。Spy private ListString list new ArrayList(); Test void testSpy() { list.add(a); list.add(b); // 真实调用了add所以size2 assertThat(list.size()).isEqualTo(2); // 对size方法打桩 doReturn(100).when(list).size(); assertThat(list.size()).isEqualTo(100); }spy在哪些场景有用典型的是测试老代码类的内部逻辑很复杂你只想替换其中某一个方法的行为又不想把整个类都mock掉。还有一种场景是测试对象内部状态流转。但spy也有坑当你spy一个真实对象时打桩语法要注意用doReturn(...).when(spy).method()而不是when(spy.method()).thenReturn(...)。原因在于后者会先真正执行一次spy.method()如果这个方法有副作用会在测试里先跑一遍产生难以预料的后果。3. 实战记录Spring Boot项目里的单元测试该怎么写3.1 基础设施依赖和基本约定以Spring Boot 2.7 JUnit 5为例pom.xml里加这些就行dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependencyspring-boot-starter-test是一个聚合依赖内部已经包含了JUnit 5、Mockito、AssertJ、Hamcrest、JSONassert、Spring Test等等不需要另外单独引入Mockito依赖。项目里建议做一个基础的测试基类把公共的东西放进去。比如所有单元测试都加ExtendWith(MockitoExtension.class)或者统一打开Mockito的某些配置。不过我的习惯是不做太重的基类因为基类藏的东西越多新人越看不明白。用ExtendWith(MockitoExtension.class)这一个注解就够了。还有一个约定俗成的命名习惯测试类名用被测类名 Test例如UserServiceTest测试方法名尽量用行为描述式我用的是should_预期行为_when_条件的格式Test void should_throw_exception_when_user_not_found() { // ... }这种命名方式能直接当文档用跑挂了也知道是哪个业务规则被破坏了。3.2 Service层测试最典型的实战姿势以用户注册场景为例。假设UserService.register(RegisterRequest request)的流程是这样的检查用户名是否被占用如果被占用抛业务异常生成新用户并保存发送一条欢迎消息返回用户ID。对应的测试可以这么写ExtendWith(MockitoExtension.class) class UserServiceTest { Mock private UserRepository userRepository; Mock private MessageSender messageSender; InjectMocks private UserService userService; Test void should_register_success_when_username_not_exists() { // given RegisterRequest request new RegisterRequest(zhangsan, 123456); User savedUser new User(1L, zhangsan, 123456); when(userRepository.existsByUsername(zhangsan)).thenReturn(false); when(userRepository.save(any(User.class))).thenReturn(savedUser); // when Long userId userService.register(request); // then assertThat(userId).isEqualTo(1L); verify(userRepository).existsByUsername(zhangsan); verify(userRepository).save(any(User.class)); verify(messageSender).send(eq(zhangsan), contains(welcome)); } Test void should_throw_exception_when_username_already_exists() { // given RegisterRequest request new RegisterRequest(zhangsan, 123456); when(userRepository.existsByUsername(zhangsan)).thenReturn(true); // when then assertThatThrownBy(() - userService.register(request)) .isInstanceOf(BusinessException.class) .hasMessageContaining(用户名已存在); // 验证后续逻辑未执行 verify(userRepository, never()).save(any(User.class)); verify(messageSender, never()).send(anyString(), anyString()); } }这里有几个细节要说一下。第一个是any(User.class)的粒度问题。如果你关心save方法里到底存了什么参数可以用ArgumentCaptor把传参捕获出来再断言ArgumentCaptorUser captor ArgumentCaptor.forClass(User.class); verify(userRepository).save(captor.capture()); User captured captor.getValue(); assertThat(captured.getUsername()).isEqualTo(zhangsan); assertThat(captured.getPassword()).isNotEqualTo(123456); // 假设密码加密存储ArgumentCaptor让你能检查mock对象接收到的参数内容这比只验证方法被调用要有价值得多。比如注册场景里你关心保存的User的username是否来自请求密码是否加密过状态字段是否正确这些都需要捕获参数来断言。第二个是verify(messageSender).send(eq(zhangsan), contains(welcome))里的参数匹配器。如果你的方法参数里有“任意参数”和“精确匹配”混用必须都用匹配器包裹比如eq(...)。直接写裸字符串再混用anyString()会直接抛InvalidUseOfMatchersException。这是Mockito里最经典的报错之一后面问题排查小节会专门讲。第三个是异常场景的验证。除了验证异常类型和消息还要验证“不该发生的事没有发生”这是单测里很容易漏掉的一环。上面用verify(userRepository, never()).save(any())就很好地表达了“用户名重复时不应该走保存逻辑”这个业务约束。3.3 Controller层测试配合MockMvc做接口级验证Controller层的测试用MockMvc来做。要注意的是这里有两种MockMvc用法。一种是WebMvcTest配合MockBean会启动Spring MVC的切片上下文加载Controller、转换器、异常处理器等但不会加载Service层。 另一种是全程手动构建mockMvc不启动Spring上下文。对于简单的Controller手动构建更快。实际项目中WebMvcTest是更常用的方式因为它更接近真实运行时的行为。比如测试一个UserControllerWebMvcTest(UserController.class) class UserControllerTest { Autowired private MockMvc mockMvc; MockBean private UserService userService; Test void should_return_user_when_get_by_id() throws Exception { when(userService.getUserById(1L)).thenReturn(new User(1L, zhangsan)); mockMvc.perform(get(/api/users/1)) .andExpect(status().isOk()) .andExpect(jsonPath($.id).value(1)) .andExpect(jsonPath($.username).value(zhangsan)); } }MockBean是Spring Boot为测试提供的注解它会创建mock对象并替换掉Spring容器里的真实Bean。这样Controller依赖的Service就是mock的不用真的走业务逻辑。不过要提醒一句MockBean和前面提到的Mock有区别。Mock是Mockito层面的不感知Spring容器MockBean是Spring Boot层面的它会去替换容器里的Bean。在WebMvcTest、SpringBootTest这类需要Spring上下文的测试里用MockBean在纯Service层单测里用Mock就够了。MockMvc这一层还有一个很多团队会忽略的点Controller层的测试不要重复验证Service层的逻辑。Controller的职责是处理HTTP协议、参数校验和响应封装而不是业务规则。所以Controller测试里重点验证的是状态码、响应结构、参数校验失败时的错误信息而不是去验证业务分支。3.4 Spring Boot先写测试还是先写功能代码热词里有人搜“springboot先写单元测试还是先写功能代码”我多说两句。这个问题本质上是TDD测试驱动开发的实践问题。我的观点是如果你正在开发新的业务功能并且业务规则足够清晰那先写测试是能显著提高代码质量的如果你在维护遗留系统补测试大多数时候只能事后补先把主要流程测了再重构。TDD不是简单的“先写测试再写代码”而是通过测试来驱动设计。当你先写测试时你会被迫思考接口应该长什么样、依赖应该怎么组织、返回值应该表达什么语义。比如上面那个UserService.register方法如果你先写测试你就会先想到“用户名重复时要抛异常”于是自然会设计出BusinessException代码结构从一开始就是可测试的。但在实际项目里我很少能做到纯粹严格的TDD原因是业务时间紧、需求变化快流程上往往是先写实现、后补测试。对大部分团队我建议退而求其次功能代码写完一个方法就补一个对应的单测不要等攒了一堆再补。技术债一旦攒着不处理后面再补的意愿会越来越低。3.5 复杂场景static方法、final类该怎么mockMockito原生不能mock static方法和final方法。如果项目里真的有这种需求有两个方案。方案一使用Mockito的inline mock maker。Mockito 3.4.0以上版本可以用mockito-inline依赖来支持static方法mock。做法是创建一个src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker文件内容写mock-maker-inline。或者直接引入依赖dependency groupIdorg.mockito/groupId artifactIdmockito-inline/artifactId scopetest/scope /dependency之后就可以mock static方法了try (MockedStaticIdGenerator mocked Mockito.mockStatic(IdGenerator.class)) { mocked.when(IdGenerator::nextId).thenReturn(100L); // 执行测试... }方案二用PowerMock。PowerMock可以mock static、final甚至私有方法但它改字节码的方式和新版JDK的兼容性不好而且写法上要加RunWith(PowerMockRunner.class)在JUnit 5下支持还要额外引入PowerMock的JUnit 5扩展集成成本高。我的态度是除非整个项目已经有大量PowerMock存量否则新代码不建议引。还有一类场景是测试时用到的对象是Java 8的Optional、Stream、LocalDate这类JDK自带的类。这些类一般是final的Mockito默认mock不了。但通常你也不应该去mock它们它们属于值类型直接用真实对象就行。如果代码里直接new了LocalDate.now()测试时想控制日期可以用Clock注入的方式而不是去mockLocalDate。4. 常见问题与排查技巧实录4.1 UnnecessaryStubbingException打桩没被使用这是Mockito strict stubbing最直接的表现。看到这个异常第一反应不是去关掉strict mode而是检查你的测试逻辑是否真的走到预期的分支。比如你在given阶段给userRepository.findById(1L)打了桩但被测方法因为参数不对走了另一个分支这个桩自然就没被调用到。排查路径先看测试方法的输入参数是不是写错了再看被测方法内部是否真的会调用到那个方法。如果被测代码确实没走那条路径要么是测试场景设计错了要么是被测代码有逻辑问题。千万不要通过MockitoSettings(strictness Strictness.LENIENT)全局关掉strict那是把有价值的提醒直接关掉了后面代码演进时问题会藏得更深。4.2 when(x).thenReturn(y) 调用了真实方法这个坑出现在spy对象上。如果你对spy对象用when(spy.method()).thenReturn(x)的语法Mockito在执行when(...)时其实会先调用一次真实的spy.method()。如果这个方法有副作用比如修改了内部状态或者抛异常你的测试就会莫名失败。解决办法是统一用doReturn(x).when(spy).method()这种语法。它的执行顺序是先告诉Mockito“接下来这个方法我要打桩”然后再去调用方法此时已经走了mock逻辑不会触发真实实现。在mock对象上两者差别不大但在spy对象上一定要用doReturn系列。同理doThrow()、doNothing()、doAnswer()这些方法在某些场景下也会比when系列更安全。代码规范里可以直接约定只要打桩的对象可能是spy一律用doReturn风格省得每次都要想。4.3 InvalidUseOfMatchersException参数匹配器混用这个异常我在各种群里几乎每周都会看到有人问。它的典型触发方式是when(userRepository.findById(1L)).thenReturn(Optional.empty()); // 报错 when(userRepository.findById(anyLong())).thenReturn(Optional.empty()); // 正常但当你在同一个方法里混合使用精确值和匹配器时verify(messageSender).send(eq(zhangsan), welcome); // 报错 verify(messageSender).send(eq(zhangsan), contains(welcome)); // 正常报错原因解释一下Mockito在解析参数时如果把所有参数都当成匹配器或者都当成精确值能正确区分一旦混用它内部的参数栈就会玩不转直接抛InvalidUseOfMatchersException。规则很简单要么全用精确值要么全用匹配器局部混用的时候精确值也要用eq()包起来。还有一类情况如果某个方法的重载版本里一个是基本类型参数一个是包装类型参数在用any()时容易出现类型不匹配的问题。建议用anyLong()、anyString()这类明确类型的匹配器而不是裸any()。4.4 测试异步方法时verify不生效如果被测方法内部用了异步线程池执行任务比如public void sendAsync() { CompletableFuture.runAsync(() - { messageSender.send(hello); }); }你的测试可能写完就执行verify(messageSender).send(hello)结果发现mock对象根本没人调用。原因很简单异步任务还没开始执行主线程的测试就结束、退出了JVM线程池里刚提交的任务还没来得及跑起来。几种常见的处理方式用CountDownLatch或Awaitility等工具来等待异步任务完成。测试中显式注入一个同线程执行的Executor比如Runnable::run。被测方法改成返回CompletableFuture测试里future.get()等待完成。我更推荐第二种把线程池作为依赖注入进来测试时换成同步执行器。这样做的好处是测试完全可控、无需等待还能测到掉链子场景。很多公司代码为了可测性会专门把Executor作为构造参数这个思路比硬等待更干净。4.5 Static方法mock后没有生效检查一下是不是mockito-inline依赖没加、MockMaker文件没配置或者mock的类不在同一个classloader里。另一个常见坑是static方法所在类在SpringBootTest里被其他Bean缓存了mock static的效果没覆盖到整个上下文测试之间出现顺序相关的奇怪问题。我的建议是尽量重构掉static方法或者抽到一个可注入的类里这比任何mock技巧都可靠。4.6 MockBean在并发测试里的坑MockBean在Spring Boot里用起来确实方便但它会修改ApplicationContext的Bean定义。如果测试类之间并行执行多个类都修改同一个上下文可能会出现互相干扰的现象。解决方案是给测试加上DirtiesContext或者避免并行执行。更进阶的做法是尽量少用SpringBootTest多写纯Mockito的单元测试把测试整体提速。5. 实操心得与避坑清单5.1 Mockito最佳实践清单根据经验我整理了一份Mockito的最佳实践团队落地时可以直接拿去做Code Review的检查项测试和被测代码在同一个模块、同包名下的test目录不使用public的测试工具类暴露内部细节。InjectMocks场景下被测类用构造器注入字段少用字段注入。给mock对象打桩时优先用when(...).thenReturn(...)对spy对象一律用doReturn(...).when(...)规避真实方法被调用。不必须的交互不要verify避免测试和实现过度耦合。对关键业务参数使用ArgumentCaptor捕获并断言而不是只用any()。严格控制static方法mock能用依赖注入解决的绝不mock static。不打没必要的桩遇见UnnecessaryStubbingException就清理掉。5.2 从只会用Mockito到理解Mockito很多初学者会卡在一个点为什么我用Mockito写完测试代码覆盖率上去了但产品上线后还是有Bug原因是Mockito这类mock工具帮你隔离了外部依赖但也同时把外部依赖的真实行为从测试里拿掉了。如果你mock了ProductClient.getPrice()并让它永远返回100那你的测试自然测不出“远程接口超时”“返回JSON格式不合法”“库存服务返回null”这些真实故障。所以单测的定位是验证“我的代码逻辑是否正确处理了我预期的输入和交互”它不等于集成测试也不等于端到端测试。Mockito解决的是“能不能测”的问题Spring Boot Test解决的是“测起来像不像真实环境”的问题。我一般在项目里会建议三层测试结构纯Mockito写Service层单测用Testcontainers或H2写Repository层集成测试用SpringBootTest写跨模块的关键链路测试。把每层的职责分清楚测试的作用才能最大化。5.3 几个容易让团队破防的测试坏味道写测试时间长了会发现一些看起来很合理、实际上很危险的写法。这里单独列几种我每次评审都重点抓的坏味道。第一种是“大爆炸式测试”。一个测试方法里把所有逻辑全测了做了一堆打桩、一堆verify、一堆断言。结果第一个断言失败时你还看不出来到底哪一层逻辑出了问题排查时间全花在拆解测试本身。这种测试应该拆成多个独立的小方法一个方法只验证一个行为。第二种是“条件断言的假测试”。有些团队为了追求覆盖率会在测试里用try { } catch (Exception e) { assertTrue(true); }这种写法。这本质上是在骗覆盖率数据测试执行完不管发生什么都是绿的什么问题都发现不了。遇到这种测试我会直接打回重写。正确做法是用assertThatThrownBy或assertThrows来明确期望异常让测试真正能表达行为。第三种是“依赖真实环境的测试”。早期项目里有人在Service层单测中去连开发库的数据库结果CI跑一次测试要几十秒还经常因为测试数据被清理而挂掉。后来把测试拆成两拨使用Mockito的纯单测跑在快速验证阶段需要真实环境的集成测试单独标记在专门的流水线阶段执行。这样一来单元测试速度从分钟级降到秒级研发体验好了很多。5.4 测试规范的落地经验写到这里再分享一点团队规范落地的经验。Mockito的用法其实不难难的是让大家在每天写代码的时候都主动写好测试。我们项目组的做法是三件事。第一把Mockito的常用写法整理成一份内部速查文档包含上面提到的各种代码示例让新人照着案例写就行。第二在Code Review时把“有没有单测”和“单测质量是否合理”作为硬性检查点初期效率会低一些但坚持一个月后大家形成了习惯效率自然上去。第三用JaCoCo统计覆盖率把覆盖率目标定在一个合理的水平比如核心业务模块方法覆盖率不低于80%分支覆盖率不低于70%。不用追求100%的覆盖率因为那通常意味着你在写无效测试维护成本还特别高。实际上覆盖率只是一个参考数字重要性远低于测试是否真正验证了业务行为。我在项目里见过一个模块覆盖率90%结果上线还是出了P0事故因为所有的测试都是happy path异常分支完全没测。后来我们把“每个业务规则至少要有异常路径测试”作为硬性约束情况才好转。Mockito真正要帮助你的是隔离、验证和精细控制而不是单纯堆砌覆盖率。最后说一个我个人的体会。Mockito的API是活的、随版本更新的可能两年前你学的用法新版本已经换了推荐方式。比如Mockito 2时代提倡的when(...).thenReturn(...)到Mockito 3/4时代更强调strict stubbing和BBD风格。保持对官方文档和release note的关注比收藏一堆年久失修的博客有价值得多。写测试这件事从来不是“写完就完了”而是要和真实代码一起用心维护、定期重构的。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻