PowerMockito实战指南:破解Java单元测试中静态与私有方法难题

发布时间:2026/7/6 13:21:39
PowerMockito实战指南:破解Java单元测试中静态与私有方法难题 1. 项目概述为什么我们需要PowerMockito在Java开发里单元测试是保证代码质量的基石Mockito几乎是每个Java开发者都会接触到的测试框架。但当你兴冲冲地写完一个测试准备运行却看到控制台报出java.lang.UnsupportedOperationException提示你无法模拟一个静态方法时那种感觉就像被泼了一盆冷水。我经历过太多次了尤其是在处理遗留代码或者集成一些设计上不那么“测试友好”的第三方库时。这就是PowerMockito登场的时候。它不是一个独立的框架而是Mockito的一个强大扩展。简单来说Mockito能做的模拟对象、验证行为PowerMockito都能做但Mockito做不到的比如模拟静态方法、私有方法、构造函数、final类/方法PowerMockito也能做。它的核心价值在于“破除限制”让你能够对那些传统上难以测试的“硬骨头”代码进行隔离和验证从而真正实现高覆盖率的单元测试。这篇文章我会从一个有十多年踩坑经验的开发者角度带你深入PowerMockito的实战。我不会只给你一堆干巴巴的API列表而是会结合真实的开发场景告诉你什么时候该用、怎么用、以及用了之后可能会遇到哪些“坑”。我们会重点攻克两个最让人头疼的难题静态方法的模拟和私有方法的测试。无论你是正在为老项目补测试而头疼还是在新项目中想提前规避测试难点这篇指南都能给你提供可以直接“抄作业”的解决方案。2. 环境搭建与核心配置避坑指南工欲善其事必先利其器。PowerMockito的配置是第一个拦路虎版本冲突和配置错误能浪费你大半天时间。下面我以最常用的构建工具Maven和Gradle为例给出经过大量项目验证的稳定配置。2.1 Maven依赖配置关键点在于版本对齐。Mockito、PowerMock的模块以及JUnit之间必须版本兼容。我强烈推荐使用以下这套组合它在Java 8和Java 11的环境中都非常稳定。properties powermock.version2.0.9/powermock.version mockito.version3.12.4/mockito.version !-- 确保与PowerMock兼容 -- /properties dependencies !-- JUnit 4 (PowerMock 2.x 主要支持 JUnit 4) -- dependency groupIdjunit/groupId artifactIdjunit/artifactId version4.13.2/version scopetest/scope /dependency !-- Mockito Core -- dependency groupIdorg.mockito/groupId artifactIdmockito-core/artifactId version${mockito.version}/version scopetest/scope /dependency !-- PowerMock 核心模块提供了特殊的类加载器 -- dependency groupIdorg.powermock/groupId artifactIdpowermock-module-junit4/artifactId version${powermock.version}/version scopetest/scope /dependency !-- PowerMock API for Mockito 2这是与Mockito集成的桥梁 -- dependency groupIdorg.powermock/groupId artifactIdpowermock-api-mockito2/artifactId version${powermock.version}/version scopetest/scope /dependency /dependencies注意如果你在使用JUnit 5情况会复杂一些。PowerMock对JUnit 5的支持通过powermock-module-junit5在稳定性和社区活跃度上不如JUnit 4。对于新项目我建议优先考虑通过重构代码来避免使用PowerMock。如果必须用可能需要额外配置PowerMockExtension这超出了本文基础范围但你可以将其作为一个探索方向。2.2 Gradle依赖配置Gradle的配置逻辑与Maven类似但语法不同。同样要注意版本统一。dependencies { testImplementation junit:junit:4.13.2 testImplementation org.mockito:mockito-core:3.12.4 testImplementation org.powermock:powermock-module-junit4:2.0.9 testImplementation org.powermock:powermock-api-mockito2:2.0.9 }2.3 测试类的基本注解配置依赖加好了写测试类时还需要两个关键注解来激活PowerMockito的“超能力”。import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; // 1. 指定使用PowerMockRunner来运行测试 RunWith(PowerMockRunner.class) // 2. 告诉PowerMock准备修改哪个类的字节码对于静态方法、final类等 PrepareForTest({YourStaticClass.class, ClassWithPrivateMethod.class}) public class YourTestClass { // 你的测试方法... }核心原理与避坑点PrepareForTest是PowerMockito的灵魂。它告诉PowerMockito“等下运行测试时你需要对括号里的这些类动点手脚修改其字节码以便我能模拟它们的行为。” 这个“动手脚”的过程发生在类加载时这也是为什么PowerMockito需要一个特殊的类加载器PowerMockRunner。最常见的坑忘记添加PrepareForTest或者加错了类。如果你要模拟ClassA的静态方法那么PrepareForTest中必须包含ClassA。更隐蔽的一种情况是如果ClassA调用了ClassB的静态方法而你也想模拟ClassB的静态方法那么ClassB也需要被PrepareForTest。一个稳妥的做法是把所有需要被模拟静态方法、final方法或构造函数的类都加进去。3. 静态方法模拟实战从简单到复杂静态方法调用比如Utils.generateUUID(),DateUtils.format(), 或者第三方库的SomeSDK.init()是单元测试中最常见的“污染源”。它们让测试依赖于全局状态或外部环境。PowerMockito的mockStatic方法就是来解决这个问题的。3.1 基础模拟让静态方法返回指定值假设我们有一个简单的工具类StringUtilspublic class StringUtils { public static boolean isEmpty(String str) { return str null || str.trim().isEmpty(); } }我们的业务代码UserService依赖了这个静态方法public class UserService { public String processUsername(String input) { if (StringUtils.isEmpty(input)) { return Guest; } return User: input; } }如何测试UserService而隔离StringUtils.isEmpty的行为呢RunWith(PowerMockRunner.class) PrepareForTest({StringUtils.class}) // 关键准备篡改StringUtils类 public class UserServiceTest { Test public void testProcessUsername_WhenInputIsEmpty_ReturnsGuest() { // 1. 模拟静态类 PowerMockito.mockStatic(StringUtils.class); // 2. 定义静态方法的行为当调用isEmpty(anyString())时返回true PowerMockito.when(StringUtils.isEmpty(Mockito.anyString())).thenReturn(true); // 3. 执行测试 UserService service new UserService(); String result service.processUsername(实际上这个值无关紧要); // 4. 断言 assertEquals(Guest, result); } Test public void testProcessUsername_WhenInputIsNotEmpty_ReturnsFormattedName() { PowerMockito.mockStatic(StringUtils.class); // 定义当传入特定参数Alice时返回false PowerMockito.when(StringUtils.isEmpty(Alice)).thenReturn(false); UserService service new UserService(); String result service.processUsername(Alice); assertEquals(User: Alice, result); } }实操心得PowerMockito.mockStatic(Class)是入口。之后你就可以像对待一个普通的Mockito模拟对象一样使用PowerMockito.when().thenReturn()来定义静态方法的行为。注意一旦对一个类执行了mockStatic这个类所有的静态方法都会被默认“置空”返回null、0或false。你必须为测试中会调用到的每一个静态方法显式定义行为否则可能会因为返回null而导致NPE。3.2 验证静态方法调用单元测试不仅要验证结果还要验证行为即方法是否被以正确的参数调用了。Mockito用verifyPowerMockito用verifyStatic。Test public void testProcessUsername_VerifiesStaticMethodCall() { PowerMockito.mockStatic(StringUtils.class); PowerMockito.when(StringUtils.isEmpty(Mockito.anyString())).thenReturn(false); UserService service new UserService(); service.processUsername(TestUser); // 验证静态方法被调用了一次且参数是TestUser PowerMockito.verifyStatic(StringUtils.class, Mockito.times(1)); StringUtils.isEmpty(TestUser); // 这行是验证的关键不是调用 }这里有一个巨坑PowerMockito.verifyStatic的用法非常反直觉。它分为两步PowerMockito.verifyStatic(Class, VerificationMode) 启动验证模式。VerificationMode可以是times(1),never()等和Mockito一样。紧接着必须直接调用一次你想要验证的那个静态方法。注意这行代码不是真正的调用它不会执行方法逻辑它只是告诉PowerMockito“我要验证的是这个方法”。如果你忘了写这第二行或者写错了方法/参数验证就不会生效或者会报错。3.3 模拟void静态方法有些静态方法没有返回值只产生副作用比如日志记录Logger.log()。模拟它们需要使用doNothing().when()或doThrow().when()的语法。public class SystemConfig { public static void loadConfig(String path) { // 从文件加载配置复杂且依赖外部环境 System.out.println(Loading config from: path); } } public class AppInitializer { public void init() { SystemConfig.loadConfig(app.properties); // ... 其他初始化 } }测试AppInitializer.init()时我们不想真的去读文件。RunWith(PowerMockRunner.class) PrepareForTest({SystemConfig.class}) public class AppInitializerTest { Test public void testInit_CallsLoadConfig() { PowerMockito.mockStatic(SystemConfig.class); // 模拟void静态方法当调用loadConfig时什么都不做 PowerMockito.doNothing().when(SystemConfig.class); SystemConfig.loadConfig(Mockito.anyString()); // 注意这里的语法 AppInitializer initializer new AppInitializer(); initializer.init(); // 这里不会真的执行loadConfig的逻辑 // 验证它被以特定参数调用了一次 PowerMockito.verifyStatic(SystemConfig.class, Mockito.times(1)); SystemConfig.loadConfig(app.properties); } Test(expected RuntimeException.class) public void testInit_WhenLoadConfigFails_ThrowsException() { PowerMockito.mockStatic(SystemConfig.class); // 模拟void静态方法抛出异常 PowerMockito.doThrow(new RuntimeException(Config file not found)) .when(SystemConfig.class); SystemConfig.loadConfig(Mockito.anyString()); AppInitializer initializer new AppInitializer(); initializer.init(); // 这里会抛出RuntimeException } }注意事项模拟void静态方法的语法PowerMockito.doNothing().when(SystemConfig.class);后面跟着的那行SystemConfig.loadConfig(...)是定义行为的一部分它和验证时的写法很像但作用完全不同。这里是定义“当调用loadConfig时做什么”。一定要区分清楚。4. 私有方法测试与模拟谨慎使用的利器测试私有方法一直是一个有争议的话题。测试驱动开发TDD和纯粹主义学派认为只应该通过公共接口测试私有行为。我赞同这个观点在理想情况下。但在现实中尤其是面对遗留代码或极其复杂的算法时直接测试一个私有方法能更快地定位问题。PowerMockito给了我们这种能力但我要强调请把它当作最后的手段。4.1 测试私有方法调用并断言假设我们有一个Calculator类里面有一个复杂的私有方法validateAndParsepublic class Calculator { public int complexCalculation(String input) { int[] numbers validateAndParse(input); // 调用私有方法 // ... 复杂的计算逻辑 return result; } private int[] validateAndParse(String input) { if (input null || !input.matches(\\d,\\d)) { throw new IllegalArgumentException(Invalid input format); } String[] parts input.split(,); return new int[]{Integer.parseInt(parts[0]), Integer.parseInt(parts[1])}; } }我们想单独测试validateAndParse这个私有方法的逻辑是否正确。RunWith(PowerMockRunner.class) PrepareForTest({Calculator.class}) // 注意这里准备的是被测类本身 public class CalculatorTest { Test public void testPrivateValidateAndParse_ValidInput_ReturnsArray() throws Exception { Calculator calculator new Calculator(); // 使用PowerMockito的Whitebox类来调用私有方法 // 参数被测对象实例 方法名 方法的参数 int[] result Whitebox.invokeMethod(calculator, validateAndParse, 10,20); assertNotNull(result); assertEquals(2, result.length); assertEquals(10, result[0]); assertEquals(20, result[1]); } Test(expected IllegalArgumentException.class) public void testPrivateValidateAndParse_InvalidInput_ThrowsException() throws Exception { Calculator calculator new Calculator(); // 调用私有方法预期会抛出异常 Whitebox.invokeMethod(calculator, validateAndParse, invalid); } }核心工具org.powermock.reflect.Whitebox。它提供了反射的便捷封装用于访问私有成员。invokeMethod是其中最常用的方法。重要提醒直接测试私有方法会让测试变得脆弱。如果重构时改变了私有方法的名字、参数或返回值类型测试就会直接失败即使公共接口的行为没有变。这违反了“测试应该关注行为而非实现”的原则。因此仅在逻辑极其独立、复杂且通过公共接口测试成本过高时使用。4.2 模拟私有方法替换其实现更罕见但有时必要的场景是在测试一个公共方法时我们希望屏蔽其中某个私有方法的真实逻辑而是让它返回一个我们预设的值或抛出异常。这通常用于在公共方法中模拟私有方法调用失败或返回特定中间结果的情况。public class OrderProcessor { public OrderResult processOrder(Order order) { if (!validateOrderInternally(order)) { // 调用了一个复杂的私有验证方法 return OrderResult.failed(Validation failed); } // ... 处理订单 return OrderResult.success(); } private boolean validateOrderInternally(Order order) { // 非常复杂的业务规则验证涉及数据库、规则引擎等 // 我们希望在单元测试中跳过它 return complexValidationLogic(order); } }在测试processOrder时我们想隔离那个复杂的validateOrderInternally方法。RunWith(PowerMockRunner.class) PrepareForTest({OrderProcessor.class}) public class OrderProcessorTest { Test public void testProcessOrder_WhenPrivateValidationFails_ReturnsFailedResult() throws Exception { Order order new Order(); OrderProcessor processor PowerMockito.spy(new OrderProcessor()); // 关键使用spy // 模拟私有方法的行为 PowerMockito.doReturn(false) .when(processor, validateOrderInternally, Mockito.any(Order.class)); OrderResult result processor.processOrder(order); assertEquals(Validation failed, result.getMessage()); assertFalse(result.isSuccess()); } Test public void testProcessOrder_WhenPrivateValidationSucceeds_Proceeds() throws Exception { Order order new Order(); OrderProcessor processor PowerMockito.spy(new OrderProcessor()); // 模拟私有方法返回true PowerMockito.doReturn(true) .when(processor, validateOrderInternally, Mockito.any(Order.class)); OrderResult result processor.processOrder(order); // 这里可以继续验证成功后的逻辑... assertTrue(result.isSuccess()); } }关键技巧使用spy 而不是mockPowerMockito.spy(new OrderProcessor())创建了一个真实对象的“间谍”。默认情况下它会调用真实方法但允许你对特定的方法包括私有方法进行模拟stub。模拟私有方法的语法PowerMockito.doReturn(...).when(spyObject, methodName, arguments...)。注意方法名是字符串参数可以使用Matcher如Mockito.any()。踩坑实录模拟私有方法时参数匹配必须非常精确。如果你用doReturn(false).when(spy, method, specificArg)定义了当参数为specificArg时返回false那么当被测代码用另一个参数调用该私有方法时模拟不会生效会执行真实方法。确保你的参数匹配器能覆盖所有测试用例中的调用。5. 进阶技巧与复杂场景处理掌握了静态和私有方法的基本操作后我们来看看一些更复杂的场景这些才是真正体现PowerMockito价值的地方。5.1 模拟静态方法的连续调用链式调用有时一个静态方法会被多次调用每次需要返回不同的值。这在模拟工具类生成序列如ID生成器时很常见。public class IdGenerator { public static long nextId() { // 访问数据库或序列服务 return System.currentTimeMillis(); // 简化示例 } } public class BatchService { public ListLong createBatch(int size) { ListLong ids new ArrayList(); for (int i 0; i size; i) { ids.add(IdGenerator.nextId()); // 多次调用静态方法 } return ids; } }测试createBatch方法RunWith(PowerMockRunner.class) PrepareForTest({IdGenerator.class}) public class BatchServiceTest { Test public void testCreateBatch_GeneratesMultipleIds() { PowerMockito.mockStatic(IdGenerator.class); // 模拟连续调用第一次返回100L第二次返回200L第三次及以后返回300L PowerMockito.when(IdGenerator.nextId()) .thenReturn(100L) .thenReturn(200L) .thenReturn(300L); // Mockito的链式语法依然适用 BatchService service new BatchService(); ListLong ids service.createBatch(4); assertEquals(Arrays.asList(100L, 200L, 300L, 300L), ids); // 验证被调用了4次 PowerMockito.verifyStatic(IdGenerator.class, Mockito.times(4)); IdGenerator.nextId(); // 验证调用次数不验证具体参数 } }这和模拟普通对象方法的连续调用语法完全一样thenReturn可以链式调用。非常直观。5.2 模拟构造方法当你测试的代码中使用了new关键字来创建依赖对象而这个对象又很难构造比如依赖网络、文件系统时模拟构造函数就派上用场了。这能让你完全控制这个新对象的产生。public class FileProcessor { public String process(String filePath) throws IOException { // 直接new了一个难以测试的依赖 FileReader reader new FileReader(filePath); // ... 处理文件内容 return content; } }我们想测试process方法但不想真的去读文件。RunWith(PowerMockRunner.class) // 需要准备两个类1. 创建对象的类(FileReader) 2. 使用new的类(FileProcessor) PrepareForTest({FileProcessor.class, FileReader.class}) public class FileProcessorTest { Test public void testProcess_WithMockedFileReader() throws Exception { String expectedContent Mocked file content; String mockFilePath test.txt; // 1. 模拟FileReader类 FileReader mockedReader Mockito.mock(FileReader.class); // 假设我们有一个方法可以模拟读取内容 Mockito.when(mockedReader.readContent()).thenReturn(expectedContent); // 2. 当使用new创建FileReader时返回我们模拟的对象 PowerMockito.whenNew(FileReader.class) .withArguments(Mockito.eq(mockFilePath)) // 匹配构造函数参数 .thenReturn(mockedReader); FileProcessor processor new FileProcessor(); String result processor.process(mockFilePath); assertEquals(expectedContent, result); // 3. 验证FileReader的构造函数被以正确的参数调用了一次 PowerMockito.verifyNew(FileReader.class).withArguments(mockFilePath); } }操作步骤解析whenNew(Class).withArguments(...).thenReturn(mockObj) 这是核心API。它拦截了对指定类的构造函数调用。withArguments用于匹配构造函数的参数必须精确匹配否则模拟不会生效会执行真实的构造函数。verifyNew用于验证构造函数是否被调用以及调用时的参数。重大注意事项模拟构造函数是一种“代码有异味”的强烈信号。它通常意味着你的类违反了依赖注入原则将依赖的创建硬编码在了内部导致高耦合和难以测试。在可能的情况下优先考虑重构代码通过构造函数或Setter方法将依赖如FileReader注入进去这样在测试时就可以轻松地用Mockito模拟了。把PowerMockito的构造函数模拟当作处理遗留代码的临时手段而不是新代码的设计模式。5.3 结合Mockito使用模拟部分静态方法有时你只想模拟一个类中的某个静态方法而其他静态方法保持原样。PowerMockito的默认行为是 mockStatic 后所有静态方法都被置空。要实现“部分模拟”需要用到PowerMockito.spy对类进行监控。public class MathUtils { public static int add(int a, int b) { return a b; // 简单的加法我们不想模拟 } public static int complexOperation(int a) { // 非常复杂、耗时的计算我们想在测试中跳过 return expensiveCalculation(a); } } public class BusinessService { public int doBusiness(int x, int y) { int sum MathUtils.add(x, y); // 用真实方法 int processed MathUtils.complexOperation(sum); // 想模拟这个方法 return processed * 2; } }测试策略保持add的真实逻辑模拟complexOperation。RunWith(PowerMockRunner.class) PrepareForTest({MathUtils.class}) public class BusinessServiceTest { Test public void testDoBusiness_PartialMockOfStaticClass() { // 关键对类本身使用spy而不是mock PowerMockito.spy(MathUtils.class); // 让add方法保持真实行为什么都不做就是调用真实方法 // 模拟complexOperation方法让它直接返回100 PowerMockito.doReturn(100) .when(MathUtils.class); // 注意语法 MathUtils.complexOperation(Mockito.anyInt()); // 这行定义要模拟哪个方法 BusinessService service new BusinessService(); int result service.doBusiness(5, 3); // add(5,3)8, complexOperation(8)被模拟返回100 // 最终结果应该是 100 * 2 200 assertEquals(200, result); // 可以验证complexOperation被调用了一次且参数是8 PowerMockito.verifyStatic(MathUtils.class, Mockito.times(1)); MathUtils.complexOperation(8); } }语法细节对静态类使用spy后默认所有静态方法都走真实逻辑。当你需要模拟其中某一个时使用doReturn().when(Class)的语法后面紧跟你要模拟的那个静态方法调用。这实现了对单个静态方法的精确打击。6. 常见问题排查与最佳实践即使按照指南操作你也可能会遇到一些奇怪的问题。下面是我总结的常见“坑”及其解决方案。6.1 问题排查速查表问题现象可能原因解决方案java.lang.NoClassDefFoundError: org/powermock/core/classloader/MockClassLoader依赖冲突或版本不匹配。特别是powermock-module-junit4和powermock-api-mockito2版本不一致或与Mockito核心版本不兼容。1. 检查并统一所有PowerMock相关依赖的版本号。2. 确保Mockito版本与PowerMock兼容官方文档有版本对应表。3. 运行mvn dependency:tree或gradle dependencies查看是否有冲突使用exclusion排除旧版本。java.lang.ExceptionInInitializerError或ClassCastExceptionPrepareForTest注解配置错误可能包含了不需要的类或者漏掉了关键的类如被模拟类的父类、或内部类。1. 仔细检查PrepareForTest确保它包含了所有需要被字节码修改的类即所有包含需要模拟的静态方法、final方法、构造函数的类。2. 如果模拟的是一个工具类的静态方法而这个工具类在静态初始化块中加载了本地库或进行了复杂操作模拟可能会破坏其初始化。考虑重构代码或将此类依赖封装。静态方法模拟不生效仍然调用了真实方法1. 忘记调用PowerMockito.mockStatic。2.PrepareForTest中未包含目标类。3. 在定义模拟行为 (when...thenReturn) 之前被测代码已经加载了那个静态类例如在测试类的Before方法中提前触发了类加载。1. 确认已调用mockStatic。2. 确认PrepareForTest配置正确。3.确保模拟行为发生在类被加载之前。对于JUnit 4将mockStatic和when调用放在Test方法内部而不是Before中是最安全的做法。verifyStatic验证失败即使方法确实被调用了verifyStatic的用法错误。最常见的是忘记了后面必须紧跟一行要验证的静态方法调用。严格按照两步走1.PowerMockito.verifyStatic(Class, VerificationMode);2.StaticClass.methodToVerify(arguments);// 这行必须有测试运行速度异常缓慢PowerMockito使用了自定义的类加载器每次测试都可能重新加载类这本身就有开销。如果PrepareForTest包含了大量类或测试套件很大速度会明显下降。1. 审视是否过度使用PowerMockito。能否通过重构如依赖注入、将静态方法改为实例方法来避免使用它2. 合并PrepareForTest将多个测试类需要的类集中写在一个父类的注解中减少重复准备。3. 这是使用PowerMockito必须接受的代价在CI/CD流水线中可能更明显。6.2 最佳实践与心得把它当作“最后的手段”这是最重要的原则。PowerMockito很强大但滥用它会掩盖糟糕的设计。如果你的代码大量需要PowerMockito才能测试这是一个强烈的重构信号。优先考虑依赖注入将静态工具类或难以构造的对象作为接口依赖通过构造函数或Setter传入这样在测试中就可以用普通的Mockito轻松模拟。封装与适配将第三方库的静态调用或复杂初始化封装到你自己的服务类中然后只模拟这个服务类。测试替身对于一些简单的静态方法如System.currentTimeMillis()可以考虑在代码中引入一个可控的“时间提供器”接口。保持测试的独立性PowerMockito会修改类的字节码这可能会影响JVM中该类的状态。虽然PowerMockRunner会尽力隔离但为了绝对安全不要在不同的测试方法中共享对静态方法的模拟状态。每个Test方法都应该独立地设置和清理自己的模拟。避免在Before中设置全局的静态模拟除非你非常清楚其影响。明确PrepareForTest的范围只添加必要的类。添加的类越多测试加载越慢且潜在的冲突风险越高。仔细分析你的调用链。为PowerMockito测试单独分类由于运行较慢可以考虑在构建脚本如Maven的surefire插件配置中将使用了PowerMockito的测试类单独标记和运行与快速单元测试分开。关注版本更新与替代方案PowerMockito社区活跃度已不如前且与新一代的JUnit 5/Jupiter、Mockito 4的集成可能存在挑战。对于新项目可以关注像Mockito 5 的mockStatic(Inline Mock Maker)功能它不需要特殊的Runner或类加载器是更现代、更轻量的选择。当然它的能力如模拟私有方法、构造函数目前还不如PowerMockito全面但代表了未来的方向。在我多年的经验里PowerMockito就像一把锋利的手术刀在解剖遗留代码、编写针对性测试时无可替代。但它不应该成为你编写新代码时的默认工具。良好的设计本身就应该具有可测试性。当你不得不拿起这把手术刀时希望这份指南能帮你用得精准、高效少走弯路。