我该如何嘲笑这个?

时间:2011-08-11 18:03:18

标签: c# .net unit-testing mocking

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

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

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

5 个答案:

答案 0 :(得分:10)

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

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

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

请参阅Strategy Pattern

这会引导你进入一个充满激动人心的Inversion of Control世界。当你深入研究时,你会发现这些问题已经通过AutoFacNinjectStructure Map这样的IoC容器得到了有效解决。

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

答案 1 :(得分:5)

  

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

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

  1. 界面标识角色提供的角色或职责。在这种情况下,界面标识EmployeeManager的角色和职责。通过使用接口,您可以防止意外依赖某些特定于数据库的内容。
  2. 接口减少了耦合。由于您的应用程序不依赖于EmployeeManager,您可以自由地交换其实现,而无需重新编译应用程序的其余部分。当然,这取决于项目结构,组件数量等,但它仍允许这种类型的重用。
  3. 界面提升了可测试性。当您使用界面时,生成动态代理变得更加容易,从而可以更轻松地测试您的软件。
  4. 界面强制思考 1 。好吧,我已经提到了它,但值得再说一遍。仅使用界面应该让您考虑对象的角色和职责。接口不应该是厨房水槽。界面代表一组紧密的角色和职责。如果接口的方法没有内聚性或者几乎不总是一起使用,那么对象可能具有多个角色。虽然不一定是坏的,但它意味着多个不同的接口更好。界面越大越难以使其协变或逆变,因此在代码中更具有可塑性。
  5.   

    然而,它将允许我创建一些EmployeeManager测试类来加载员工而不涉及数据库....我不明白这一点。为什么我可以从IEmployeeManager创建一个简单的测试类来提供我需要的东西?

    正如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的{​​{1}}行为时,我们有兴趣验证他是否正确地向员工经理传达了此透视员工作为员工添加的信息。你会经常看到这样的东西:

    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);
        //...
    }
    

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

    可以使您的存根测试类记录交互,但是您正在模拟模拟框架。如果你要测试行为 - 使用模拟框架。

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

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

答案 2 :(得分:1)

在场景中提取界面是个好主意。我不会太担心你只需要这个来进行测试。提取此接口会使您的代码与数据库分离。之后,您可以选择编写自己的测试实现,也可以使用模拟框架为您生成此实现。这是个人喜好的问题。这取决于你对模拟框架的熟悉程度,以及你是否想花时间学习新的语法。

在我看来,值得学习。它会为你节省很多打字。它们也很灵活,并不总是需要一个接口来生成测试实现。例如,RhinoMocks可以模拟具体类,只要它们具有空构造函数并且方法是虚拟的。另一个优点是模拟API使用一致的命名,因此您将熟悉“模拟”,“存根”等。在您的方案中,您需要stub, not mock。手动编写实际模拟可能比使用框架更耗费人力。

模拟框架的危险在于它们中的一些非常强大并且几乎可以模拟任何东西,包括私有字段(TypeMock)。换句话说,他们太容易设计错误,并允许你编写非常耦合的代码。

关于手写与生成存根的主题,这是一个很好的read

答案 3 :(得分:1)

创建一个IEmployeeManager接口以便能够进行模拟,大多数.NET开发人员都会采用这种方式来使这样的类可测试。

另一种选择是从EmployeeManager继承并覆盖您要测试的方法,这样就不会涉及数据库 - 这也意味着您需要更改设计。

答案 4 :(得分:0)

通过使您的类实现接口,您不仅可以使它们更易于测试,而且还可以使您的应用程序更加灵活和可维护。如果你说“由于界面没有其他用途,这似乎不正确”,那么它是有缺陷的,因为它允许你松散地结合你的课程。

如果我可以推荐几本书Head First Design PatternsHead First Software Development 会更好地解释这些概念,那么我可以回答这个问题。

如果您不想使用像Moq这样的模拟框架,那么滚动您自己的模拟/存根很简单,这里有一篇快速的博客帖子Rolling your own Mock Objects