开发者

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. 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. 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. 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:

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:

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?


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. The BoardEngine's Unit-Test will just check whenever the call is being correctly implemented, using a Mock object of the Board. 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. 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. Here, I am testing the state.

Is this correct?

Also, when us开发者_Go百科ing TDD, which tests should I do first? I'd say tests of type 1. When should I use tests of type 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. So, the unit test exercises the BoardEngine's relationship with its Board, driven by a higher-level test that drives the whole play.


This is going to vary depending on the mocking framework that you're using. With Rhino.Mocks, it would look like this:

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.

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. All you're testing is that BoardEngine calls the specified method on the injected dependency when it is instantiated.

edit in response to OP edit

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. 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. 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. Start implementing Board : IBoard and cover it as you go. 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.


i come from a java background and have zero .Net experience:

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:

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. 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.
I'd prefer the first approach - testing state - if possible but sometimes you don't have a choice.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜