// 测试中 [Fact] publicvoidProcessOrder_Should_Work() { var dummyLogger = new DummyLogger(); // 占位对象,不会被使用 var order = new Order { Id = 1 }; var service = new OrderService(); service.ProcessOrder(order, dummyLogger); // dummyLogger 不会被调用,只是占位 }
// 测试中使用 Fake [Fact] publicasync Task GetUser_Should_Return_User() { var fakeRepo = new InMemoryUserRepository(); await fakeRepo.SaveAsync(new User { Id = 1, Name = "Test" }); var user = await fakeRepo.GetByIdAsync(1); Assert.NotNull(user); Assert.Equal("Test", user.Name); }
// 测试中使用 Spy [Fact] publicvoidProcessOrder_Should_Log_Message() { var realLogger = new ConsoleLogger(); var spyLogger = new SpyLogger(realLogger); var service = new OrderService(spyLogger); service.ProcessOrder(new Order()); // 验证日志被记录 Assert.Contains("Order processed", spyLogger.LoggedMessages); }
三、对比总结
类型
是否有实现
是否验证交互
主要用途
示例
Dummy
❌ 无
❌ 否
占位,满足参数要求
null, 空对象
Fake
✅ 简化实现
❌ 否
替代重量级依赖
内存数据库
Stub
⚠️ 预设响应
❌ 否
返回预设数据
固定返回值
Mock
⚠️ 预设响应
✅ 是
验证交互行为
Moq, NSubstitute
Spy
✅ 真实实现
✅ 是
记录调用信息
包装真实对象
四、实际应用建议
4.1 何时使用 Stub
使用 Stub 当:
✅ 只需要返回预设数据
✅ 不关心方法如何被调用
✅ 测试重点是业务逻辑,不是交互
示例场景:
1 2 3
// 只需要返回成功响应,不关心调用细节 var stubRepo = new StubUserRepository(); stubRepo.SetupGetById(1, new User { Id = 1, Name = "Test" });
4.2 何时使用 Mock
使用 Mock 当:
✅ 需要验证方法是否被调用
✅ 需要验证调用次数和参数
✅ 测试重点是对象间的协作
示例场景:
1 2 3
// 需要验证 SendEmail 被调用了一次 var mockEmail = new Mock<IEmailService>(); mockEmail.Verify(x => x.SendEmail(It.IsAny<string>()), Times.Once);
4.3 何时使用 Fake
使用 Fake 当:
✅ 需要真实的简化实现
✅ 需要持久化数据(在测试范围内)
✅ 替代重量级依赖
示例场景:
1 2 3
// 使用内存数据库替代真实数据库 var fakeDb = new InMemoryDbContext(); // 可以保存和查询数据,但只在内存中
// Fixture:测试数据准备 publicclassOrderFixture { public Order CreatePendingOrder(string orderId = null) { returnnew Order { Id = orderId ?? Guid.NewGuid().ToString(), Status = "Pending", Amount = 100, CreatedAt = DateTime.UtcNow }; } public Order CreateCompletedOrder() { returnnew Order { Id = Guid.NewGuid().ToString(), Status = "Completed", Amount = 100, CreatedAt = DateTime.UtcNow, CompletedAt = DateTime.UtcNow }; } }
// 使用 Fixture [Fact] publicvoidProcessOrder_Should_Work() { var fixture = new OrderFixture(); var order = fixture.CreatePendingOrder("order-123"); // 使用 fixture 创建测试数据 }
五、常见错误和最佳实践
5.1 常见错误
❌ 错误 1:过度使用 Mock
1 2 3 4 5
// ❌ 不好的做法:Mock 一切 var mockRepo = new Mock<IUserRepository>(); var mockLogger = new Mock<ILogger>(); var mockCache = new Mock<ICache>(); // 过度 Mock,测试变得复杂且难以理解
✅ 正确做法:合理选择
1 2 3 4
// ✅ 好的做法:根据需求选择 var fakeRepo = new InMemoryUserRepository(); // Fake:需要真实行为 var stubLogger = new StubLogger(); // Stub:只需要占位 var mockEmail = new Mock<IEmailService>(); // Mock:需要验证调用
var mock = new Mock<IService>(); mock.Setup(x => x.GetData()).Returns("test"); mock.Verify(x => x.GetData(), Times.Once);
6.2 NSubstitute(Mock 框架)
1 2 3
var substitute = Substitute.For<IService>(); substitute.GetData().Returns("test"); substitute.Received().GetData();
6.3 Bogus(测试数据生成)
1 2 3 4
var faker = new Faker<User>() .RuleFor(u => u.Name, f => f.Name.FullName()) .RuleFor(u => u.Email, f => f.Internet.Email()); var user = faker.Generate(); // Fixture 数据生成