如何在单元测试中高效验证Controller兼容各Shape子类实例?
无冗余的Controller单元测试方案
核心思路:拆分两个独立测试维度
把测试拆成互不重叠的两部分,彻底避免场景与Shape类型的组合冗余:
- 场景路径覆盖:聚焦Controller的校验逻辑和业务分支,用单个Shape实例(或Mock对象)覆盖所有成功、错误等场景——毕竟Controller的逻辑只依赖
Shape接口,和具体子类无关。 - Shape兼容性验证:单独验证所有6种Shape子类都能被Controller正确接收并传递给Service,无需重复场景逻辑。
具体实现(基于JUnit 5 + Mockito)
1. 场景路径覆盖测试
随便选一个具体Shape子类(比如Circle)或者Mock的Shape对象,就能覆盖doSomething的所有分支:
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.*; @ExtendWith(MockitoExtension.class) class ControllerScenarioTests { @Mock private Service mockService; @InjectMocks private Controller controller; // 测试校验成功的正常流程 @Test void doSomething_ValidShape_CallsServiceSendData() { Shape validShape = new Circle(); controller.doSomething(validShape); // 验证Service的sendData被正确调用 verify(mockService).sendData(validShape); } // 测试空Shape触发的校验错误 @Test void doSomething_NullShape_ThrowsValidationException() { org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> { controller.doSomething(null); }); // 验证Service没被调用 verify(mockService, never()).sendData(any(Shape.class)); } // 测试不符合业务规则的错误分支(比如Shape属性不合法) @Test void doSomething_InvalidShape_ThrowsBusinessException() { // 用Mock模拟不符合校验的Shape Shape invalidShape = mock(Shape.class); // 如果校验依赖Shape的方法,可通过Mock返回非法值: // when(invalidShape.someValidationMethod()).thenReturn(false); org.junit.jupiter.api.Assertions.assertThrows(BusinessException.class, () -> { controller.doSomething(invalidShape); }); verify(mockService, never()).sendData(invalidShape); } }
2. Shape兼容性验证测试
用参数化测试遍历所有Shape子类,验证每个类型都能被正确处理:
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.util.stream.Stream; import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) class ControllerShapeCompatibilityTests { @Mock private Service mockService; @InjectMocks private Controller controller; // 提供所有Shape子类的实例 private static Stream<Shape> allShapeInstances() { return Stream.of( new Circle(), new Square(), new Triangle(), // 假设存在的其他子类 new Rectangle(), new Pentagon(), new Hexagon() ); } // 参数化测试,逐个验证每个Shape类型 @ParameterizedTest @MethodSource("allShapeInstances") void doSomething_AnyShapeType_CallsServiceSendData(Shape shape) { controller.doSomething(shape); // 验证每个Shape都能被正确传递给Service verify(mockService).sendData(shape); } }
为什么这种方式无冗余?
- 场景测试只关注Controller的逻辑分支,单个Shape就够覆盖所有路径,不用为每个Shape重复写场景用例。
- 兼容性测试只关注类型适配,验证所有Shape都能被处理,不涉及场景分支,彻底避免了6×N的组合冗余。
- 两个维度独立维护:新增场景只需要在场景测试里加用例,新增Shape子类只需要在
allShapeInstances()里加实例。
内容的提问来源于stack exchange,提问作者Sankalp
相关产品推荐
相关产品推荐

