如何编写单元测试友好的ASP.NET Core 6.0代码及相关实践疑问
.NET开发中便于单元测试的最佳实践
针对你提到的“为未规划单元测试的遗留项目补测难度大”的痛点,以下是针对你三个问题的具体解答:
一、开发Web API/Web应用时的核心要点
- 控制器做“薄”层:控制器只负责接收请求、参数校验、调用业务服务、返回响应,不要在控制器里写复杂业务逻辑,否则测试时需要模拟HTTP上下文,成本极高。
- 抽象外部依赖:所有外部依赖(数据库、Redis、第三方API、文件系统等)都用接口封装,比如
IUserRepository、IEmailService,避免直接在业务层实例化具体实现类。 - 杜绝静态硬编码:不要用静态类/静态方法处理业务逻辑或依赖(比如
StaticDbHelper.GetUser()),静态成员无法被Mock,会让单元测试耦合到具体实现。 - 配置通过依赖注入获取:不要直接在代码里读取
appsettings.json(比如Configuration["Key"]),而是将配置封装成强类型对象,通过DI注入到需要的类中,测试时可以轻松传入模拟配置。 - DTO与实体分离:数据库实体(Entity)只在数据访问层使用,业务层和表现层用DTO传输数据,避免业务逻辑依赖数据库结构,同时也降低测试时的复杂度。
- 统一异常处理:业务层抛出具体的业务异常(比如
UserNotFoundException),由全局异常过滤器处理HTTP状态码返回,不要在业务层直接返回BadRequest这类HTTP相关结果,让业务逻辑与Web框架解耦。
二、最适配单元测试的架构
- 分层架构(经典三层):分为表现层(API控制器/视图)、业务逻辑层(Service)、数据访问层(Repository),每层依赖下一层的抽象而非具体实现。测试业务层时,只需Mock数据访问层的接口,无需启动数据库。
- 整洁架构(Clean Architecture):核心是业务实体和用例层,外层(表现层、数据访问层)依赖内层,所有外部框架(ASP.NET Core、EF Core)都作为外围依赖,测试核心业务逻辑时完全不需要依赖框架,测试速度快且稳定。
- 洋葱架构(Onion Architecture):和整洁架构思路一致,强调依赖方向向内,业务逻辑不依赖任何外部系统,所有外部依赖都通过接口注入,非常适合隔离测试。
- CQRS(命令查询职责分离):将读写操作分离,每个命令(Command)和查询(Query)都是单一职责的类,测试时只需针对单个命令/查询验证逻辑,无需考虑其他操作的影响,测试粒度更细。
三、需遵循的关键原则
- SOLID原则
- 单一职责原则(SRP):每个类只负责一个功能模块,比如
UserService只处理用户相关业务,OrderService只处理订单,这样测试时每个测试用例只关注一个功能点。 - 依赖倒置原则(DIP):高层模块不依赖低层模块的具体实现,而是依赖抽象。比如业务层依赖
IUserRepository,而非EfCoreUserRepository,测试时可以用Mock实现替代真实数据库操作。 - 接口隔离原则(ISP):不要设计包含大量方法的“胖接口”,比如把用户的增删改查和权限验证都放到
IUserService里,拆分出IUserQueryService、IUserCommandService,客户端只依赖自己需要的接口,Mock时也无需实现无关方法。 - 里氏替换原则(LSP):子类可以无缝替换父类,比如
PremiumUserService继承UserService后,测试UserService的用例也能适用于PremiumUserService,无需重复编写测试。
- 单一职责原则(SRP):每个类只负责一个功能模块,比如
- 关注点分离(Separation of Concerns):和分层架构呼应,让不同模块负责不同职责,避免代码耦合,降低测试时的依赖复杂度。
- 依赖注入优先:所有依赖都通过DI容器注入,不要手动
new依赖对象,比如在UserService里不要new EfCoreUserRepository(),而是通过构造函数注入IUserRepository,这样测试时可以传入Mock对象。 - 避免“上帝类”:不要出现一个类包含上百个方法、处理多种业务逻辑的情况,拆分后的小类更易测试,也更容易定位问题。
内容的提问来源于stack exchange,提问作者Isanka Thalagala
相关产品推荐
相关产品推荐

