方法内通过Factory方法创建类时的Mock测试最佳实践咨询
核心结论
你担心「仅为测试抽象接口属于过度设计」是误区,这类依赖外部创建对象的场景,依赖注入+工厂接口是行业通用的最佳实践,完全不算过度设计,同时你也可以根据项目规模选择更轻量的改造方案。
可选方案及适用场景
1. 工厂接口注入(长期项目最推荐)
静态工厂的核心问题是硬编码了Car类和工厂的耦合关系,抽象接口的改动成本极低,同时还能提升生产代码的可维护性:
首先定义工厂接口:
public interface IEngineTypeFactory { EngineType GetEngineType(CarType carType); }
原静态工厂改为接口实现类:
public class EngineTypeFactory : IEngineTypeFactory { public EngineType GetEngineType(CarType carType) { return carType switch { CarType.Petrol => EngineType.Petrol, CarType.Electric => EngineType.Electric, _ => throw new ArgumentOutOfRangeException(nameof(carType)) }; } }
Car类改造为构造函数注入工厂实例:
public class Car { private readonly IEngineTypeFactory _engineFactory; public Car(IEngineTypeFactory engineFactory) { _engineFactory = engineFactory; } public void BuildCar(CarType carType) { var engineType = _engineFactory.GetEngineType(carType); var engineInsertionResult = InsertEngine(engineType); if (!engineInsertionResult.IsRunning) { throw new OhNoEngineFailedException("Engine Failure"); } } private EngineInsertionResult InsertEngine(EngineType engineType) { var ignitionResult = engineType.TurnOn(); return new EngineInsertionResult() { IsRunning = ignitionResult.IsEngineRunning }; } }
测试时直接Mock IEngineTypeFactory的GetEngineType方法,返回自定义的EngineType实例,控制TurnOn()的返回值即可覆盖IsRunning为true/false的所有场景。
优势说明:这个改造不止是为了测试,后续新增混动等车型时,只需要修改工厂实现,不需要动Car类的代码,完全符合开闭原则,额外收获了生产代码的可扩展性。
2. 静态工厂可配置委托(小项目轻量方案)
如果项目规模极小,不想引入依赖注入框架,也可以给静态工厂加测试入口,改造成本几乎为零:
public static class EngineTypeFactory { // 测试时可替换该委托自定义返回值 public static Func<CarType, EngineType> GetEngineTypeImpl { get; set; } = carType => { return carType switch { CarType.Petrol => EngineType.Petrol, CarType.Electric => EngineType.Electric, _ => throw new ArgumentOutOfRangeException(nameof(carType)) }; }; public static EngineType GetEngineType(CarType carType) => GetEngineTypeImpl(carType); }
注意事项:该方案的静态委托是全局生效的,多线程并行测试时会出现冲突,仅适合单线程测试的小型项目。
3. 直接测试公开行为(无外部依赖场景最优)
如果EngineType的TurnOn()方法是纯逻辑实现,没有外部IO、配置读取等依赖,完全不需要修改现有代码,直接构造对应场景的CarType参数,通过EngineType的测试构造参数控制TurnOn()的返回值即可完成测试,改造成本最低。
补充说明
你提到的将EngineType作为BuildCar方法入参的方案也是合理的,适合EngineType由上层业务逻辑生成的场景;如果确实需要在BuildCar内部根据CarType动态生成EngineType,工厂方案是更符合单一职责的选择。
内容的提问来源于stack exchange,提问作者r0bb077

