简体   繁体   English

我如何嘲笑这个?

[英]How do I mock this?

In a .NET windows app, I have a class named EmployeeManager.在 .NET windows 应用程序中,我有一个名为 EmployeeManager 的 class。 On instantiation, this class loads employees into a List from the database that haven't completed registration.在实例化时,此 class 将尚未完成注册的员工从数据库加载到列表中。 I'd like to use EmployeeManager in unit test.我想在单元测试中使用 EmployeeManager。 However, I don't want to involve the database.但是,我不想涉及数据库。

From what I understand about this scenario, I need an IEmployeeManager interface, which is only used for testing purposes.根据我对这种情况的理解,我需要一个 IEmployeeManager 接口,该接口仅用于测试目的。 This doesn't seem right since the interface has no other use.这似乎不对,因为接口没有其他用途。 However, it will allow me to create some EmployeeManager test class that loads employees without involving the database.但是,它将允许我创建一些 EmployeeManager 测试 class 来加载员工而不涉及数据库。 This way, I can assign values that would have otherwise come from the database.这样,我可以分配原本来自数据库的值。

Is the above correct and do I need to Mock it?以上是否正确,我需要模拟它吗? Mocking (Moq framework) seems to use lots of code just to do simple things such as assigning a property. Mocking(Moq 框架)似乎使用大量代码来做一些简单的事情,例如分配属性。 I don't get the point.我不明白这一点。 Why mock when I can just create a simple test class from IEmployeeManager that will provide what I need?当我可以从 IEmployeeManager 创建一个简单的测试 class 来提供我需要的东西时,为什么还要模拟呢?

Inversion of control is your solution, not Mock objects.控制反转是您的解决方案,而不是 Mock 对象。 Here's why:原因如下:

You mock the interface to make sure that some code that utilizes your IEmployeeManager is using it properly.您模拟界面以确保某些使用您的 IEmployeeManager 的代码正确使用它。 You aren't using the test code to prove IEmployeeManager works.您没有使用测试代码来证明 IEmployeeManager 有效。 So there has to be another class that takes an IEmployeeManager, for instance, which you will actually be testing with your mock object.因此,必须有另一个 class,例如,您将使用模拟 object 对其进行测试。

If you are actually just testing EmployeeManager, you can do much better.如果您实际上只是在测试 EmployeeManager,则可以做得更好。 Consider dependency injection.考虑依赖注入。 In this manner, you will expose a constructor for EmployeeManager that will take at least one parameter which is an interface.通过这种方式,您将为 EmployeeManager 公开一个构造函数,该构造函数将至少采用一个参数,即接口。 Your EmployeeManager code will internally use this interface for any implementation specific calls that it needs to make.您的 EmployeeManager 代码将在内部将此接口用于它需要进行的任何特定于实现的调用。

See Strategy Pattern策略模式

This will lead you into a whole, exciting world of Inversion of Control .这将带您进入一个完整的、令人兴奋的控制反转世界。 And as you dig into that, you will find that problems like these have been effectively solved with IoC containers such as AutoFac , Ninject , and Structure Map , to name a few.当您深入研究时,您会发现此类问题已通过 IoC 容器得到有效解决,例如AutoFacNinjectStructure Map等等。

Mocking interfaces is great, and you can mock an interface that you then pass into IoC. Mocking 接口很棒,您可以模拟一个接口,然后将其传递给 IoC。 But you'll find that IoC is a much more robust solution to your problem.但是您会发现 IoC 是解决您问题的更强大的解决方案。 And yes, while you might only be implementing a second alternative just for testing, it is still important to do for that very reason -- seperating the strategy under test from the business logic of EmployeeManager.是的,虽然您可能只是为了测试而实施第二个替代方案,但出于这个原因,这样做仍然很重要——将被测策略与 EmployeeManager 的业务逻辑分开。

From what I understand about this scenario, I need an IEmployeeManager interface, which is only used for testing purposes.根据我对这种情况的理解,我需要一个 IEmployeeManager 接口,该接口仅用于测试目的。 This doesn't seem right since the interface has no other use.这似乎不对,因为接口没有其他用途。

It's well worth creating the interface.创建界面非常值得。 Note also that the interface actually has multiple purposes:另请注意,该接口实际上有多种用途:

  1. The interface identifies roles or responsibilities provided by an actor.接口识别参与者提供的角色或职责。 In this case, the interface identifies the roles and responsibilities of the EmployeeManager .在这种情况下,接口标识了EmployeeManager的角色和职责。 By using an interface you're preventing an accidental dependency on something database specific.通过使用接口,您可以防止对特定数据库的意外依赖。
  2. The interface reduces coupling.该接口减少了耦合。 Since your application won't depend on the EmployeeManager , you're free to swap out its implementation without needing to recompile the rest of the application.由于您的应用程序不依赖于EmployeeManager ,因此您可以随意更换其实现,而无需重新编译应用程序的 rest。 Of course, this depends on project structure, number of assemblies, etc., but it nevertheless allows this type of reuse.当然,这取决于项目结构、程序集的数量等,但它仍然允许这种类型的重用。
  3. The interface promotes testability.该接口促进了可测试性。 When you use an interface it becomes much easier to generate dynamic proxies that allow your software to be more easily tested.当您使用界面时,生成动态代理会变得更加容易,从而可以更轻松地测试您的软件。
  4. The interface forces thought 1 .接口力思想1 Ok, I kind of already alluded to it, but it's worth saying again.好吧,我已经提到过,但值得再说一遍。 Just using an interface alone should make you think about an object's roles and responsibilities.仅使用接口就应该让您考虑对象的角色和职责。 An interface shouldn't be a kitchen sink.接口不应该是厨房水槽。 An interface represents a cohesive set of roles and responsibilities.接口代表了一组有凝聚力的角色和职责。 If an interface's methods aren't cohesive or aren't almost always used together then it's likely that an object has multiple roles.如果接口的方法不具有凝聚力或几乎不总是一起使用,那么 object 很可能具有多个角色。 Though not necessarily bad, it implies that multiple distinct interfaces are better.虽然不一定很糟糕,但这意味着多个不同的接口更好。 The larger an interface the harder it is to make it covariant or contravariant and, therefore, more malleable in code.接口越大,就越难使其协变或逆变,因此在代码中更具可塑性。

However, it will allow me to create some EmployeeManager test class that loads employees without involving the database.... I don't get the point.但是,它将允许我创建一些 EmployeeManager 测试 class 加载员工而不涉及数据库......我不明白这一点。 Why mock when I can just create a simple test class from IEmployeeManager that will provide what I need?当我可以从 IEmployeeManager 创建一个简单的测试 class 来提供我需要的东西时,为什么还要模拟呢?

As one poster pointed out, it sounds like you're talking about creating a stub test class.正如一位海报指出的那样,听起来您正在谈论创建一个存根测试 class。 Mocking frameworks can be used to create stubs, but one of the most important features about them is that they allow you to test behavior instead of state. Mocking 框架可用于创建存根,但它们最重要的特性之一是它们允许您测试行为而不是 state。 Now let's look at some examples.现在让我们看一些例子。 Assume the following:假设如下:

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

When we test the HiringOfficer 's HireEmployee behavior, we're interested in validating that he correctly communicated to the employee manager that this perspective employee be added as an employee.当我们测试HiringOfficerHireEmployee行为时,我们有兴趣验证他是否正确地向员工经理传达了该透视员工被添加为员工的信息。 You'll often see something like this:你会经常看到这样的东西:

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

The above test is reasonable... but not good.上面的测试是合理的……但不是很好。 It's a state-based test.这是一个基于状态的测试。 That is, it verifies the behavior by checking the state before and after some action.也就是说,它通过在某些操作之前和之后检查 state 来验证行为。 Sometimes this is the only way to test things;有时这是测试事物的唯一方法; sometimes it's the best way to test something.有时这是测试某事的最佳方式。

But , testing behavior is often better, and this is where mocking frameworks shine:但是,测试行为通常更好,这就是 mocking 框架的亮点:

// 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
}

In the above we tested the only thing that really mattered -- that the hiring officer communicated to the employee manager that a new employee needed to be added (once, and only once... though I actually wouldn't bother checking the count in this case).在上面我们测试了唯一真正重要的事情——招聘官告诉员工经理需要添加一个新员工(一次,而且只有一次......虽然我实际上不会费心检查计数这个案例)。 Not only that, I validated that the employee that I asked the hiring officer to hire was added by the employee manager.不仅如此,我还验证了我要求招聘人员雇用的员工是由员工经理添加的。 I've tested the critical behavior.我已经测试了关键行为。 I didn't need even a simple test stub.我什至不需要一个简单的测试存根。 My test was shorter.我的测试更短。 The actual behavior was much more evident -- it becomes possible to see the interaction and validate interaction between objects.实际行为更加明显——可以看到交互并验证对象之间的交互。

It is possible to make your stub test class record interactions, but then you're emulating the mocking frameworks.可以让您的存根测试 class 记录交互,但是您正在模拟 mocking 框架。 If you're going to test behavior -- use a mocking framework.如果你要测试行为——使用 mocking 框架。

As another poster mentioned, dependency injection (DI) and inversion of control (IoC) are important.正如另一张海报所提到的,依赖注入 (DI) 和控制反转 (IoC) 很重要。 My example above isn't a good example of this, but both should be carefully considered and judiciously used.我上面的例子不是一个很好的例子,但两者都应该仔细考虑并明智地使用。 There's a lot of writing on the subject available .有很多关于这个主题文章

1 - Yes, thinking is still optional, but I'd strongly recommend it;). 1 - 是的,思考仍然是可选的,但我强烈推荐它;)。

Extracting interface in your scenario is a good idea.在您的场景中提取接口是一个好主意。 I would not worry too much about the fact that you only need this for testing.我不会太担心您只需要它来进行测试这一事实。 Extracting this interface makes your code decoupled from database.提取此接口使您的代码与数据库分离。 After that you will have a choice between writing your own implementation for testing or use mocking framework to generate this implementation for you.之后,您可以选择编写自己的测试实现或使用 mocking 框架为您生成此实现。 This is a matter of personal preference.这是个人喜好问题。 It depends on how familiar you are with mocking framework and whether you want to spend time learning new syntax.这取决于您对 mocking 框架的熟悉程度以及您是否想花时间学习新语法。

In my opinion it is worth learning.在我看来,值得学习。 It will save you a lot of typing.它将为您节省大量打字。 They are also flexible and don't always require an interface to generate test implementation.它们也很灵活,并不总是需要一个接口来生成测试实现。 RhinoMocks for example can mock concrete classes as long they have empty constructor and methods are virtual.例如,RhinoMocks 可以模拟具体的类,只要它们有空的构造函数并且方法是虚拟的。 Another advantage is that mocking APIs use consistent naming so you will get familiar with 'Mocks', 'Stubs' etc. In your scenario by the way you need stub, not mock .另一个优点是 mocking API 使用一致的命名,因此您将熟悉“Mocks”、“Stubs”等。在您的场景中,您需要stub,而不是 mock Writing an actual mock manually may be more labor intensive than using framework.手动编写一个实际的模拟可能比使用框架更费力。

The danger with mocking frameworks is that some of them are so powerful and can mock pretty much anything, including private fields ( TypeMock ). mocking 框架的危险在于它们中的一些非常强大并且可以模拟几乎任何东西,包括私有字段( TypeMock )。 In other words they are too forgiving to design mistakes and allow you to write very coupled code.换句话说,它们对设计错误过于宽容,并允许您编写非常耦合的代码。

This is a good read on the subject of hand written vs. generated stubs这是关于手写与生成存根主题的好读物

Creating an IEmployeeManager interface in order to be able to mock is that way most .NET developers would go about making such a class testable.创建一个IEmployeeManager接口以便能够模拟是大多数 .NET 开发人员会 go 关于使这样的 class 可测试的方式。

Another option is to inherit from EmployeeManager and override the method you want to test so it will not involve the database - this too means you will need to change your design.另一种选择是从EmployeeManager继承并覆盖您要测试的方法,这样它就不会涉及数据库 - 这也意味着您将需要更改您的设计。

By making your classes implement Interfaces you are not only making them more testable, you're making your application more flexible and maintainable.通过使您的类实现接口,您不仅使它们更可测试,而且使您的应用程序更加灵活和可维护。 When you say "This doesn't seem right since the interface has no other use", is flawed since it allows you to loosely couple your classes.当您说“这似乎不对,因为接口没有其他用途”时,这是有缺陷的,因为它允许您松散地耦合您的类。

If I could suggest a couple of books Head First Design Patterns and Head First Software Development will do a much better job of explaining the concepts then I could in a SO answer.如果我可以推荐几本书Head First Design PatternsHead First Software Development会更好地解释这些概念,那么我可以在 SO 答案中。

If you don't want to use a mocking framework like Moq, it's simple enough to roll your own mock/stubs, here is a quick blog post on it Rolling your own Mock Objects如果你不想使用像 Moq 这样的 mocking 框架,滚动你自己的模拟/存根很简单,这里有一篇关于它的快速博客文章滚动你自己的模拟对象

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM