如何测试调用了受保护方法的类公共方法?三种测试方案选型咨询
结论
方案2是完全符合你需求的可行方案,不需要修改现有业务代码,也没有重复测试成本,用PHP主流测试框架PHPUnit的部分模拟(Partial Mock) 功能就能实现。
测试分层逻辑说明
你可以把测试拆成两个独立部分,完全解耦:
- 对公共方法
b、c的测试:仅验证对外接口逻辑是否正确,不需要关心内部方法d的实现 - 对受保护方法
d的单独测试:覆盖d的所有分支逻辑,只需要编写1次对应测试用例
具体实现代码(基于PHPUnit)
1. 公共方法b/c的测试
创建类a的部分模拟对象,仅替换受保护方法d,其他方法保留原有业务逻辑,不需要处理d内部依赖的service5、service6:
public function testB() { // 仅mock d方法,其余方法保留原实现 $mockA = $this->getMockBuilder(a::class) ->onlyMethods(['d']) ->getMock(); // 注入mock后的service1、service2,配置对应返回值 $mockService1 = $this->createMock(service1::class); $mockService1->method('getArg1')->willReturn('val1'); $mockA->service1 = $mockService1; $mockService2 = $this->createMock(service2::class); $mockService2->method('getArg2')->willReturn('val2'); $mockA->service2 = $mockService2; // 场景1:d返回真值,验证b返回true $mockA->method('d') ->with('val1', 'val2') // 可选校验传给d的参数是否正确 ->willReturn('非空结果'); $this->assertTrue($mockA->b()); // 场景2:d返回null,验证b返回false $mockA->method('d') ->with('val1', 'val2') ->willReturn(null); $this->assertFalse($mockA->b()); }
方法c的测试逻辑和上面完全一致,仅需要替换注入的service3、service4即可。
2. 受保护方法d的单独测试
可以通过临时子类暴露受保护方法,仅在这一处处理service5、service6的mock,覆盖两个分支场景:
public function testD() { // 创建临时子类,把d方法暴露为公共方法 $testA = new class extends a { public function d($arg1, $arg2) { return parent::d($arg1, $arg2); } }; // 注入mock后的service5、service6,配置对应返回值 $mockService5 = $this->createMock(service5::class); // 按需配置service5的方法调用预期 $testA->service5 = $mockService5; $mockService6 = $this->createMock(service6::class); // 按需配置service6的方法调用预期 $testA->service6 = $mockService6; // 场景1:arg1等于arg2,验证返回结果符合预期 $this->assertEquals('预期结果值', $testA->d('相同值', '相同值')); // 场景2:arg1不等于arg2,验证返回null $this->assertNull($testA->d('val1', 'val2')); }
三种方案对比参考
- 方案1:重复工作量大,后续
d逻辑调整时需要同步修改b、c的所有测试用例,维护成本最高,不推荐 - 方案3:符合单一职责设计原则,如果后续
d的逻辑会扩展、会被其他类复用的话更推荐,但你不需要调整现有代码结构的话没必要选择 - 方案2:零业务代码侵入,测试逻辑清晰,维护成本低,完全匹配你当前的需求
内容的提问来源于stack exchange,提问作者Tom Explore
相关产品推荐
相关产品推荐

