4

假设您想使用包含以下方法签名来模拟接口:Mockito

public void doThis(Object o);

public void doThis(Object... o)

我需要验证(而doThis(Object o)不是其他方法)已被调用一次。

首先,我认为以下行可以解决问题:

verify(mock, times(1)).doThis(anyObject());

但是,由于这似乎在 Windows 上有效,但在 Linux 上却不行,因为在这种环境中,需要调用其他doThis方法。
这是因为该anyObject()参数似乎与两个方法签名都匹配,并且以或多或少不可预测的方式选择了一个。

如何强制Mockito 始终选择doThis(Object o)进行验证?

4

2 回答 2

2

这不是模拟问题。

在进一步调查期间,我意识到实际方法是在编译时选择的 (JLS §15.12.2)。所以基本上类文件在 windows 和 linux 之间是不同的,这导致了不同的 mockito 行为。

不鼓励使用该接口(请参阅 参考资料Effective Java, 2nd Edition, Item 42)。我将其更改为匹配以下内容:

public void doThis(Object o);

public void doThis(Object firstObj, Object... others)

通过此更改,将始终选择预期的(第一个)方法。

还剩下一件事:
为什么 windows 上的 java 编译器(eclipse 编译器)产生的输出与 Linux 上的 Oracle JDK javac 不同?

这可能是 ECJ 中的一个错误,因为我希望这里的 java 语言规范非常严格。

于 2015-12-15T11:13:36.480 回答
2

我同意另一个答案的大部分内容,只有一部分尚未得到回答:为什么编译器有差异?

仔细一看,这不是 ecj 和 javac 之间的区别,而是不同版本的 JLS 之间的区别:

  • 在合规性 1.7 或更低版本编译时,两个编译器都选择第一个(单参数)方法。
  • 在合规性 1.8 下编译,两个编译器都选择第二种(可变参数)方法

让我们看一下生成的字节码:

     7: invokestatic  #8                  // Method anyObject:()Ljava/lang/Object;
    10: checkcast     #9                  // class "[Ljava/lang/Object;"
    13: invokevirtual #10                 // Method doThis:([Ljava/lang/Object;)V

(两个编译器也同意这些字节)

这就是说:Java 8 中的推理变得更加强大,现在能够推断出 to 的类型anyObject()参数Object[]。这可以从指令中看出,该checkcast指令翻译回源代码如下:(Object[])anyObject(). 这意味着也doThis(Object...)可以在没有 varargs 魔法的情况下调用,但通过传递一个 type 的参数Object[]

现在这两种方法都适用于同一类别(“适用于固定数量调用”),并且在适用方法中搜索最具体的方法会选择第二种方法。

相比之下,Java 7 只允许将第二种方法作为可变参数调用来调用,但如果找到了固定参数匹配,则甚至不会尝试这样做。

以上还说明了如何使程序对 JLS 中的更改具有鲁棒性:

verify(mock, times(1)).doThis(Matchers.<Object>anyObject());

这将告诉所有版本的所有编译器选择第一个方法,因为现在它总是会看到doThis()as的参数Object——如果你真的无法避免这种不健康的重载,那就是:)

于 2015-12-16T21:27:32.083 回答