从我对这种情况的理解来看,我需要一个IEmployeeManager接口,该接口仅用于测试目的。这似乎不正确,因为该接口没有其他用途。
值得创建该接口。请注意,该接口实际上具有多个目的:
- 接口标识由参与者提供的角色或职责。 在这种情况下,接口标识了
EmployeeManager
的角色和职责。使用接口可以防止意外依赖于特定于数据库的内容。
- 接口减少耦合。 由于应用程序不会依赖于
EmployeeManager
,因此您可以自由地替换其实现,而无需重新编译其余部分的应用程序。当然,这取决于项目结构、程序集数量等,但它仍然允许这种重用。
- 接口促进可测试性。 使用接口后,生成动态代理以更轻松地对软件进行测试变得更加容易。
- 接口强制思考1。 好吧,我已经暗示过了,但值得再说一遍。仅使用接口就应该使您考虑一个对象的角色和职责。接口不应成为一个厨房水槽。接口表示一组凝聚的角色和职责。如果接口方法不是凝聚的或几乎总是一起使用,则可能一个对象具有多个角色。虽然不一定是坏事,但它意味着多个不同的接口更好。接口越大,使其在代码中具有协变性或逆变性就越难,因此越不易塑形。
然而,这将允许我创建一些EmployeeManager测试类,加载员工而不涉及数据库... 我不明白重点在哪里。为什么要模拟,当我可以只创建一个简单的从IEmployeeManager继承的测试类,提供我所需的内容呢?
正如
一位发帖者指出的那样,你似乎在谈论创建存根测试类。模拟框架可用于创建存根,但它们最重要的特点之一是它们允许您测试行为而不是状态。现在让我们看一些例子。假设以下情况:
interface IEmployeeManager {
void AddEmployee(ProspectiveEmployee e);
void RemoveEmployee(Employee e);
}
class HiringOfficer {
private readonly IEmployeeManager manager
public HiringOfficer(IEmployeeManager manager) {
this.manager = manager;
}
public void HireProspect(ProspectiveEmployee e) {
manager.AddEmployee(e);
}
}
当我们测试
HiringOfficer
的
HireEmployee
行为时,我们感兴趣的是验证他是否正确地向员工经理传达了这位潜在雇员应该被添加为员工的信息。通常会看到类似以下的内容:
[Test]
public void HiringOfficerAddsProspectiveEmployeeToDatabase() {
var manager = new TestableEmployeeManager();
var officer = new HiringOfficer(manager);
var prospect = CreateProspect();
Assert.AreEqual(4, manager.EmployeeCount());
officer.HireProspect(prospect);
Assert.AreEqual(5, manager.EmployeeCount());
Assert.AreEqual("John", manager.Employees[4].FirstName);
Assert.AreEqual("Doe", manager.Employees[4].LastName);
}
上面的测试是合理的...但不够好。它是一种基于状态的测试。也就是说,通过检查某些操作之前和之后的状态来验证行为。有时这是测试事物的唯一方法;有时这是测试某些东西的最佳方法。
但是,测试行为通常更好,这就是模拟框架发挥作用的地方:
[Test]
public void HiringOfficerCommunicatesAdditionOfNewEmployee() {
var mockEmployeeManager = new Mock<EmployeeManager>();
var officer = new HiringOfficer(mockEmployeeManager.Object);
var prospect = CreateProspect();
officer.HireProspect(prospect);
mockEmployeeManager.Verify(m => m.AddEmployee(prospect), Times.Once);
}
在上面的测试中,我们测试了唯一真正重要的事情——招聘官向员工经理传达需要添加新员工的信息(仅一次……尽管在这种情况下我实际上不会费心检查计数)。不仅如此,我还验证了我要求招聘官雇用的
员工已由员工经理添加。我测试了关键行为。我甚至不需要一个简单的测试存根。我的测试更短。实际行为更加明显——可以看到对象之间的交互并验证交互。
虽然可能让你的存根测试类记录交互,但这样你就是在模拟框架。如果你要测试行为——使用模拟框架。
作为另一位发帖者提到的,依赖注入(DI)和控制反转(IoC)非常重要。我上面的例子并不是这方面的好例子,但这两个概念应该仔细考虑并谨慎使用。关于这个主题有很多
文章可以参考
阅读。
1 - 是的,思考仍然是可选的,但我强烈建议这样做 ;)。