无法理解如何在单元测试中使用Mock

时间:2010-08-02 04:27:54

标签: c# java unit-testing mocking

我定义了以下单元测试:

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

现在,问题是目前一切都很简单,我可以实例化显示的Board,并在构造函数中传递它的大小。我知道后来我会有更复杂的Board,它有很多依赖,所以我想尝试用mock来编写这个测试。问题是我没有得到如何。我应该用这个模拟器设置什么?

我想测试的行为是,当实例化BoardEngine时,电路板的所有单元格必须将其颜色设置为黑色。如何在Mock而不是Board类的测试中表示这一点?

在内部,我目前在IBoard中只有一个BoardEngine字段:

public class BoardEngine {
    IBoard board;

    ...

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

        SetAllCellsToBlack();
    }


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

    ...
}

IBoard定义如下:

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

我在这里采取了正确的方法吗?也许问题在于BoardEngine基本上将其所有工作委托给IBoard,我应该测试一个IBoard实现而不是BoardEngine


修改

所以,如果我正确理解你们要告诉我的内容,我有两个选择:

  1. 我可以做(我认为如果我错了就纠正我)BoardEngine和Board的单元测试。 BoardEngine的Unit-Test将使用Board的Mock对象检查何时正确实现调用。 Board的Unit-Test实际上并不需要模拟,只是检查当我运行SetAllCellsTo(Color color)时它会真正变成那种颜色。有了这个,我正在测试这种行为。
  2. 我实际拥有的是一个测试,用于在使用Board实例实例化boardEngine之后测试状态,检查所有板单元以查看它们是否为黑色。在这里,我正在测试州。
  3. 这是对的吗?

    另外,使用TDD时,我应该先做哪些测试?我会说类型1的测试。我什么时候应该使用类型2的测试?

    由于

5 个答案:

答案 0 :(得分:3)

关于这种TDD需要记住的另一件事是,功能应该由端到端测试驱动,以确保所有部分都适合。因此,单元测试演练了BoardEngine与其董事会的关系,这是由推动整场比赛的更高级别测试所驱动的。

答案 1 :(得分:2)

这取决于您正在使用的模拟框架。使用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));

在这里,您可以创建模拟或存根,执行您正在测试的内容,然后断言预期的事情发生了。在这种情况下,您希望调用SetAllCellsTo(Color.Black)一次。

你不关心所有的细胞现在都是黑色的,因为这是在单位之外的行为 - 甚至在被测试的类之外,在这种情况下是BoardEngine。您正在测试的是BoardEngine在实例化时对注入的依赖项调用指定的方法。

编辑以响应OP编辑

您在编辑中列出的两个选项是正确的,但不是互斥的。我建议你进行单元测试,以确保每个功能在各种输入条件下正常工作,测试确保交互工作在更高的层次。

请注意,您可以通过以下方式覆盖基础:(1)测试board.SetAllCellsTo(Color.Black)的呼叫是否真的有效,独立于BoardEngine,然后(2)测试BoardEngine来电[测试工作]方法。不过,您应该进行更高级别的测试,以确保所有内容实际上按预期一起播放而没有副作用。

开始时的测试有点主观。在使用TDD驱逐功能方面,您需要很好地了解系统的工作方式 - BoardEngineBoard如何协同工作。您可以定义两个接口并概述测试,但是在实现两个接口之前实际上不能执行测试,如果编写测试,则无法编译它,因为您没有类实例。另一方面,您可以在实现每个接口时编写单元测试。从IBoard开始,因为它具有较少的依赖性。开始实施Board : IBoard并随时覆盖它。当您知道对SetAllColorsTo()的调用按预期工作时,您会更熟悉调用该方法的BoardEngine构造函数的单元测试。

答案 2 :(得分:2)

我来自java背景,没有.Net经验:

使用典型的模拟库,您需要执行以下操作:

  1. 创建模拟对象
  2. 编写模拟对象
  3. 将模拟对象设置为重播模式
  4. 验证模拟对象是否已正确使用
  5. 我通常使用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);
        }
    }
    

答案 3 :(得分:2)

电路板引擎的行为与电路板的行为不同。您的电路板引擎似乎是a facade,隐藏了一些复杂的电路板。您正在测试电路板引擎,因此您不应该测试电路板的行为,而是引擎会调用电路板的正确行为。在这种情况下,你注入了一个模板,并验证它是否已被调用板引擎应如何调用它。

答案 4 :(得分:2)

您可以通过验证状态或行为来解决此问题 如果您在BoardEngine实例化后验证状态是否需要测试,那么它的所有单元都是黑色的。在这种情况下,你会表明你不关心它是如何实现的,你只是想测试它已经完成 另一个选择是通过向BoardEngine提供IBoard的模拟实现来测试行为,并在实例化BoardEngine之后验证是否使用适当的颜色调用了IBoard的SetAllCellsTo方法。
我更喜欢第一种方法 - 测试状态 - 如果可能的话,但有时候你没有选择。