Мокіто знущається з місцевого випускного класу, але не вдається в Дженкінсі


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 вони не спрацьовують на Дженкінсі (коли я натискаю свій код у віддаленій гілці, запускається збірка і тестові одиниці запускаються в кінці). Повідомлення про помилку є таким, як:

org.mockito.exceptions.base.MockitoException: Не вдається знущатися / шпигунського класу com.packagename.Customer Mockito не може глузувати / шпигувати, тому що: - остаточний клас

Що я спробував?

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

Запитання

Ось ось два питання у мене:

  1. Як це можливо в першу чергу? Чи не повинен тест провалитися як на місцевому рівні, так і в Дженкінсі?
  2. Як це можна виправити виходячи з обмежень, про які я згадував вище?

Заздалегідь дякую за будь-яку допомогу.


1
Я думаю, що enable finalконфігурація працює у вашій робочій області, але при запуску на Jenkinsїї файлі не вдається знайти цей файл. Перевірте, де Jenkinsшукається файл і чи є він насправді чи ні.
другий

Це інший потік пояснює , як включити остаточний клас глузування в Mockito 2, шляхом додавання файлу конфігурації Mockito в каталозі ресурсів: stackoverflow.com/questions/14292863 / ...
Хосе Tepedino

3
Чи можливо в коді, з яким ви маєте справу, витягти інтерфейс із класу клієнтів, скажімо, ICustomer, і використовувати його в класі Utility? Тоді ви могли знущатися над цим інтерфейсом замість конкретного фінального класу
Хосе Тепедіно,

@JoseTepedino Це дійсний пункт. Це має сенс цілком, і це, безумовно, елегантний спосіб подолати цю проблему. Однак мені цікаво, чи є інший спосіб і що ще важливіше, я хочу зрозуміти, чому сучасний підхід досягає успіху на місцях і не вдається Дженкінсу.
Крістос

1
Чи Customerє в ньому якась логіка чи це просто німий клас даних? Якщо це просто купа полів з геттерами та сетерами, то ви можете просто інстанціювати це.
Вілліс Блекберн

Відповіді:


2

Альтернативним підходом було б використання схеми «метод для класу».

  1. Перенесіть методи з класу клієнтів в інший клас / класи, скажімо, CustomerSomething, наприклад, / CustomerFinances (або будь-яка відповідальність за нього).
  2. Додайте конструктор до Клієнта.
  3. Тепер вам не потрібно знущатися над Клієнтом, просто клас CustomerSomething! Можливо, вам не потрібно буде знущатися над тим, якщо він не має зовнішніх залежностей.

Ось хороший блог на тему: https://simpleprogrammer.com/back-to-basics-mock-elimining-patterns/


1
Дякуємо за вашу відповідь (+1). Я знайшов спосіб це виправити (відповідь на друге запитання). Однак причина, чому тести не спрацьовують у IntelliJ, мені досі не зрозуміла. Крім того, я більше не можу його відтворити (збій всередині IntelliJ), що абсолютно дивно.
Христос

1

Як це можливо в першу чергу? Чи не повинен тест провалитися як на місцевому рівні, так і в Дженкінсі?

Це, очевидно, своєрідна специфіка навколишнього середовища. Питання лише - як визначити причину різниці.

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

Якщо mockMakerте саме - порівняйте завантажені класи IDE-Clientпроти 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);
                }
            }
        };
    }
}

Дякуємо за вашу відповідь (+1). Я знайшов спосіб це виправити (відповідь на друге запитання). Однак причина, чому тести не спрацьовують у IntelliJ, мені досі не зрозуміла. Крім того, я більше не можу його відтворити (збій всередині IntelliJ), що абсолютно дивно.
Христос

1

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


Файл org.mockito.plugins.MockMakerіснує і в машині дженкінса. Я використовую той же JVM в бот-машинах. Я перевірю 3 вказані вами. Спасибі
Христос

Я спробував запустити тест через консоль, використовуючи команду, що використовується в Дженкінсі. Вони не отримують однакове точне повідомлення про помилку. Так щось дивне відбувається всередині IntelliJ.
Крістос

Погляньте на .idea / workspace.xml під час конфігурації запуску, він знаходиться в тезі <component>. Після цього ви можете дізнатися, як перетворити цей xml в команду bash
Link182

Чи можете ви показати команду терміналу jenkins, яка використовується для запуску тестів? Ви також можете сказати мені, який менеджер пакунків ви використовуєте?
Посилання182

Як інструмент збирання я використовую Gradle.
Крістос
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.