在.NET Windows应用程序中,我有一个名为EmployeeManager的类。在实例化时,该类将雇员从尚未完成注册的数据库加载到列表中。我想在单元测试中使用employeemanager。但是,我不想涉及数据库。
根据我对这个场景的理解,我需要一个iemployeemanager接口,它只用于测试目的。这似乎不对,因为接口没有其他用途。但是,它将允许我创建一些employeemanager测试类,该类加载雇员而不涉及数据库。这样,我就可以指定其他来自数据库的值。
上面说的对吗?我需要嘲笑它吗?mocking(moq framework)似乎使用了很多代码来完成一些简单的事情,比如分配一个属性。我不明白重点。当我能从iBeaMeMaNeGER创建一个简单的测试类时,为什么要嘲笑我,这将提供我所需要的?
最佳答案
根据我对这个场景的理解,我需要一个iemployeemanager接口,它只用于测试目的。这似乎不对,因为接口没有其他用途。
很值得创建这个界面。还要注意,接口实际上有多种用途:
接口标识参与者提供的角色或职责。在这种情况下,接口标识EmployeeManager
的角色和职责。通过使用一个接口,您可以防止对特定于数据库的内容的意外依赖。
界面减少了耦合。因为您的应用程序不依赖于EmployeeManager
,所以您可以自由地交换它的实现,而无需重新编译应用程序的其余部分。当然,这取决于项目结构、程序集数量等,但它仍然允许这种类型的重用。
接口提高了可测试性。当您使用一个接口时,生成允许您的软件更容易测试的动态代理变得容易得多。
界面的作用力。好吧,我已经暗示过了,但值得再说一次。仅仅使用一个接口就应该让您考虑对象的角色和职责。接口不应该是厨房的水槽。接口表示角色和职责的内聚集。如果一个接口的方法没有内聚性,或者几乎不总是一起使用,那么一个对象很可能有多个角色。虽然不一定是坏的,但它意味着多个不同的接口更好。接口越大,就越难使其协变或反变,因此在代码中更具可塑性。
但是,它将允许我创建一些employeemanager测试类,该类在不涉及数据库的情况下加载employees….我不明白重点。当我能从iBeaMeMaNeGER创建一个简单的测试类时,为什么要嘲笑我,这将提供我所需要的?
正如one poster所指出的,听起来像是在创建存根测试类。模拟框架可以用来创建存根,但是它们的一个最重要的特征是允许测试行为而不是状态。现在让我们看一些例子。假设如下:
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
行为时,我们感兴趣的是验证他是否正确地与员工经理沟通,是否将此透视图员工添加为员工。你经常会看到这样的东西:// you have an interface IEmployeeManager and a stub class
// called TestableEmployeeManager that implements IEmployeeManager
// that is pre-populated with test data
[Test]
public void HiringOfficerAddsProspectiveEmployeeToDatabase() {
var manager = new TestableEmployeeManager(); // Arrange
var officer = new HiringOfficer(manager); // BTW: poor example of real-world DI
var prospect = CreateProspect();
Assert.AreEqual(4, manager.EmployeeCount());
officer.HireProspect(prospect); // Act
Assert.AreEqual(5, manager.EmployeeCount()); // Assert
Assert.AreEqual("John", manager.Employees[4].FirstName);
Assert.AreEqual("Doe", manager.Employees[4].LastName);
//...
}
以上测试是合理的…但不好。这是一个基于州的测试。也就是说,它通过检查某个操作前后的状态来验证行为。有时这是测试事物的唯一方法;有时这是测试事物的最好方法。
但是,测试行为通常更好,这就是模拟框架的亮点:
// using Moq for mocking
[Test]
public void HiringOfficerCommunicatesAdditionOfNewEmployee() {
var mockEmployeeManager = new Mock<EmployeeManager>(); // Arrange
var officer = new HiringOfficer(mockEmployeeManager.Object);
var prospect = CreateProspect();
officer.HireProspect(prospect); // Act
mockEmployeeManager.Verify(m => m.AddEmployee(prospect), Times.Once); // Assert
}
在上面我们测试了唯一真正重要的东西——招聘官告诉员工经理需要增加一个新员工(一次,而且只有一次……不过,在这种情况下,我其实不会费心检查计数。不仅如此,我还证实了我要求招聘经理雇佣的雇员是由雇员经理加入的。我已经测试了批判行为。我甚至不需要一个简单的测试存根。我的测验较短。实际行为更加明显——可以看到交互和验证对象之间的交互。
可以让存根测试类记录交互,但随后您将模拟模拟框架。如果要测试行为,请使用模拟框架。
正如另一张海报所提到的,依赖注入(di)和控制反转(ioc)非常重要。我上面的例子并不是一个很好的例子,但是两者都应该被仔细考虑和明智地使用。在writingsubject上有很多available。
1-是的,思考仍然是可选的,但我强烈推荐;)。
关于c# - 我该如何 mock ?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/7030667/