Мокито издевается над финальным классом, но терпит неудачу в Дженкинсе

11

Я написал несколько тестов для статического метода. Статический метод принимает только один аргумент. Тип аргумента является окончательным классом. С точки зрения кода:

public class Utility {

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

public final class Customer {
   // class definition
}

Итак, для Utilityкласса я создал тестовый класс, UtilityTestsв котором я написал тесты для этого метода getName. Основой модульного тестирования является TestNG, а используемая библиотека - mocking 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.Customer Mockito не может издеваться / шпионить, потому что: - последний класс

Что я пробовал?

Я немного искал, чтобы найти решение, но не смог. Я отмечаю здесь, что мне не разрешено менять тот факт, что Customerэто последний класс. В дополнение к этому, я хотел бы, если возможно, вообще не изменять его дизайн (например, создавать интерфейс, который содержал бы методы, которые я хочу смоделировать, и утверждать, что класс Customer реализует этот интерфейс, как правильно указал Хосе в своей статье). комментарий). То, что я попробовал, это второй вариант, упомянутый на финале конкурса . Несмотря на то, что это решило проблему, оно затормозило некоторые другие юнит-тесты :(, которые нельзя исправить ни одним очевидным способом.

Вопросов

Итак, вот два вопроса, которые у меня есть:

  1. Как это возможно в первую очередь? Разве тест не должен пройти как локально, так и в Дженкинсе?
  2. Как это можно исправить, основываясь на вышеупомянутых ограничениях?

Заранее благодарю за любую помощь.

Christos
источник
1
Я предполагаю, что enable finalконфигурация работает в вашей рабочей области, но при запуске Jenkinsне может найти этот файл. Проверьте, где Jenkinsнаходится файл и находится ли он на самом деле там или нет.
Второе
В этом другом потоке объясняется, как включить окончательное макетирование класса в Mockito 2, добавив файл конфигурации mockito в каталог ресурсов: stackoverflow.com/questions/14292863/…
Хосе Тепедино
3
Возможно ли в коде, с которым вы работаете, извлечь интерфейс из класса Customer, скажем, ICustomer, и использовать его в классе Utility? Тогда вы могли бы смоделировать этот интерфейс вместо конкретного финального класса
Жозе Тепедино
@JoseTepedino Это верная точка. Это действительно имеет смысл, и это определенно элегантный способ преодолеть эту проблему. Однако мне интересно, есть ли другой путь и, что более важно, я хочу понять, почему текущий подход успешен локально и терпит неудачу в Дженкинсе.
Христос
1
Есть ли в Customerэтом какая-то логика, или это просто тупой класс данных? Если это просто набор полей с геттерами и сеттерами, то вы можете просто создать его экземпляр.
Уиллис Блэкберн

Ответы:

2

Альтернативный подход заключается в использовании шаблона «метод для класса».

  1. Переместите методы из класса customer в другой класс / классы, например CustomerSomething, например / CustomerFinances (или независимо от того, за что он отвечает).
  2. Добавьте конструктор в Customer.
  3. Теперь вам не нужно издеваться над Клиентом, просто класс CustomerSomething! Вам, возможно, не нужно издеваться над этим, если у него нет внешних зависимостей.

Вот хороший блог на эту тему: https://simpleprogrammer.com/back-to-basics-mock-eliminating-patterns/

Джонни Альфа
источник
1
Спасибо за ваш ответ (+1). Я нашел способ это исправить (ответ на второй вопрос). Однако причина, по которой тесты не пройдены внутри IntelliJ, до сих пор мне не ясна. Кроме того, я больше не могу воспроизвести его (ошибка внутри IntelliJ), что совершенно странно.
Христос
1

Как это возможно в первую очередь? Разве тест не должен пройти как локально, так и в Дженкинсе?

Это, очевидно, своего рода env-специфика. Вопрос только в том, как определить причину различия.

Я бы посоветовал вам проверить org.mockito.internal.util.MockUtil#typeMockabilityOfметод и сравнить, что mockMakerна самом деле используется в обеих средах и почему.

Если mockMakerже - сравнить загруженные классы IDE-ClientVS Jenkins-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);
                }
            }
        };
    }
}
Ursa
источник
Спасибо за ваш ответ (+1). Я нашел способ это исправить (ответ на второй вопрос). Однако причина, по которой тесты не пройдены внутри IntelliJ, до сих пор мне не ясна. Кроме того, я больше не могу воспроизвести его (ошибка внутри IntelliJ), что совершенно странно.
Христос
1

Убедитесь, что вы запускаете тест с теми же аргументами. Проверьте, соответствуют ли ваши настройки запуска intellij jenkins. https://www.jetbrains.com/help/idea/creating-and-editing-run-debug-configurations.html . Вы можете попытаться запустить тест на локальной машине с теми же аргументами, что и на jenkins (из терминала), если это не удастся, это означает, что проблема в аргументах

Link182
источник
Файл org.mockito.plugins.MockMakerсуществует также в машине Дженкинса. Я использую ту же JVM в бот-машинах. Я проверю 3, которые вы указали. Спасибо
Христос
Я попытался запустить тест через консоль, используя команду, использованную в Jenkins. Они терпят неудачу с тем же самым точным сообщением об ошибке. Что-то странное происходит внутри IntelliJ.
Христос
Посмотрите на .idea / workspace.xml в вашей конфигурации запуска, она находится внутри тега <component>. После этого вы можете узнать, как преобразовать этот xml в команду bash
Link182
Можете ли вы показать команду терминала jenkins, которая используется для запуска тестов? Также вы можете сказать мне, какой менеджер пакетов вы используете?
Ссылка 182
В качестве инструмента для сборки я использую Gradle.
Христос