7

我正在使用 GWTP,添加了一个 Contract 层来抽象 Presenter 和 View 之间的知识,我对 GWTP 的结果非常满意。我正在用 Mockito 测试我的演示者。

但随着时间的推移,我发现很难通过测试来保持一个干净的演示者。我做了一些重构来改进它,但我仍然不满意。

我发现以下是问题的核心:我的演示者经常需要异步调用,或者通常使用回调调用对象方法来继续我的演示者流程(它们通常是嵌套的)。

例如 :

  this.populationManager.populate(new PopulationCallback()
  {
     public void onPopulate()
     {
        doSomeStufWithTheView(populationManager.get());
     }
  });

在我的测试中,我最终验证了模拟的 PopulationManager 对象的 population() 调用。然后在 doSomeStufWithTheView() 方法上创建另一个测试。

但我很快发现这是一个糟糕的设计:任何更改或重构都会破坏我的许多测试,并迫使我从头开始创建其他测试,即使演示者功能没有改变!另外,我没有测试回调是否是我想要的。

所以我尝试使用 mockito doAnswer 方法来不破坏我的演示者测试流程:

doAnswer(new Answer(){
     public Object answer(InvocationOnMock invocation) throws Throwable
     {
        Object[] args = invocation.getArguments();
        ((PopulationCallback)args[0]).onPopulate();
        return null;
     }
 }).when(this.populationManager).populate(any(PopulationCallback.class));

我考虑到它的代码不那么冗长(并且在内部不太依赖于 arg 位置):

doAnswer(new PopulationCallbackAnswer())
  .when(this.populationManager).populate(any(PopulationCallback.class));

所以在嘲笑 populationManager 的同时,我仍然可以测试我的演示者的流程,基本上是这样的:

@Test
public void testSomeStuffAppends()
{
  // Given
  doAnswer(new PopulationCallbackAnswer())
  .when(this.populationManager).populate(any(PopulationCallback.class));

  // When
  this.myPresenter.onReset();

  // Then
  verify(populationManager).populate(any(PopulationCallback.class)); // That was before
  verify(this.myView).displaySomething(); // Now I can do that.
}

我想知道它是否很好地使用了doAnswer方法,或者它是否是代码味道,并且可以使用更好的设计?

通常,我的演示者倾向于只使用其他对象(例如某些 Mediator Pattern)并与视图交互。我有一些演示者有数百(~400)行代码。

再一次,这是一个糟糕设计的证明,还是演示者冗长是正常的(因为它使用了其他对象)?

有没有人听说过一些使用 GWTP 并干净地测试其演示者的项目?

我希望我能以全面的方式解释。

先感谢您。

PS:我对 Stack Overflow 还很陌生,加上我的英语还不够,如果我的问题需要改进,请告诉我。

4

3 回答 3

1

您可以使用ArgumentCaptor
查看此博客文章了解更多详细信息。

于 2012-12-07T09:42:25.040 回答
0

如果我理解正确,您是在询问设计/架构。

这不应该算作答案,这只是我的想法。

如果我遵循了代码:

    public void loadEmoticonPacks() {
    executor.execute(new Runnable() {
        public void run() {
            pack = loadFromServer();
            savePackForUsageAfter();
        }
    });
}

我通常不指望执行者,只是通过加载和保存来检查方法是否完成了具体工作。所以这里的执行器只是防止UI线程中长时间操作的工具。

如果我有类似的东西:

accountManager.setListener(this);
....
public void onAccountEvent(AccountEvent event) {
....
}

我将首先检查我们是否订阅了事件(并在某些破坏时取消订阅),我将检查是否onAccountEvent符合预期的场景。

UPD1。可能,在示例 1 中,最好是提取方法loadFromServerAndSave并检查它是否未在 UI 线程上执行,并检查它是否按预期执行所有操作。

UPD2。最好使用 Guava Bus 之类的框架进行事件处理。

于 2012-12-07T13:08:07.413 回答
0

我们在演示者测试中也使用了这种 doAnswer 模式,通常它工作得很好。但有一个警告:如果您像这样测试它,您实际上是在消除调用的异步性质,即在服务器调用启动后立即执行回调。

这可能导致未发现的竞争条件。要检查这些,您可以将其分为两步:调用服务器时,answer 方法仅保存回调。然后,当它适合你的测试时,你可以在你的答案上调用类似 flush() 或 onSuccess() 的东西(我建议为此创建一个可以在其他情况下重用的实用程序类),这样你就可以控制何时结果的回调确实被调用了。

于 2013-04-21T06:00:42.503 回答