Mockito单元测试实战:从核心概念到Spring Boot集成
1. 项目概述为什么我们需要 Mockito在 Java 开发里写单元测试是每个合格工程师的必修课。但很多时候我们写的测试代码比业务代码还难维护一个测试用例动不动就要启动整个 Spring 容器连接数据库调用外部接口跑一次测试要等半天。这根本不是单元测试这叫集成测试甚至是“系统测试”。真正的单元测试核心思想是隔离——只测试当前类或方法的逻辑把它的依赖全部“挡”在外面。这就是 Mockito 登场的时候了。它不是一个测试框架而是一个强大的 Mock模拟框架。简单说它能帮你凭空造出模拟一个对象这个对象看起来和真实依赖一模一样但行为完全由你控制。你可以指定当调用它的某个方法时返回什么值或者抛出什么异常从而让你能专注于测试目标方法本身的逻辑。举个例子你要测试一个OrderService的createOrder方法这个方法内部会调用UserService检查用户状态调用InventoryService扣减库存最后调用PaymentService执行支付。如果没有 Mockito你的测试就得准备好真实的用户数据、库存数据和支付网关环境搭建复杂测试速度慢而且支付失败这种异常场景很难构造。有了 Mockito你可以轻松地模拟Mock出这三个 Service告诉 Mockito“当调用userService.checkUserStatus(userId)时直接返回true当调用inventoryService.deduct(stockId, quantity)时直接返回成功当调用paymentService.pay(order)时模拟一个支付超时异常。” 这样你就能在一个纯净、可控的环境里快速验证createOrder方法在支付超时时的补偿逻辑比如订单状态是否被正确标记为“待处理”是否正确。所以这个实战案例的核心就是带你手把手地从一个真实的业务场景出发看看如何用 Mockito 把那些烦人的依赖“收拾”得服服帖帖写出既快又稳的单元测试。无论你是刚接触单元测试的新手还是想优化现有测试套件的老手这里面的技巧和踩过的坑都值得一看。2. 核心概念与 Mockito 基础扫盲在动手之前我们得先把 Mockito 的几个核心“武器”搞清楚。很多人用 Mockito 就只知道个when(...).thenReturn(...)其实它的工具箱丰富得很。2.1 Mock vs. Spy模拟与部分模拟这是最容易混淆的一对概念。Mock 对象你可以把它理解成一个“空壳”或者“替身演员”。你创建它的时候它内部所有方法默认都是“空的”或者返回默认值比如返回null、0、false或空集合。你必须通过when().thenReturn()等方式明确地告诉它每个方法应该怎么演。如果你调用了某个没有“编排”过的方法Mockito 可能会返回默认值或者如果你用的是严格模式直接抛出异常。Mock 对象适用于模拟那些你完全不关心其内部实现只关心其交互行为的依赖。Spy 对象你可以把它理解成一个“真实对象的监控器”。你基于一个真实对象创建 Spy这个对象本身的方法逻辑都是真实存在的。但是你可以选择性地“覆盖”Stub其中的某些方法让它们按照你的剧本走而其他没有被覆盖的方法则会执行原有的真实逻辑。Spy 对象适用于当你需要测试一个庞大、复杂的类但只想模拟其中的一两个方法而其他方法保持原样的场景。什么时候用 Mock什么时候用 Spy我的经验是优先使用 Mock。因为单元测试的核心是隔离Mock 能提供最彻底的隔离让你的测试用例意图更清晰。只有在一种情况下我会考虑用 Spy当你要测试的那个类它依赖的某些方法是private或final的或者这个类本身就是一个具体的工具类你只想验证其中某一部分逻辑但又不想重构代码时。但这种情况往往暗示着代码设计可能有问题比如类职责过重需要审视。2.2 核心注解Mock, InjectMocks, Spy在 Spring Boot 或类似的集成测试中我们通常使用注解来让代码更简洁。Mock 标注在一个字段上告诉 Mockito“请为我创建一个该类型的 Mock 对象。” 例如Mock private UserService userService;。InjectMocks 标注在你要测试的类的实例上。Mockito 会创建这个类的实例并自动将当前测试类中所有Mock或Spy注解创建的模拟对象通过构造函数、setter 方法或字段反射的方式“注入”到这个实例中。这是实现依赖注入的关键。Spy 标注在一个字段上告诉 Mockito“请为我创建一个该类型真实对象的 Spy。” 例如Spy private ListString list new ArrayList();。注意对于具体类你通常需要初始化它对于抽象类或接口Mockito 会创建一个 Mock。注意这些注解本身不会自动生效。你需要在测试类的BeforeEachJUnit 5或BeforeJUnit 4方法中调用MockitoAnnotations.openMocks(this)或者在测试类上使用ExtendWith(MockitoExtension.class)JUnit 5来初始化它们。我个人强烈推荐使用ExtendWith(MockitoExtension.class)更现代也更清晰。2.3 行为验证verify 的妙用Mockito 不仅能“给定行为”Stubbing还能“验证行为”Verification。这是单元测试中“断言”的另一种高级形式用于确保你的代码与依赖对象进行了预期的交互。// 假设我们 Mock 了一个 emailSender Mock private EmailSender emailSender; // 在测试方法中调用被测方法后可以验证交互 verify(emailSender).sendWelcomeEmail(user.getEmail()); // 验证 sendWelcomeEmail 被调用了一次且参数是 user.getEmail() verify(emailSender, times(1)).sendWelcomeEmail(anyString()); // 明确验证调用次数为1次 verify(emailSender, never()).sendErrorReport(any()); // 验证某个方法从未被调用 verify(emailSender, timeout(100)).sendAsyncNotification(notification); // 验证异步调用在100毫秒内被调用一个常见的误区过度使用verify。单元测试应该更关注状态State而非交互Interaction。也就是说你应该更多地断言被测方法执行后其返回值或某些对象的状态发生了变化而不是去验证它调用了哪个对象的哪个方法。过度验证交互会导致测试变得脆弱内部实现一改测试就挂且与实现细节耦合过紧。verify更适合用于验证那些具有“副作用”的调用比如发送消息、记录日志、调用外部API等。3. 实战案例用户注册服务单元测试好了理论说再多不如动手干。我们来看一个经典的业务场景用户注册。假设我们有一个UserRegistrationService它的依赖关系如下Service public class UserRegistrationService { private final UserRepository userRepository; private final EmailService emailService; private final PasswordEncoder passwordEncoder; public UserRegistrationService(UserRepository userRepository, EmailService emailService, PasswordEncoder passwordEncoder) { this.userRepository userRepository; this.emailService emailService; this.passwordEncoder passwordEncoder; } public User register(String username, String rawPassword, String email) { // 1. 检查用户名是否已存在 if (userRepository.findByUsername(username).isPresent()) { throw new UsernameAlreadyExistsException(Username already taken: username); } // 2. 密码加密 String encodedPassword passwordEncoder.encode(rawPassword); // 3. 创建用户实体 User newUser new User(username, encodedPassword, email); // 4. 保存用户 User savedUser userRepository.save(newUser); // 5. 发送欢迎邮件异步或同步这里假设是同步但可失败 try { emailService.sendWelcomeEmail(savedUser.getEmail(), savedUser.getUsername()); } catch (EmailException e) { // 邮件发送失败不应导致注册失败但需要记录日志 log.warn(Welcome email failed to send for user: {}, username, e); } // 6. 返回保存后的用户 return savedUser; } }我们的目标是为这个register方法编写高质量的单元测试。3.1 测试环境搭建与依赖模拟首先建立测试类骨架。我们使用 JUnit 5 和 Mockito。import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; ExtendWith(MockitoExtension.class) // 初始化 Mockito 注解 class UserRegistrationServiceTest { Mock private UserRepository userRepository; // 模拟数据库访问层 Mock private EmailService emailService; // 模拟邮件服务 Mock private PasswordEncoder passwordEncoder; // 模拟密码编码器 InjectMocks private UserRegistrationService userRegistrationService; // 被测试的服务依赖会自动注入 // 测试用例将写在这里 }关键点解析ExtendWith(MockitoExtension.class) 这是 JUnit 5 的扩展它替代了以前需要手动调用MockitoAnnotations.openMocks(this)的步骤让测试类更简洁。Mock 为三个依赖创建了 Mock 对象。在测试中它们都是完全可控的“傀儡”。InjectMocks 创建了UserRegistrationService的真实实例并将上面三个 Mock 对象注入进去。这样userRegistrationService内部操作的userRepository等就是我们模拟的对象。3.2 测试用例设计正常流程与异常分支一个好的单元测试应该覆盖主流程Happy Path和关键的异常分支Sad Path。针对register方法我们可以设计以下测试用例成功注册用户名不存在所有依赖正常工作验证用户被保存且邮件发送。用户名已存在模拟userRepository.findByUsername返回一个存在的用户验证抛出了正确的异常。邮件发送失败模拟emailService.sendWelcomeEmail抛出异常验证注册流程依然成功用户已保存但异常被妥善处理例如日志被记录。3.2.1 测试用例1成功注册Test void register_ShouldSuccess_WhenUsernameIsAvailable() { // 1. 准备测试数据 (Arrange) String username testUser; String rawPassword myPassword123; String encodedPassword encodedHashString; String email testexample.com; User mockUser new User(username, encodedPassword, email); User savedUser new User(username, encodedPassword, email); savedUser.setId(1L); // 模拟保存后有了ID // 2. 定义模拟对象的行为 (Stubbing) when(userRepository.findByUsername(username)).thenReturn(Optional.empty()); // 用户名不存在 when(passwordEncoder.encode(rawPassword)).thenReturn(encodedPassword); // 模拟密码编码 when(userRepository.save(any(User.class))).thenReturn(savedUser); // 模拟保存并返回带ID的用户 doNothing().when(emailService).sendWelcomeEmail(email, username); // 模拟邮件发送成功无返回值方法 // 3. 执行被测方法 (Act) User result userRegistrationService.register(username, rawPassword, email); // 4. 验证结果和行为 (Assert Verify) // 4.1 验证返回值 assertNotNull(result); assertEquals(savedUser.getId(), result.getId()); assertEquals(username, result.getUsername()); assertEquals(encodedPassword, result.getPassword()); // 注意这里断言的是加密后的密码 assertEquals(email, result.getEmail()); // 4.2 验证交互行为 verify(userRepository).findByUsername(username); // 验证查询被调用 verify(passwordEncoder).encode(rawPassword); // 验证编码被调用 verify(userRepository).save(argThat(user - // 验证保存被调用且保存的用户参数符合预期 user.getUsername().equals(username) user.getPassword().equals(encodedPassword) user.getEmail().equals(email) )); verify(emailService).sendWelcomeEmail(email, username); // 验证邮件发送被调用 // 4.3 验证不应发生的行为 verifyNoMoreInteractions(userRepository, emailService, passwordEncoder); // 验证没有其他意外的交互 }实操心得any(User.class)与argThat(...)any()是一种宽松的参数匹配器只关心类型。argThat()则更强大允许你自定义断言逻辑来匹配参数。在验证save方法时使用argThat可以确保保存的对象正是我们构造的那个增强了测试的精确性。doNothing().when(...)对于返回值是void的方法如sendWelcomeEmail我们不能用when(...).thenReturn(...)而要用doNothing().when(...).methodCall(...)来设定行为。如果想让它抛出异常则用doThrow(...).when(...).methodCall(...)。verifyNoMoreInteractions这是一个非常有用的“洁癖”检查。它确保在验证了所有预期的交互之后模拟对象没有再被调用任何其他方法。这能帮你发现一些意外的、隐藏的依赖调用让测试更严谨。但要注意如果被测方法内部有合理的其他调用比如日志记录你可能需要 Mock 对应的 Logger 或者忽略它。3.2.2 测试用例2用户名已存在Test void register_ShouldThrowException_WhenUsernameAlreadyExists() { // Arrange String username existingUser; String password password; String email existexample.com; User existingUser new User(username, oldEncodedPass, email); when(userRepository.findByUsername(username)).thenReturn(Optional.of(existingUser)); // 模拟用户名已存在 // Act Assert UsernameAlreadyExistsException exception assertThrows( UsernameAlreadyExistsException.class, () - userRegistrationService.register(username, password, email) ); // 验证异常信息 assertTrue(exception.getMessage().contains(username)); // 验证后续的依赖调用都没有发生 verify(userRepository, never()).save(any()); verifyNoInteractions(passwordEncoder, emailService); }注意事项使用assertThrows来断言预期的异常被抛出并可以捕获这个异常对象进行进一步验证如消息内容。一旦抛出异常业务流程中断。因此我们需要验证那些不应该被调用的方法。verify(... , never())和verifyNoInteractions(...)在这里派上用场。这确保了当用户名冲突时密码编码、用户保存和邮件发送等操作都没有执行符合业务逻辑。3.2.3 测试用例3邮件发送失败Test void register_ShouldSaveUser_WhenEmailSendingFails() { // Arrange String username newUser; String rawPassword pass; String encodedPassword encoded; String email newexample.com; User userToSave new User(username, encodedPassword, email); User savedUser new User(username, encodedPassword, email); savedUser.setId(99L); when(userRepository.findByUsername(username)).thenReturn(Optional.empty()); when(passwordEncoder.encode(rawPassword)).thenReturn(encodedPassword); when(userRepository.save(any(User.class))).thenReturn(savedUser); // 模拟邮件服务抛出异常 doThrow(new EmailException(SMTP server down)).when(emailService).sendWelcomeEmail(email, username); // Act User result userRegistrationService.register(username, rawPassword, email); // Assert assertNotNull(result); assertEquals(savedUser.getId(), result.getId()); // Verify: 即使邮件失败用户保存必须成功 verify(userRepository).save(any(User.class)); // Verify: 邮件发送确实被尝试了并且失败了 verify(emailService).sendWelcomeEmail(email, username); // 注意这里我们无法直接验证日志是否被记录除非我们 Mock 了 Log。这引出了下一个话题。 }踩坑记录 这个测试用例暴露了一个常见问题如何测试日志记录我们的业务逻辑里捕获了EmailException并记录了警告日志。在单元测试中我们通常不直接测试日志输出因为那属于实现细节且会使测试变得脆弱。更佳实践是不验证日志只验证核心业务状态用户已保存和主要交互邮件服务被调用。日志记录被视为一个可观察的副作用但在单元测试中通常忽略。使用 Mock 验证日志调用如果必须验证可以给 Service 注入一个Logger依赖例如通过构造器然后在测试中 Mock 这个 Logger并验证logger.warn方法被以预期的参数调用。但这会让代码和测试都更复杂需权衡利弊。大多数情况下方法1就够了。4. 高级技巧与疑难杂症排查掌握了基础用法我们来看看一些更进阶的场景和那些让人头疼的报错。4.1 处理静态方法、final 方法与构造函数PowerMock 的替代方案Mockito 默认不能 Mock 静态方法、final 类/方法和构造函数。以前我们不得不请出“重型武器” PowerMock。但 PowerMock 兼容性差且违背了“良好的代码设计本身就应该易于测试”的原则。现在我们有更好的选择。方案一使用 Mockito 3.4.0 的 “Inline Mock Maker”从 Mockito 3.4.0 开始提供了实验性的功能来 Mock final 类和枚举。你需要在项目的src/test/resources/mockito-extensions目录下创建一个名为org.mockito.plugins.MockMaker的文件内容为一行mock-maker-inline这样配置后Mockito 就能 Mock 很多 final 类型了。这也是你搜索热词中看到的那条警告mockito is currently self-attaching to enable the inline-mock-maker的来源——Mockito 正在尝试启用这个功能。方案二推荐重构你的代码这是最根本的解决方案。如果你的代码因为依赖了静态工具类如Utils.validate(...)而难以测试考虑将其包装在一个非静态的、可注入的 Service 里。例如创建一个ValidationService接口其实现内部调用静态工具方法。这样在你的业务类里你依赖的是ValidationService接口在测试中就可以轻松 Mock 它。这遵循了“依赖抽象而非具体”的原则不仅让代码可测试也提高了可维护性。方案三使用 Mockito 的mockStatic(有限支持)对于静态方法Mockito 从 3.4.0 开始通过mockStatic在try-with-resources块内提供有限支持。try (MockedStaticYourStaticClass mockedStatic mockStatic(YourStaticClass.class)) { mockedStatic.when(() - YourStaticClass.someStaticMethod(anyString())).thenReturn(mockedValue); // 在这里调用你的被测方法 // ... } // 在 try 块外静态方法恢复原样这种方法适用于遗留代码的临时测试但长期来看还是方案二更好。4.2 参数捕获器ArgumentCaptor的精准验证有时候你不仅想知道一个方法被调用了还想深入检查传递给它的参数对象在调用时的具体状态。verify配合argThat可以做到但ArgumentCaptor更直观尤其当参数是复杂对象时。假设我们想验证userRepository.save被调用时传入的User对象的创建时间createdAt是否被正确设置为当前时间非空。Test void register_ShouldSetCreationTimestamp_WhenSavingUser() { // ... 前面的 Arrange 部分省略假设用户名可用密码编码正常 ... // 创建参数捕获器指定捕获的类型是 User ArgumentCaptorUser userCaptor ArgumentCaptor.forClass(User.class); when(userRepository.save(userCaptor.capture())).thenAnswer(invocation - { User userToSave invocation.getArgument(0); userToSave.setId(100L); // 模拟保存后设置ID return userToSave; }); // Act userRegistrationService.register(newUser, pwd, ab.com); // Assert verify(userRepository).save(any(User.class)); User capturedUser userCaptor.getValue(); // 获取捕获到的参数 assertNotNull(capturedUser.getCreatedAt()); // 验证创建时间已被设置 // 可以进一步验证时间是否接近当前时间允许几秒误差 assertTrue(Math.abs(System.currentTimeMillis() - capturedUser.getCreatedAt().getTime()) 5000); }ArgumentCaptor让你能拿到方法调用时的“实参快照”进行非常细致的断言。对于多次调用还可以用captor.getAllValues()获取所有参数的列表。4.3 常见报错与解决方案实录在实际使用中你肯定会遇到各种奇怪的错误。这里记录几个高频的问题1Argument(s) are different!或Wanted but not invoked现象verify失败提示预期的调用参数与实际不符。排查检查verify语句中使用的参数匹配器如eq(“value”),any()等是否与模拟对象实际接收到的参数一致。特别注意如果参数中有多个要么全部使用匹配器要么全部使用真实值不能混用除了eq()。检查模拟对象的行为定义when是否准确。可能你定义了when(mock.method(“A”))但实际代码调用的是mock.method(“B”)。使用Mockito.mockingDetails(mock).getInvocations()打印出模拟对象接收到的所有调用进行对比。问题2UnnecessaryStubbingException现象测试通过但控制台抛出此异常。原因你定义了某个模拟方法的行为when(...)...但在整个测试执行过程中这个方法从未被实际调用。这通常是代码冗余或测试用例设计不严谨的信号。解决方案A推荐删除这个不必要的 Stubbing。让你的测试用例保持简洁。方案B如果某些 Stubbing 是为多个测试用例准备的公共部分可以将其移到BeforeEach方法中。但需谨慎这可能导致测试用例间的隐性耦合。方案C使用MockitoSettings(strictness Strictness.LENIENT)注解类来降低严格度但不推荐这会掩盖问题。问题3NotAMockException现象尝试对非 Mock/Spy 对象使用when()或verify()。排查检查对象是否真的被Mock或Spy注解。可能是忘了注解或者注解未生效忘了加ExtendWith。检查你是否不小心对真实对象如new出来的进行了 Mockito 操作。问题4NullPointerException在InjectMocks对象内部现象测试运行时在InjectMocks标注的被测对象内部出现 NPE。排查Mockito 的注入策略按类型、按名称可能失败导致某些依赖没有被成功注入。检查InjectMocks类的依赖是否都能通过构造函数、setter 或字段反射成功匹配到Mock对象。确保所有需要 Mock 的依赖都已经被Mock标注。如果被测类有多个构造函数Mockito 可能会选择错误的那个。考虑使用InjectMocks配合Spy或手动构造。最稳妥的方式在BeforeEach方法中手动构造被测对象。虽然代码多一行但绝对清晰可控。BeforeEach void setUp() { // 先创建所有 Mock userRepository mock(UserRepository.class); emailService mock(EmailService.class); passwordEncoder mock(PasswordEncoder.class); // 手动注入依赖 userRegistrationService new UserRegistrationService(userRepository, emailService, passwordEncoder); }5. 集成 Spring Boot Test让单元测试更“单元”我们上面的例子是“纯”单元测试没有 Spring 容器的参与。但在 Spring Boot 项目中很多 Service 本身是Component并且可能使用了Autowired进行字段注入。我们如何测试它们核心原则依然是尽量不启动容器进行隔离测试。5.1 使用 MockBean 进行切片测试Spring Boot Test 提供了MockBean注解。它可以被添加到 Spring 的ApplicationContext中用来替换掉上下文中某个 Bean 的真实实现。这非常适合用来测试 Controller 层使用WebMvcTest或 Service 层使用SpringBootTest但只加载部分配置。SpringBootTest(classes {UserRegistrationService.class}) // 只加载这个Service及其配置 class UserRegistrationServiceSpringTest { MockBean // Spring 会用一个 Mock 替换掉上下文中的真实 Bean private UserRepository userRepository; MockBean private EmailService emailService; MockBean private PasswordEncoder passwordEncoder; Autowired // 这里注入的 Service其依赖已经是上面 Mock 过的 Bean 了 private UserRegistrationService userRegistrationService; Test void register_withMockBean() { // 测试写法与纯 Mockito 测试几乎一样 when(userRepository.findByUsername(test)).thenReturn(Optional.empty()); when(passwordEncoder.encode(anyString())).thenReturn(encoded); // ... 其余部分相同 } }优缺点优点与 Spring 的依赖注入机制结合紧密对于使用字段注入或复杂的 Bean 依赖图的情况比较方便。缺点仍然会启动一个轻量级的 Spring 上下文取决于SpringBootTest的配置测试速度比纯 Mockito 测试慢。它更像是一种“集成单元测试”。5.2 坚持纯单元测试改造代码设计我个人的强烈偏好是Service 层的测试尽可能使用纯 Mockito 测试不依赖 Spring 容器。这倒逼你写出更易于测试的代码通常也意味着更好的设计。如何做到很简单坚持使用构造函数注入。Service public class UserRegistrationService { // 字段声明为 final强制通过构造函数初始化 private final UserRepository userRepository; private final EmailService emailService; private final PasswordEncoder passwordEncoder; // 唯一的构造函数Spring 会自动使用它进行依赖注入 Autowired // 在 Spring 4.3 中如果只有一个构造函数Autowired 可省略 public UserRegistrationService(UserRepository userRepository, EmailService emailService, PasswordEncoder passwordEncoder) { this.userRepository userRepository; this.emailService emailService; this.passwordEncoder passwordEncoder; } // ... 业务方法 }这样的代码在单元测试中你可以像本章第三节那样直接使用new关键字来创建被测对象并传入 Mock 依赖。测试速度快如闪电且完全不依赖 Spring。这才是真正的“单元”测试。所以当你觉得写单元测试很费劲时很可能不是 Mockito 的问题而是你的代码结构需要优化。可测试性往往是高质量代码的一个副产品。6. 测试代码的组织与维护写测试不是一锤子买卖。随着项目演进测试代码会越来越多维护成本也随之增加。好的组织习惯至关重要。6.1 命名规范让人一眼看懂测试意图测试方法名应该清晰地表达它的意图。一个流行的模式是[MethodUnderTest]_[Scenario]_[ExpectedResult]。register_ShouldSuccess_WhenUsernameIsAvailableregister_ShouldThrowException_WhenUsernameAlreadyExistscalculateDiscount_ShouldReturnZero_WhenUserIsNotVIPprocessOrder_ShouldCompensateInventory_WhenPaymentFails这种命名方式即使不看测试内容也能大致知道这个用例在验证什么。6.2 使用 BeforeEach 进行通用准备如果多个测试用例都需要相同的初始 Mock 行为例如passwordEncoder.encode总是返回一个固定的加密字符串可以将其移到BeforeEach方法中避免重复。BeforeEach void setUp() { // 每个测试运行前都会执行 // 可以在这里放一些所有测试共用的、基础的 Stubbing // 例如默认模拟密码编码器总是成功 when(passwordEncoder.encode(anyString())).thenReturn(defaultEncodedPassword); // 注意谨慎在这里 Mock 那些行为会变化的依赖容易导致测试间干扰。 }6.3 保持测试的独立性与可读性每个测试方法都应该是独立的不依赖于其他测试的执行顺序或状态。这意味着不要在测试间共享可变的 Mock 状态。使用BeforeEach来重置状态而不是BeforeAll除非是只读的、昂贵的资源。测试方法内部遵循Arrange-Act-Assert(准备-执行-断言) 模式用空行将这三个阶段分开让代码结构一目了然。最后记住单元测试的最终目的它是你代码的活文档是重构时的安全网是设计质量的反馈器。当你用 Mockito 写出干净、快速、可靠的单元测试时你收获的不仅仅是更少的 Bug还有一份对自身代码更深的理解和信心。