我该如何嘲笑这个?

4th*_*ace 13 .net c# unit-testing mocking

在.NET Windows应用程序中,我有一个名为EmployeeManager的类.在实例化时,此类将员工加载到尚未完成注册的数据库中的List中.我想在单元测试中使用EmployeeManager.但是,我不想涉及数据库.

根据我对这种情况的理解,我需要一个IEmployeeManager接口,它仅用于测试目的.这似乎不对,因为界面没有其他用途.但是,它允许我创建一些EmployeeManager测试类,在不涉及数据库的情况下加载员工.这样,我可以分配原本来自数据库的值.

以上是否正确,我需要嘲笑吗?模拟(Moq框架)似乎只是使用大量代码来执行简单的操作,例如分配属性.我不明白这一点.为什么我可以从IEmployeeManager创建一个简单的测试类来提供我需要的东西?

Mic*_*ays 10

控制反转是你的解决方案,而不是模拟对象.原因如下:

您模拟界面以确保使用IEmployeeManager的某些代码正确使用它.您没有使用测试代码来证明IEmployeeManager的工作原理.因此,必须有另一个类采用IEmployeeManager,例如,您实际上将使用模拟对象进行测试.

如果您实际上只是在测试EmployeeManager,那么您可以做得更好.考虑依赖注入.通过这种方式,您将公开EmployeeManager的构造函数,该构造函数将至少使用一个参数作为接口.您的EmployeeManager代码将在内部使用此接口进行其需要进行的任何特定于实现的调用.

战略模式

这将引导您进入一个完整的,令人兴奋的控制反转世界.当你深入研究它时,你会发现像这样的问题已经通过AutoCac,NinjectStructure Map等IoC容器得到了有效的解决.

模拟接口非常棒,您可以模拟一个接口然后传递到IoC.但是你会发现IoC是一个更加强大的解决方案.是的,虽然您可能只是为了测试而实施第二种替代方案,但出于这个原因仍然很重要 - 从EmployeeManager的业务逻辑中分离测试中的策略.


Kal*_*son 5

根据我对这种情况的理解,我需要一个IEmployeeManager接口,它仅用于测试目的.这似乎不对,因为界面没有其他用途.

非常值得创建界面.另请注意,界面实际上有多种用途:

  1. 界面标识角色提供的角色或职责. 在这种情况下,界面标识了角色和职责EmployeeManager.通过使用接口,您可以防止意外依赖某些特定于数据库的东西.
  2. 界面减少了耦合.由于您的应用程序不依赖于EmployeeManager,您可以自由地交换其实现,而无需重新编译应用程序的其余部分.当然,这取决于项目结构,组件数量等,但它仍然允许这种类型的重用.
  3. 界面提升了可测试性.当您使用界面时,生成动态代理变得更加容易,从而可以更轻松地测试您的软件.
  4. 界面强迫思想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);
    }
}
Run Code Online (Sandbox Code Playgroud)

当我们测试HiringOfficerHireEmployee行为,我们感兴趣的验证,他正确地传达给员工的经理,这个观点的员工添加为雇员.你会经常看到这样的东西:

// 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);
    //...
}
Run Code Online (Sandbox Code Playgroud)

上述测试合理......但不好.这是一项基于状态的测试.也就是说,它通过检查某个操作之前和之后的状态来验证行为.有时这是测试事物的唯一方法; 有时它是测试某些东西的最佳方式.

但是,测试行为往往更好,这就是模拟框架闪耀的地方:

// 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
}
Run Code Online (Sandbox Code Playgroud)

在上面我们测试了唯一真正重要的事情 - 招聘官员告诉员工经理需要添加新员工(一次,只有一次......虽然我实际上不会费心检查这个案例).不仅如此,我还证实我要求招聘人员雇用的员工是由员工经理添加的.我测试了批评行为.我甚至不需要一个简单的测试存根.我的测试时间更短.实际行为更加明显 - 可以看到交互并验证对象之间的交互.

可以使您的存根测试类记录交互,但随后您将模拟模拟框架.如果您要测试行为 - 使用模拟框架.

正如另一张海报所提到的,依赖注入(DI)和控制反转(IoC)非常重要.我上面的例子不是一个很好的例子,但两者都应该仔细考虑并明智地使用.有很多的主题 可用.

1 - 是的,思考仍然是可选的,但我强烈推荐它;).