> 技术文档 > Java 单元测试Mockito与PowerMock_powermock mockito

Java 单元测试Mockito与PowerMock_powermock mockito


Java 单元测试:Mockito 与 PowerMock 实战指南

在 Java 单元测试中,当被测试类依赖外部资源(如数据库、网络接口)或其他复杂组件时,直接测试会面临“依赖难隔离、环境难搭建”的问题。MockitoPowerMock 是解决这类问题的核心工具:

  • Mockito:轻量级 Mock 框架,专注于模拟非静态、非私有的依赖对象(如接口、普通类),简化依赖隔离。
  • PowerMock:基于 Mockito 扩展,支持模拟 静态方法、私有方法、构造函数 等 Mockito 难以处理的场景,适合遗留系统或设计不够灵活的代码测试。

一、Mockito 核心用法(基础篇)

1. 环境准备

需引入依赖(以 Maven 为例):

 <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>4.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>4.8.1</version> <scope>test</scope> </dependency> 

2. 核心注解与概念

  • @Mock:创建一个 Mock 对象(模拟依赖)。
  • @InjectMocks:将 @Mock 标记的对象自动注入到被测试类中(依赖注入)。
  • @ExtendWith(MockitoExtension.class):JUnit 5 扩展,启用 Mockito 注解支持。

3. 基础操作:模拟行为与验证

示例场景

被测试类 OrderService 依赖 UserDaoProductDao,需测试 calculateTotalPrice 方法(根据用户等级和商品价格计算订单总价)。

// 被依赖的 Dao 接口(模拟对象) public interface UserDao { // 根据用户 ID 获取用户等级(1-普通,2-会员)  int getUserLevel(Long userId); } public interface ProductDao { // 根据商品 ID 获取价格  double getPrice(Long productId); } // 被测试类 public class OrderService { private final UserDao userDao; private final ProductDao productDao; // 构造函数注入依赖  public OrderService(UserDao userDao, ProductDao productDao) { this.userDao = userDao; this.productDao = productDao; } // 计算订单总价:会员享 9 折,普通用户无折扣  public double calculateTotalPrice(Long userId, List<Long> productIds) { int level = userDao.getUserLevel(userId); double total = 0.0; for (Long pid : productIds) {  total += productDao.getPrice(pid); } return level == 2 ? total * 0.9 : total; // 会员折扣  } } 
测试代码(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 java.util.Arrays; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) // 启用 Mockito 注解 public class OrderServiceTest { // 模拟依赖:UserDao 和 ProductDao  @Mock private UserDao userDao; @Mock private ProductDao productDao; // 被测试对象:自动注入上面的 Mock 对象  @InjectMocks private OrderService orderService; // 测试 1:普通用户(等级 1)无折扣  @Test void calculateTotalPrice_normalUser_noDiscount() { // 1. Arrange(准备):设置 Mock 对象的行为  Long userId = 1L; List<Long> productIds = Arrays.asList(100L, 200L); // 模拟 userDao 返回普通用户(等级 1)  when(userDao.getUserLevel(userId)).thenReturn(1); // 模拟 productDao 返回商品价格(100 和 200)  when(productDao.getPrice(100L)).thenReturn(100.0); when(productDao.getPrice(200L)).thenReturn(200.0); // 2. Act(执行):调用被测试方法  double total = orderService.calculateTotalPrice(userId, productIds); // 3. Assert(断言):验证结果  assertEquals(300.0, total); // 100+200=300,无折扣  } // 测试 2:会员用户(等级 2)享 9 折  @Test void calculateTotalPrice_vipUser_withDiscount() { // 1. Arrange  Long userId = 2L; List<Long> productIds = Arrays.asList(100L); when(userDao.getUserLevel(userId)).thenReturn(2); // 会员  when(productDao.getPrice(100L)).thenReturn(100.0); // 2. Act  double total = orderService.calculateTotalPrice(userId, productIds); // 3. Assert  assertEquals(90.0, total); // 100 * 0.9 = 90  } } 

4. Mockito 进阶操作

(1)验证方法调用次数

verify 验证 Mock 对象的方法是否被正确调用:

@Test void testVerifyInvocation() { // 执行测试  orderService.calculateTotalPrice(1L, Arrays.asList(100L, 200L)); // 验证 userDao.getUserLevel(1L) 被调用了 1 次  verify(userDao, times(1)).getUserLevel(1L); // 验证 productDao.getPrice 被调用了 2 次(因为有 2 个商品)  verify(productDao, times(2)).getPrice(anyLong()); // anyLong() 匹配任意 Long 参数 } 
(2)模拟异常抛出

doThrow 模拟依赖方法抛出异常:

@Test void testExceptionHandling() { // 模拟:当 productId=999 时,productDao 抛出异常  when(productDao.getPrice(999L)).thenThrow(new RuntimeException(\"商品不存在\")); // 验证调用时是否抛出预期异常  assertThrows(RuntimeException.class, () -> { orderService.calculateTotalPrice(1L, Arrays.asList(999L)); }); } 
(3)使用 ArgumentCaptor 捕获参数

当需要验证方法调用的参数详情时(如复杂对象),用 ArgumentCaptor 捕获参数:

import org.mockito.ArgumentCaptor; // ... @Test void testArgumentCaptor() { // 执行测试  orderService.calculateTotalPrice(1L, Arrays.asList(100L, 200L)); // 捕获 productDao.getPrice 的参数  ArgumentCaptor<Long> productIdCaptor = ArgumentCaptor.forClass(Long.class); verify(productDao, times(2)).getPrice(productIdCaptor.capture()); // 验证捕获的参数是否符合预期  List<Long> capturedIds = productIdCaptor.getAllValues(); assertEquals(Arrays.asList(100L, 200L), capturedIds); } 

二、PowerMock 核心用法(解决 Mockito 局限性)

Mockito 无法直接模拟 静态方法、私有方法、构造函数 等,而 PowerMock 扩展了这些能力。以下是典型场景及解决方案。

1. 环境准备

需额外引入 PowerMock 依赖(注意版本兼容性,此处适配 JUnit 5 和 Mockito 4.x):

 <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-core</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit5</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency> 

2. 模拟静态方法

场景

被测试类依赖一个含静态方法的工具类 DateUtils,需模拟其静态方法返回固定值。

// 含静态方法的工具类 public class DateUtils { // 静态方法:获取当前年份  public static int getCurrentYear() { return Calendar.getInstance().get(Calendar.YEAR); } } // 被测试类(依赖 DateUtils 静态方法) public class OrderService { // ... 其他代码省略 ...  // 计算订单年份前缀(如 2024 -> \"ORD-2024-\")  public String getOrderPrefix() { int year = DateUtils.getCurrentYear(); // 调用静态方法  return \"ORD-\" + year + \"-\"; } } 
测试代码(PowerMock 模拟静态方法)
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit5.PowerMockExtension; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.powermock.api.mockito.PowerMockito.mockStatic; import static org.powermock.api.mockito.PowerMockito.when; // 启用 PowerMock 扩展,并指定需要处理的静态类(DateUtils) @ExtendWith(PowerMockExtension.class) @PrepareForTest({DateUtils.class}) // 关键:声明需要模拟静态方法的类 public class OrderServiceStaticTest { @Test void testGetOrderPrefix() { // 1. 模拟静态类 DateUtils  mockStatic(DateUtils.class); // 初始化静态模拟  // 2. 设置静态方法返回固定值(当前年份固定为 2024)  when(DateUtils.getCurrentYear()).thenReturn(2024); // 3. 执行测试  OrderService orderService = new OrderService(null, null); // 其他依赖为 null(此处不影响)  String prefix = orderService.getOrderPrefix(); // 4. 断言  assertEquals(\"ORD-2024-\", prefix); } } 

3. 模拟私有方法

场景

被测试类有一个私有方法 calculateTax,需模拟其返回值(避免依赖复杂逻辑)。

public class OrderService { // ... 其他代码省略 ...  // 计算税费(私有方法,逻辑复杂)  private double calculateTax(double total) { // 实际逻辑可能依赖外部税率接口,测试时需模拟  return total * 0.1; // 假设税率 10%  } // 计算最终支付金额(总价 + 税费)  public double calculatePayAmount(Long userId, List<Long> productIds) { double total = calculateTotalPrice(userId, productIds); double tax = calculateTax(total); // 调用私有方法  return total + tax; } } 
测试代码(PowerMock 模拟私有方法)
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit5.PowerMockExtension; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.powermock.api.mockito.PowerMockito.*; @ExtendWith(PowerMockExtension.class) @PrepareForTest(OrderService.class) // 声明需要处理私有方法的类 public class OrderServicePrivateTest { @Test void testCalculatePayAmount_withMockPrivateMethod() throws Exception { // 1. 创建被测试类的 spy 对象(保留真实对象,仅模拟部分方法)  OrderService orderService = spy(new OrderService(mock(UserDao.class), mock(ProductDao.class))); // 2. 模拟私有方法 calculateTax:当输入 300 时,返回 30(而非真实的 300*0.1=30,此处仅演示)  doReturn(30.0).when(orderService, \"calculateTax\", 300.0); // 3. 模拟其他依赖(calculateTotalPrice 返回 300)  doReturn(300.0).when(orderService).calculateTotalPrice(anyLong(), anyList()); // 4. 执行测试  double payAmount = orderService.calculatePayAmount(1L, Arrays.asList(100L, 200L)); // 5. 断言:300(总价) + 30(模拟的税费) = 330  assertEquals(330.0, payAmount); } } 

4. 模拟构造函数

场景

被测试类在方法中直接 new 了一个 LogUtils 对象(硬编码依赖),需模拟该对象的行为。

// 日志工具类(被 new 出来的依赖) public class LogUtils { public void log(String message) { // 实际会写入文件或发送到日志服务,测试时需模拟  System.out.println(\"Log: \" + message); } } public class OrderService { // ... 其他代码省略 ...  // 创建订单并记录日志(直接 new LogUtils)  public void createOrder(Long orderId) { // 业务逻辑...  LogUtils logUtils = new LogUtils(); // 硬编码依赖,难以用 Mockito 模拟  logUtils.log(\"Order created: \" + orderId); // 调用日志方法  } } 
测试代码(PowerMock 模拟构造函数)
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit5.PowerMockExtension; import static org.powermock.api.mockito.PowerMockito.*; @ExtendWith(PowerMockExtension.class) @PrepareForTest({OrderService.class, LogUtils.class}) // 声明相关类 public class OrderServiceConstructorTest { @Test void testCreateOrder_withMockConstructor() throws Exception { // 1. 模拟 LogUtils 的构造函数:当 new LogUtils() 时,返回 Mock 对象  LogUtils mockLogUtils = mock(LogUtils.class); whenNew(LogUtils.class).withNoArguments().thenReturn(mockLogUtils); // 2. 执行测试  OrderService orderService = new OrderService(mock(UserDao.class), mock(ProductDao.class)); orderService.createOrder(123L); // 3. 验证日志方法被正确调用  verify(mockLogUtils).log(\"Order created: 123\"); } } 

三、Mockito 与 PowerMock 最佳实践

  1. 优先使用 Mockito
    Mockito 轻量、简洁,适合大多数场景(模拟接口、普通类)。PowerMock 因需要修改字节码,可能导致测试复杂且与部分框架冲突,仅在必要时使用。

  2. 避免过度模拟
    若频繁需要模拟静态方法、私有方法,可能意味着代码设计存在问题(如耦合过高)。优先通过“依赖注入”“接口抽象”重构代码,减少对 PowerMock 的依赖。

  3. 注意版本兼容性
    PowerMock 与 JUnit、Mockito 版本强相关(如 PowerMock 2.0.9 适配 Mockito 4.x 和 JUnit 5),需严格匹配版本(参考 PowerMock 官方文档)。

  4. 测试命名与可读性
    含 Mock 的测试用例命名应明确模拟对象和场景,例如 testCalculatePayAmount_whenTaxIsMocked_returnsCorrectTotal

四、总结

  • Mockito 是 Java 单元测试的“标配”工具,专注于模拟常规依赖(接口、普通类),通过 @Mock @InjectMocks 简化依赖隔离,核心能力包括模拟行为、验证调用、捕获参数。
  • PowerMock 作为补充,解决 Mockito 无法处理的场景(静态方法、私有方法、构造函数),但需谨慎使用(避免掩盖代码设计问题)。

掌握这两个工具,可有效隔离复杂依赖,聚焦被测试单元的逻辑验证,大幅提升单元测试的覆盖率和可靠性,是保障代码质量的核心手段。以下是Java中使用Mockito进行单元测试的实际场景案例,覆盖常见测试需求(模拟依赖、验证交互、处理返回值/异常、参数匹配等),并附详细说明。

准备工作

  1. 依赖引入(Maven):
<dependencies>  <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency>  <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>4.11.0</version> <scope>test</scope> </dependency>  <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>4.11.0</version> <scope>test</scope> </dependency></dependencies>
  1. 核心注解说明
  • @Mock:创建一个模拟对象(替代真实依赖,避免依赖真实资源如数据库、网络)。
  • @InjectMocks:将@Mock标注的模拟对象自动注入到被测试类中(依赖注入)。
  • @ExtendWith(MockitoExtension.class):JUnit 5扩展,用于初始化Mockito注解的对象。

案例1:基础模拟(Service依赖DAO)

场景:测试用户服务(UserService),其依赖用户DAO(UserDao)。需模拟UserDao,避免真实数据库操作。

代码结构
// 实体类public class User { private Long id; private String name; // 构造器、getter、setter}// DAO接口(依赖)public interface UserDao { User findById(Long id); // 根据ID查询用户 void save(User user); // 保存用户}// 服务类(被测试)public class UserService { private final UserDao userDao; // 依赖注入的DAO public UserService(UserDao userDao) { // 构造器注入 this.userDao = userDao; } // 根据ID查询用户(调用DAO) public User getUserById(Long id) { if (id == null || id <= 0) { throw new IllegalArgumentException(\"ID非法\"); } return userDao.findById(id); } // 注册用户(调用DAO保存) public void register(User user) { if (user == null || user.getName() == null) { throw new IllegalArgumentException(\"用户信息不能为空\"); } userDao.save(user); }}
测试代码
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.junit.jupiter.api.Assertions.*;import static org.mockito.Mockito.*;@ExtendWith(MockitoExtension.class) // 初始化Mockito注解class UserServiceTest { @Mock // 模拟UserDao依赖 private UserDao userDao; @InjectMocks // 将userDao注入到userService中 private UserService userService; // 测试:查询存在的用户(DAO返回非空) @Test void getUserById_Exist() { // 1. 准备测试数据 Long userId = 1L; User mockUser = new User(userId, \"张三\"); // 2. 模拟依赖行为:当调用userDao.findById(1L)时,返回mockUser when(userDao.findById(userId)).thenReturn(mockUser); // 3. 调用被测试方法 User result = userService.getUserById(userId); // 4. 断言结果 assertNotNull(result); assertEquals(userId, result.getId()); assertEquals(\"张三\", result.getName()); // 5. 验证交互:确保userDao.findById(1L)被调用了1次 verify(userDao, times(1)).findById(userId); } // 测试:查询不存在的用户(DAO返回null) @Test void getUserById_NotExist() { // 1. 准备测试数据 Long userId = 999L; // 2. 模拟依赖行为:当调用userDao.findById(999L)时,返回null when(userDao.findById(userId)).thenReturn(null); // 3. 调用被测试方法 User result = userService.getUserById(userId); // 4. 断言结果为null assertNull(result); // 5. 验证交互 verify(userDao, times(1)).findById(userId); } // 测试:输入非法ID(触发参数校验异常) @Test void getUserById_InvalidId() { // 1. 准备非法ID(如-1) Long invalidId = -1L; // 2. 调用被测试方法,预期抛出IllegalArgumentException IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, () -> userService.getUserById(invalidId) ); // 3. 断言异常信息 assertEquals(\"ID非法\", exception.getMessage()); // 4. 验证交互:确保DAO未被调用(因为参数校验失败,不会执行DAO查询) verify(userDao, never()).findById(any()); // any()表示任意参数 } // 测试:注册用户(验证DAO的save方法被正确调用) @Test void register_ValidUser() { // 1. 准备测试数据 User user = new User(null, \"李四\"); // 2. 调用被测试方法(无需模拟save返回值,因为save是void方法) userService.register(user); // 3. 验证交互:确保userDao.save(user)被调用了1次 verify(userDao, times(1)).save(user); } // 测试:注册空用户(触发参数校验异常) @Test void register_NullUser() { // 1. 调用被测试方法,预期抛出异常 IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, () -> userService.register(null) ); // 2. 断言异常信息 assertEquals(\"用户信息不能为空\", exception.getMessage()); // 3. 验证DAO的save方法未被调用 verify(userDao, never()).save(any()); }}

案例2:模拟异常抛出

场景:测试依赖方法抛出异常时,被测试类的处理逻辑。例如:DAO查询时抛出SQLException,服务层是否正确捕获并转换为自定义异常。

代码结构
// 自定义异常public class DataAccessException extends RuntimeException { public DataAccessException(String message) { super(message); }}// DAO接口(新增可能抛异常的方法)public interface UserDao { // ... 原有方法 User findByUsername(String username) throws SQLException; // 可能抛SQLException}// 服务类(处理异常)public class UserService { // ... 原有代码 public User getUserByUsername(String username) { try { return userDao.findByUsername(username); } catch (SQLException e) { // 将SQLException转换为自定义异常 throw new DataAccessException(\"查询用户失败:\" + e.getMessage()); } }}
测试代码
@Testvoid getUserByUsername_DaoThrowException() throws SQLException { // 1. 准备测试数据 String username = \"test\"; SQLException mockSqlEx = new SQLException(\"数据库连接超时\"); // 2. 模拟依赖行为:当调用findByUsername(\"test\")时,抛出mockSqlEx when(userDao.findByUsername(username)).thenThrow(mockSqlEx); // 3. 调用被测试方法,预期抛出自定义异常 DataAccessException exception = assertThrows( DataAccessException.class, () -> userService.getUserByUsername(username) ); // 4. 断言异常信息 assertTrue(exception.getMessage().contains(\"查询用户失败:数据库连接超时\")); // 5. 验证交互 verify(userDao, times(1)).findByUsername(username);}

案例3:参数匹配器(复杂参数场景)

场景:当方法参数复杂(如对象、集合),无法直接用具体值匹配时,使用Mockito的参数匹配器(如any()eq()argThat())。

代码结构
// 订单服务(被测试)public class OrderService { private final InventoryService inventoryService; // 依赖库存服务 public OrderService(InventoryService inventoryService) { this.inventoryService = inventoryService; } // 创建订单前检查库存 public boolean createOrder(Long productId, int quantity) { // 调用库存服务检查是否有足够库存 return inventoryService.checkStock(productId, quantity); }}// 库存服务(依赖)public interface InventoryService { boolean checkStock(Long productId, int quantity); // 检查库存}
测试代码
@ExtendWith(MockitoExtension.class)class OrderServiceTest { @Mock private InventoryService inventoryService; @InjectMocks private OrderService orderService; @Test void createOrder_WithParamMatchers() { // 1. 准备测试数据 Long productId = 100L; int quantity = 5; // 2. 模拟依赖行为:使用参数匹配器 // 当productId为任意Long类型,且quantity大于0时,返回true when(inventoryService.checkStock(any(Long.class), argThat(q -> q > 0))) .thenReturn(true); // 3. 调用被测试方法 boolean result = orderService.createOrder(productId, quantity); // 4. 断言结果 assertTrue(result); // 5. 验证交互(使用参数匹配器) verify(inventoryService, times(1)) .checkStock(eq(productId), eq(quantity)); // eq()用于精确匹配 }}

常用参数匹配器

  • any(Type.class):匹配任意该类型的参数(如any(Long.class))。
  • eq(value):匹配等于value的参数(如eq(100L))。
  • argThat(predicate):自定义匹配器(如argThat(q -> q > 0)匹配正整数)。

案例4:Spy(部分模拟真实对象)

场景:需要保留对象的真实行为,仅模拟部分方法时,使用Spy(间谍)。与@Mock(完全模拟)不同,Spy会调用对象的真实方法,除非被显式模拟。

代码结构
// 工具类(部分方法需要真实调用,部分需要模拟)public class StringUtils { // 真实方法:反转字符串 public String reverse(String str) { if (str == null) return null; return new StringBuilder(str).reverse().toString(); } // 需要模拟的方法:调用外部服务(测试时不希望真实调用) public boolean isExternalValid(String str) { // 真实逻辑:调用外部API,测试时需模拟 return true; }}// 依赖工具类的服务public class StringProcessor { private final StringUtils stringUtils; public StringProcessor(StringUtils stringUtils) { this.stringUtils = stringUtils; } public String process(String str) { if (!stringUtils.isExternalValid(str)) { // 依赖isExternalValid return \"无效字符串\"; } return stringUtils.reverse(str); // 依赖reverse }}
测试代码
@Testvoid process_WithSpy() { // 1. 创建真实对象的Spy(保留reverse的真实逻辑,模拟isExternalValid) StringUtils stringUtilsSpy = spy(new StringUtils()); // 2. 模拟Spy的部分方法:当调用isExternalValid(\"test\")时,返回true when(stringUtilsSpy.isExternalValid(\"test\")).thenReturn(true); // 3. 初始化被测试对象 StringProcessor processor = new StringProcessor(stringUtilsSpy); // 4. 调用被测试方法 String result = processor.process(\"test\"); // 5. 断言结果:reverse的真实逻辑被执行(\"test\"反转为\"tset\") assertEquals(\"tset\", result); // 6. 验证交互 verify(stringUtilsSpy, times(1)).isExternalValid(\"test\"); verify(stringUtilsSpy, times(1)).reverse(\"test\");}

核心总结

Mockito的核心价值是隔离被测试类与依赖,通过以下方式实现:

  1. 模拟依赖:用@Mock创建依赖的模拟对象,避免真实资源调用。
  2. Stub行为:用when(xxx).thenReturn(yyy)thenThrow(zzz)定义依赖的返回值/异常。
  3. 验证交互:用verify(xxx, times(n)).method(...)确保依赖被正确调用。
  4. 灵活匹配:用参数匹配器(any()eq()等)处理复杂参数场景。
  5. 部分真实:用Spy保留对象的真实行为,仅模拟需要的方法。

通过上述案例,可覆盖大部分日常单元测试场景,关键是明确“被测试类的逻辑”与“依赖的模拟边界”,确保测试聚焦于目标代码的逻辑正确性。