简体   繁体   English

难以理解如何在单元测试中使用Mock

[英]Having trouble understanding how to use Mock in a Unit-Test

I have defined the following Unit-Test: 我定义了以下单元测试:

[TestMethod] //@Test for the Java crowd
public void In_The_Beginning_All_The_Board_Is_Black()
{
    IBoard board = new Board(new Size(10, 22));
    BoardEngine boardEngine = new BoardEngine(board);

    for (int y = 0; y < boardEngine.Size.Width; ++y)
    {
        for (int x = 0; x < boardEngine.Size.Width; ++x)
        {
            Assert.AreEqual<Color>(Color.Black, boardEngine.GetCellAt(new Point(x, y)).Color);
        }
    }
}

Now, the problem is that currently everything is simple and I can instantiate Board as it's shown, passing it its size in the constructor. 现在的问题是,目前一切都很简单,我可以实例化Board ,如图所示,将其大小传递给构造函数。 I know later I'll have a way more complex Board , that has lots of dependencies, so I'd like to try to write out this test with a mock instead. 我知道以后我会使用一种更复杂的Board ,它具有很多依赖关系,所以我想尝试用一个模拟写这个测试。 The issue is that I don't get how. 问题是我不知道怎么做。 What should I setup with this mock? 我应该用这个模拟设置什么?

The behaviour I want to test is that when a BoardEngine is instantiated, all the cells of the board must have its color set to black. 我要测试的行为是,当实例化BoardEngine时,开发板的所有单元格必须将其颜色设置为黑色。 How can I represent that in a test with a Mock instead of the Board class? 我该如何用模拟而非板子类的测试来表示这一点?

Internally, I currently just have an IBoard field in BoardEngine : 在内部,我目前在BoardEngine只有一个IBoard字段:

public class BoardEngine {
    IBoard board;

    ...

    public BoardEngine(IBoard board) {
        this.board = board;

        SetAllCellsToBlack();
    }


    private void SetAllCellsToBlack() {
        board.SetAllCellsTo(Color.Black);
    }

    ...
}

and IBoard is defined as follows: IBoard的定义如下:

public interface IBoard
{
    Size Size { get; }
    BoardCell GetCellAt(Point location);
    void SetCellAt(Point location, BoardCell cell);
    void SetAllCellsTo(Color color);
}

Am I taking the right approach here? 我在这里采用正确的方法吗? Maybe the problem is that as BoardEngine is basically delegating all its work to IBoard , I should be testing an IBoard implementation and not BoardEngine ? 也许问题在于,随着BoardEngine基本上将所有工作委托给IBoard ,我应该测试IBoard实现,而不是BoardEngine


Edit 编辑

So, if I'm understanding correctly what you guys are trying to tell me, I have 2 options: 因此,如果我正确理解了你们要告诉我的内容,那么我有两种选择:

  1. I can make (what I think would be, correct me if I'm wrong) Unit-Tests of both the BoardEngine and the Board. 我可以对BoardEngine和Board进行单元测试(如果我错了,我想会纠正我)。 The BoardEngine's Unit-Test will just check whenever the call is being correctly implemented, using a Mock object of the Board. BoardEngine的单元测试将仅使用Board的Mock对象检查何时正确实现了调用。 The Board's Unit-Test doesn't actually need a mock and just checks that when I run SetAllCellsTo(Color color) it will really turn them that color. 董事会的单元测试实际上并不需要模拟,只是检查当我运行SetAllCellsTo(Color color)时是否真的可以将其变为该颜色。 With this, I am testing the behaviour. 以此,我正在测试行为。
  2. What I actually have, a test that tests the state after instantiating a boardEngine with a Board instance, checking all the board cells to see if they are black. 我真正拥有的是一个测试,该测试在使用Board实例实例化boardEngine之后测试状态,检查所有Board单元是否为黑色。 Here, I am testing the state. 在这里,我正在测试状态。

Is this correct? 这个对吗?

Also, when using TDD, which tests should I do first? 另外,在使用TDD时,我应该首先进行哪些测试? I'd say tests of type 1. When should I use tests of type 2? 我会说类型1的测试。什么时候应该使用类型2的测试?

Thanks 谢谢

The other thing to remember about this kind of TDD is that the functionality should be driven by an end-to-end test to make sure that the pieces all fit together. 关于这种TDD还要记住的另一件事是,应通过端到端测试来驱动该功能,以确保各个部分都装配在一起。 So, the unit test exercises the BoardEngine's relationship with its Board, driven by a higher-level test that drives the whole play. 因此,单元测试通过驱动整个过程的更高级别的测试来行使BoardEngine与董事会之间的关系。

This is going to vary depending on the mocking framework that you're using. 这将根据您使用的模拟框架而有所不同。 With Rhino.Mocks, it would look like this: 使用Rhino.Mocks,它看起来像这样:

var board = MockRepository.GenerateStub<IBoard>();
board.Size = new Size(2,2);

BoardEngine boardEngine = new BoardEngine(board);

board.AssertWasCalled(b => b.SetAllCellsTo(Color.Black),
     options => options.Repeat.Times(1));

Here you create the mock or stub, do what you're testing, and then assert that the expected thing happened. 在这里,您可以创建模拟或存根,执行要测试的内容,然后断言预期的事情已经发生。 In this case, you're expecting SetAllCellsTo(Color.Black) to be called once. 在这种情况下,您希望SetAllCellsTo(Color.Black)被调用一次。

You're not concerned with whether all the cells are now black, because that is behaviour outside of the unit -- outside, even, of the class under test, which in this case is BoardEngine . 您不必担心所有单元格现在是否都是黑色的,因为那是单元外部的行为-甚至在被测类的外部,在本例中为BoardEngine All you're testing is that BoardEngine calls the specified method on the injected dependency when it is instantiated. 您要测试的是, BoardEngine在实例化注入的依赖项时调用指定的方法。

edit in response to OP edit 编辑以响应OP编辑

The two options you outline in your edit are correct, but not mutually exclusive. 您在编辑中概述的两个选项是正确的,但不是互斥的。 I recommend that you have unit tests in place to be sure that each piece of functionality works as expected across various entry conditions, and tests that make sure the interaction works at a higher level. 我建议您进行单元测试,以确保每个功能在各种输入条件下都能按预期工作, 确保交互在更高级别上进行测试。

Keep in mind that you can cover the bases by (1) testing that a call to board.SetAllCellsTo(Color.Black) actually works, independent of BoardEngine , and then (2) testing that BoardEngine calls that [tested to be working] method. 请记住,您可以通过(1)测试对board.SetAllCellsTo(Color.Black)的调用是否有效,独立于BoardEngine ,然后(2)测试BoardEngine调用该[testing to work]方法来BoardEngine基础。 。 Still, you should have higher-level tests to ensure that everything actually plays together as expected without side effects. 不过,您应该进行更高级别的测试,以确保所有功能都能按预期正常运行而不会产生副作用。

What test to start with is a bit subjective. 开始的测试有点主观。 In terms of driving out functionality with TDD, you need to have a good idea of how you want the system to work -- how BoardEngine and Board will work together. 在淘汰TDD的功能方面,您需要对系统的工作方式BoardEngine了解BoardEngineBoard如何一起工作。 You could define both interfaces and outline a test, but you can't actually execute the test until you've implemented both interfaces, and if you write the test you won't be able to compile it because you'll have no classes to instantiate. 您可以定义两个接口并概述测试,但是只有在实现两个接口后才能真正执行测试,并且如果编写测试,则将无法编译该测试,因为您将没有任何类可以实例化。 On the other hand, you can write unit tests as you implement each interface. 另一方面,您可以在实现每个接口时编写单元测试。 Start with IBoard because it has fewer dependencies. IBoard开始,因为它具有较少的依赖性。 Start implementing Board : IBoard and cover it as you go. 开始实施Board : IBoard并随身携带。 When you know that a call to SetAllColorsTo() works as expected, you'll be more comfortable with a unit test of the BoardEngine constructor calling that method. 当您知道对SetAllColorsTo()的调用可以按预期工作时,您将对调用该方法的BoardEngine构造函数的单元测试更加满意。

i come from a java background and have zero .Net experience: 我来自Java背景并且有.NET零经验:

with typical mock libraries, you need to do the following things: 对于典型的模拟库,您需要执行以下操作:

  1. create mock objects 创建模拟对象
  2. program the mock objects 编写模拟对象
  3. set the mock object to replay mode 将模拟对象设置为重播模式
  4. verify the mock object was used appropriately 验证模拟对象是否正确使用

i typically use the EasyMock library, and the above steps my look as follows in Java: 我通常使用EasyMock库,上述步骤在Java中的外观如下:

public class BoardEngineTest {
    @Test
    public void engineShouldSetCellsBlack() {

        // 1. create mock
        IBoard mockBoard = EasyMock.createMock(IBoard.class);

        // 2. program mock
        EasyMock.reset(mockBoard); // put in record mode

        // this doesn't actually happen now, the mock is just
        // being programmed to expect this method call with this
        // argument
        mockBoard.setAllCellsTo(Color.Black);

        // 3. put in replay mode - it's alive at this point!
        EasyMock.replay(mockBoard);

        // do something that cause the mock to be used
        BoardEngine engine = new BoardEngine(mockBoard);

        // 4. make sure cells were actually set to black!
        EasyMock.verify(mockBoard);
    }
}

The behavior of the board engine is different from the behavior of the board. 棋盘引擎的行为与棋盘的行为不同。 Your board engine seems to be a facade , hiding away some complexity of the board. 您的董事会引擎似乎是一个立面 ,隐藏了董事会的某些复杂性。 You're testing the board engine, so you should not test the behavior of the board, but that the engine calls the correct behavior of the board. 您正在测试电路板引擎,因此您不应测试电路板的行为,而应由引擎调用电路板的正确行为。 In that case you inject a mock of the board and verify whether it has been called with how the board engine should call it. 在这种情况下,您需要注入一个模拟板,并验证其是否已被调用,以及板机引擎应该如何调用它。

You can approach this by verifying state or behaviour. 您可以通过验证状态或行为来解决此问题。
If you verify state you want to test that after BoardEngine is instatntiated all cells of it's board are black. 如果您验证状态,则要测试在将BoardEngine设置为无效后,其板上的所有单元都是黑色的。 In this case you would show that you do not care how it's achieved and you just want to test it's done. 在这种情况下,您将表明您并不关心它是如何实现的,而只是想测试它是否完成。
Another option is to test behaviour by supplying a BoardEngine with a mock implementation of IBoard and after BoardEngine is instantiated you verify that the SetAllCellsTo method of IBoard was called with the appropriate color. 另一个选择是通过为BoardEngine提供IBoard的模拟实现来测试行为,并在实例化BoardEngine之后,验证是否使用适当的颜色调用了IBoard的SetAllCellsTo方法。
I'd prefer the first approach - testing state - if possible but sometimes you don't have a choice. 如果可能的话,我更喜欢第一种方法-测试状态-但有时候您别无选择。

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

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