【问题标题】:How to unit test a method whose side effect is to call other method?如何对副作用是调用其他方法的方法进行单元测试?
【发布时间】:2015-03-03 20:04:01
【问题描述】:

这是我的例子:

void doneWithCurrentState(State state) {
    switch (state) {
        case State.Normal:
            // this method is never actually called with State.Normal
            break;
        case State.Editing:
            controller.updateViewState(State.Normal);
            database.updateObjectWithDetails(controller.getObjectDetailsFromViews())
            break;
        case State.Focus:
            controller.updateViewState(State.Editing);
            break;
    }
}

当按下特定按钮时,我的controller 会调用doneWithCurrentStatestates 是屏幕上的不同位置,controller 的视图可以假定。

如果当前状态为Normal,按钮将被隐藏。

如果在当前状态为Editing 的情况下按下按钮,则将调用doneWithCurrentState 方法(我说方法是因为它实际上是在一个类中),它应该将控制器的视图状态更改为@987654330 @ 并使用应从控制器视图(即文本字段,复选框等)。

如果在当前状态为Focus 的情况下按下按钮,它应该只是发送回Editing 状态。

我正在像这样对它进行单元测试:

void testDoneWithCurrentStateEditing() {
    mockController.objectDetails = ...;

    myClass.doneWithCurrentState(State.Editing);

    AssertEqual(mockController.viewState, State.Normal, "controller state should change to Normal");

    AssertTrue(mockDatabase.updateObjectWithDetailsWasCalled, "updateObjectWithDetails should be called");
    AssertEqual(mockDatabase.updatedWithObjectDetail, mockController.objectDetails, "database should be updated with corresponding objectDetails");
}

void testDoneWithCurrentStateFocus() {
    myClass.doneWithCurrentState(State.Focus);

    AssertEqual(mockController.viewState, State.Editing, "controller state should change to Editing");

    AssertFalse(mockDatabase.updateObjectWithDetailsWasCalled, "updateObjectWithDetails should not be called");
}

但这似乎是错误的,似乎我在断言一个方法调用已经完成,然后我正在调用......这就像断言 setter 和 getter 方法一样。

测试doneWithCurrentState 方法的正确方法是什么? 作为答案的一部分,我确实接受类似“首先你应该重构方法以更好地分离这些问题......”。

谢谢。

【问题讨论】:

  • 就是wasCalled后面的断言
  • 对了,有人需要眼镜
  • 我多次阅读您的代码,但在您的测试中看不到任何错误。那很好White boxing tests

标签: unit-testing testing mocking tdd


【解决方案1】:

如果你写的不是测试优先,一个明显的写法是写一个case,然后复制粘贴到下一个case。在这种情况下,一个容易犯的错误是忘记将参数更新为 updateViewState()。因此(例如)您可能会发现自己从 State.Focus 转到 State.Normal。你所写的测试,虽然对你来说可能看起来很弱,但可以防止这种性质的错误。所以我认为它正在做它应该做的。

【讨论】:

  • 没错,但我在测试中也会犯同样的错误,这就是为什么我说这似乎是测试 getter 和 setter。
  • @RodrigoRuiz 是的,你可以,但没关系。当您编写 white boxing 单元测试时,那种错误是 legit:您将在集成阶段修复代码和测试,或者如果编写一些 black boxing以捕获这些错误的方式进行测试。但是,当您进行这些测试时,您可以重构您的方法或添加功能而不会出现任何回归。
  • 如何为这个方法添加黑盒单元测试?
【解决方案2】:

首先,请考虑使用state machine 进行状态转换,您将摆脱switch 语句分支业务,这将大大简化您的测试。

接下来,将您的测试视为代码和设计异味的潜在来源。如果很难为一段代码编写测试 - 可能代码缺乏质量(破坏 SRP、过于耦合等)并且可以简化/改进。

void doneWithCurrentState(State state) {
     State nextState = this.stateMachine.GetNextState(state);
     controller.updateViewState(nextState);

     if(nextState == State.Editing) 
         database.updateObjectWithDetails(controller.getObjectDetailsFromViews());
}

然后你可以注意到你可以拉出对方法的状态机的调用,并传入nextState。

//whoever calls this method should get nextState from state machine.
void doneWithCurrentState(State nextState) {
     controller.updateViewState(nextState);

     if(nextState == State.Editing) 
         database.updateObjectWithDetails(controller.getObjectDetailsFromViews());
}

等等..您将在状态机测试中为状态转换编写简单的测试..您的整体代码复杂性下降,一切都很好!?好吧,您可以达到的良好程度几乎没有限制,我可以看到可以进一步清理代码的多种方式。

根据您的原始问题,如何测试您的类的代码使用具有特定状态的适当参数调用“数据库”或“控制器”。您正在“正确”地做,什么是模拟打算做的。但是,还有更好的方法。考虑基于事件的设计。如果您的控制器可以触发诸如“NextState”之类的事件并且您的“数据库”对象可以订阅它怎么办?那么你所有的测试需要测试的是正确的事件被触发并且不包括任何关于数据库的东西(消除依赖:))

【讨论】:

  • 在其中一种情况下,我不更新ViewState(...),除了您将使用 if 代替 switch 之外,它最终会相同。唯一可以分离的逻辑是 nextState
  • 我不喜欢观察者模式的一点是它更难追踪。您仍在创建依赖项,但现在它不是显式的。
【解决方案3】:

我认为 Paul 是正确的:将基于传入状态的状态更改放入状态机,即一个负责确定接下来会发生什么的对象。这听起来可能很愚蠢,因为您有点将相同的代码移动到另一个对象,但至少这会让控制器节食。它不应该担心太多细节本身无法维护。

不过,我担心updateViewState。为什么它与用户交互的控制器回调采用相同类型的参数?你能以不同的方式建模吗?如果不查看信息流,很难告诉您任何具体的信息(带有 cmets 的详细序列图可能会有所帮助),因为对此类问题的真正洞察通常位于调用堆栈更深的多个级别。如果不了解这一切的含义,就很难想出一个适合的罐头解决方案。

可能有帮助的问题:

  • 如果 State 代表 3 个 (?) 用户交互,它们都通过同一个隧道,您能否将要采取的操作建模为策略或命令?
  • 如果doneWithCurrentState代表完成多种交互模式中的一种,你真的需要使用共享的doneWithCurrentState方法吗?你不能使用三个不同的回调吗?也许这是一种错误的抽象。 (“不要重复自己”isn't about code but about things that change (in)dependently

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多