Mockito嘲笑本地决赛,但在詹金斯失败


11

我已经为静态方法编写了一些单元测试。静态方法仅接受一个参数。参数的类型是最终类。在代码方面:

public class Utility {

   public static Optional<String> getName(Customer customer) {
       // method's body.
   }
}

public final class Customer {
   // class definition
}

因此,对于Utility该类,我创建了一个测试类,UtilityTests在其中为该方法编写了测试getName。单元测试框架为TestNG,而使用的模拟库为Mockito。因此,典型的测试具有以下结构:

public class UtilityTests {

   @Test
   public void getNameTest() {
     // Arrange
     Customer customerMock = Mockito.mock(Customer.class);
     Mockito.when(...).thenReturn(...);

     // Act
     Optional<String> name = Utility.getName(customerMock);

     // Assert
     Assert.assertTrue(...);
   }
}

问题是什么 ?

尽管测试在IntelliJ本地成功运行,但是在Jenkins上却失败(当我将代码推送到远程分支中时,将触发构建并最终运行单元测试)。错误消息如下所示:

org.mockito.exceptions.base.MockitoException:无法模拟/间谍类com.packagename。客户Mockito无法模拟/间谍,因为:-最终类

我尝试了什么?

我搜索了一下,以找到解决方案,但没有成功。我在这里指出,我容许改变的事实,Customer是一个最终的类。除此之外,我想尽可能更改其设计(例如,创建一个接口,该接口将容纳我要模拟的方法并声明Customer类实现了该接口,正如Jose在其著作中正确指出的那样。评论)。我尝试过的事情是在模仿最终中提到的第二种选择。尽管事实上这解决了问题,但它制动了其他一些单元测试:(,无法以任何明显的方式解决。

问题

所以这是我有两个问题:

  1. 首先这怎么可能?考试是否应该在本地和詹金斯都失败?
  2. 如何根据我上面提到的约束来解决此问题?

在此先感谢您的帮助。


1
我的猜测是该enable final配置可在您的工作空间中工作,但在Jenkins其上运行时找不到该文件。检查哪里Jenkins寻找文件,以及它是否实际存在。
第二

这个其他线程说明如何启用最后一类的嘲弄中2的Mockito由资源目录下添加一个配置的Mockito文件:stackoverflow.com/questions/14292863/...
何塞Tepedino

3
在您正在处理的代码中,是否有可能从Customer类(例如ICustomer)中提取接口,并在Utility类中使用它?然后,您可以模拟该接口,而不是具体的最终类
Jose Tepedino

@JoseTepedino这是一个有效的观点。这确实是完全有道理的,并且绝对是解决该问题的一种优雅方法。但是,我想知道是否还有另一种方法,更重要的是,我想了解为什么当前的方法在本地成功而在詹金斯失败。
Christos

1
是否Customer有任何逻辑,还是仅仅是愚蠢的数据类?如果只是一堆带有getter和setter的字段,那么您可以实例化它。
威利斯·布莱克本

Answers:


2

另一种方法是使用“分类方法”模式。

  1. 将方法从客户类中移出到另一个类中,例如CustomerSomething,例如/ CustomerFinances(或它的职责)。
  2. 将构造函数添加到客户。
  3. 现在,您不需要模拟Customer,只需模拟CustomerSomething类!如果它没有外部依赖项,则可能不需要模拟它。

这是一个关于该主题的好博客:https : //simpleprogrammer.com/back-to-basics-mock-eliminate-patterns/


1
感谢您的回答(+1)。我找到了一种解决它的方法(第二个问题的答案)。但是,对于IntelliJ内部测试失败的原因,我仍然不清楚。此外,我再也无法重现它(IntelliJ内部的故障),这完全是怪异的。
克里斯托斯(Christos)

1

首先怎么可能?考试是否应该在本地和詹金斯都失败?

显然,这是一种特定于环境的内容。唯一的问题是-如何确定差异的原因。

我建议您检查org.mockito.internal.util.MockUtil#typeMockabilityOf方法并进行比较,mockMaker两种环境中实际使用的是以及为什么使用。

如果mockMaker相同-比较加载的类IDE-ClientJenkins-Client-它们在测试执行时间上是否有任何区别?

如何根据我上面提到的约束来解决此问题?

以下代码是在假设使用OpenJDK 12和Mockito 2.28.2的情况下编写的,但是我相信您可以将其调整为任何实际使用的版本。

public class UtilityTest {    
    @Rule
    public InlineMocksRule inlineMocksRule = new InlineMocksRule();

    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Test
    public void testFinalClass() {
        // Given
        String testName = "Ainz Ooal Gown";
        Client client = Mockito.mock(Client.class);
        Mockito.when(client.getName()).thenReturn(testName);

        // When
        String name = Utility.getName(client).orElseThrow();

        // Then
        assertEquals(testName, name);
    }

    static final class Client {
        final String getName() {
            return "text";
        }
    }

    static final class Utility {
        static Optional<String> getName(Client client) {
            return Optional.ofNullable(client).map(Client::getName);
        }
    }    
}

对于内联模拟有单独的规则:

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.MockUtil;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class InlineMocksRule implements TestRule {
    private static Field MOCK_MAKER_FIELD;

    static {
        try {
            MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
            VarHandle modifiers = lookup.findVarHandle(Field.class, "modifiers", int.class);

            MOCK_MAKER_FIELD = MockUtil.class.getDeclaredField("mockMaker");
            MOCK_MAKER_FIELD.setAccessible(true);

            int mods = MOCK_MAKER_FIELD.getModifiers();
            if (Modifier.isFinal(mods)) {
                modifiers.set(MOCK_MAKER_FIELD, mods & ~Modifier.FINAL);
            }
        } catch (IllegalAccessException | NoSuchFieldException ex) {
            throw new RuntimeException(ex);
        }
    }

    @Override
    public Statement apply(Statement base, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                Object oldMaker = MOCK_MAKER_FIELD.get(null);
                MOCK_MAKER_FIELD.set(null, Plugins.getPlugins().getInlineMockMaker());
                try {
                    base.evaluate();
                } finally {
                    MOCK_MAKER_FIELD.set(null, oldMaker);
                }
            }
        };
    }
}

感谢您的回答(+1)。我找到了一种解决它的方法(第二个问题的答案)。但是,对于IntelliJ内部测试失败的原因,我仍然不清楚。此外,我再也无法重现它(IntelliJ内部的故障),这完全是怪异的。
克里斯托斯(Christos)

1

确保使用相同的参数运行测试。检查您的intellij运行配置是否与jenkins相匹配。https://www.jetbrains.com/help/idea/creating-and-editing-run-debug-configurations.html。您可以尝试使用与jenkins相同的参数在本地计算机上运行测试(来自终端),如果它将失败,则意味着问题出在参数上


该文件org.mockito.plugins.MockMaker确实在jenkins机器中也存在。我在机器人机器中确实使用了相同的JVM。我将检查您指出的3。谢谢
Christos

我尝试使用Jenkins中使用的命令通过控制台运行测试。它们失败并显示相同的错误消息。因此,IntelliJ内部发生了一些奇怪的事情。
Christos

在您的运行配置中查看.idea / workspace.xml,它位于<component>标记内。之后,您可以学习如何将xml转换为bash命令
Link182 '19

您可以显示用于运行测试的jenkins terminal命令吗?还可以告诉我您使用哪个软件包管理器吗?
Link182

作为构建工具,我使用Gradle。
克里斯托斯(Christos)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.