Mockito wyśmiewa lokalnie ostatnią klasę, ale zawodzi w Jenkins


11

Napisałem kilka testów jednostkowych dla metody statycznej. Metoda statyczna przyjmuje tylko jeden argument. Typ argumentu jest klasą końcową. Pod względem kodu:

public class Utility {

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

public final class Customer {
   // class definition
}

Tak dla Utilityklasy I stworzyliśmy klasę testową UtilityTests, w której mam napisane testy na tej metodzie getName. Środowisko testowania jednostkowego to TestNG, a używana jest fałszywa biblioteka Mockito. Typowy test ma więc następującą strukturę:

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(...);
   }
}

Jaki jest problem ?

Podczas gdy testy przebiegają pomyślnie lokalnie, wewnątrz IntelliJ, nie działają na Jenkinsie (kiedy pcham mój kod w zdalnej gałęzi, uruchamiana jest kompilacja, a testy jednostkowe uruchamiane na końcu). Komunikat o błędzie jest następujący:

org.mockito.exceptions.base.MockitoException: Nie można wyśmiewać / szpiegować klasy com.packagename. Klient Mockito nie może wyśmiewać / szpiegować, ponieważ: - klasa ostateczna

Co próbowałem

Szukałem trochę, aby znaleźć rozwiązanie, ale nie udało mi się. Zaznaczam tutaj, że nie wolno mi zmieniać faktu, że Customerjest to klasa ostateczna . Poza tym chciałbym, jeśli to możliwe, w ogóle nie zmieniać jego projektu (np. Stworzyć interfejs, który przechowuje metody, które chcę wyśmiewać i stwierdza, że ​​klasa Customer implementuje ten interfejs, jak słusznie zauważył Jose w swoim komentarz). To, czego próbowałem, to druga opcja wspomniana na finale mockito . Pomimo tego, że rozwiązało to problem, hamuje kilka innych testów jednostkowych :(, których nie można naprawić w żaden widoczny sposób.

pytania

Oto dwa pytania, które mam:

  1. Jak to w ogóle możliwe? Czy test nie powinien zawieść zarówno lokalnie, jak i w Jenkins?
  2. Jak można to naprawić w oparciu o ograniczenia, o których wspomniałem powyżej?

Z góry dziękuję za wszelką pomoc.


1
Domyślam się, że enable finalkonfiguracja działa w twoim obszarze roboczym, ale po uruchomieniu Jenkinsnie można znaleźć tego pliku. Sprawdź, gdzie Jenkinsszuka pliku i czy rzeczywiście tam jest, czy nie.
— drugi

Ten drugi wątek wyjaśnia, jak włączyć końcowe kpiny z klasy w Mockito 2, dodając plik konfiguracyjny mockito w katalogu zasobów: stackoverflow.com/questions/14292863/...
— Jose Tepedino,

3
Czy w kodzie, z którym mamy do czynienia, byłoby możliwe wyodrębnienie interfejsu z klasy Customer, powiedzmy ICustomer, i użycie go w klasie Utility? Następnie możesz wyśmiewać ten interfejs zamiast konkretnej klasy końcowej
— Jose Tepedino,

@JoseTepedino To jest poprawny punkt. Ma to całkowicie sens i jest zdecydowanie eleganckim sposobem na rozwiązanie tego problemu. Zastanawiam się jednak, czy istnieje inny sposób i, co ważniejsze, chcę zrozumieć, dlaczego obecne podejście odnosi sukces lokalnie, a nie w Jenkins.
— Christos,

1
Czy Customerma w tym jakąś logikę, czy to tylko głupia klasa danych? Jeśli to tylko kilka pól z modułami pobierającymi i ustawiającymi, możesz po prostu utworzyć jego instancję.
— Willis Blackburn,

Odpowiedzi:


2

Alternatywnym podejściem byłoby użycie wzorca „metoda do klasyfikacji”.

  1. Przenieś metody z klasy klienta do innej klasy / klas, powiedz CustomerSomething np. / CustomerFinances (lub cokolwiek to jest odpowiedzialność).
  2. Dodaj konstruktora do klienta.
  3. Teraz nie musisz kpić z klienta, tylko klasa CustomerSomething! Być może nie będziesz musiał kpić z tego, jeśli nie ma zewnętrznych zależności.

Oto dobry blog na ten temat: https://simpleprogrammer.com/back-to-basics-mock- Eliminating-patterns/


1
Dziękuję za odpowiedź (+1). Znalazłem sposób, aby to naprawić (odpowiedź na drugie pytanie). Jednak powód niepowodzenia testów w IntelliJ nadal nie jest dla mnie jasny. Co więcej, nie mogę go już odtworzyć (awaria w IntelliJ), co jest całkowicie dziwne.
— Christos,

1

Jak to w ogóle możliwe? Czy test nie powinien zawieść zarówno lokalnie, jak i w Jenkins?

To oczywiście rodzaj env-specyfiki. Jedyne pytanie brzmi - jak ustalić przyczynę różnicy.

Sugeruję sprawdzenie org.mockito.internal.util.MockUtil#typeMockabilityOfmetody i porównanie, co mockMakerjest faktycznie używane w obu środowiskach i dlaczego.

Jeśli mockMakerjest to samo - porównaj załadowane klasy IDE-Clientvs Jenkins-Client- czy mają one jakąkolwiek różnicę w czasie wykonywania testu.

Jak można to naprawić w oparciu o ograniczenia, o których wspomniałem powyżej?

Poniższy kod został napisany przy założeniu OpenJDK 12 i Mockito 2.28.2, ale wierzę, że można go dostosować do dowolnej faktycznie używanej wersji.

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);
        }
    }    
}

Z osobną regułą dla próbnych wstawek:

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);
                }
            }
        };
    }
}

Dziękuję za odpowiedź (+1). Znalazłem sposób, aby to naprawić (odpowiedź na drugie pytanie). Jednak powód niepowodzenia testów w IntelliJ nadal nie jest dla mnie jasny. Co więcej, nie mogę go już odtworzyć (awaria w IntelliJ), co jest całkowicie dziwne.
— Christos,

1

Upewnij się, że uruchomiłeś test z tymi samymi argumentami. Sprawdź, czy konfiguracje uruchamiania Intellij pasują do jenkins. https://www.jetbrains.com/help/idea/creating-and-editing-run-debug-configurations.html . Możesz spróbować uruchomić test na komputerze lokalnym z tymi samymi argumentami, co na jenkins (z terminala), jeśli się nie powiedzie, oznacza to, że problem jest w argumentach


Plik org.mockito.plugins.MockMakeristnieje również w maszynie Jenkinsa. Używam tej samej maszyny JVM w botach. Sprawdzę 3, które wskazałeś. Dzięki
— Christos,

Próbowałem uruchomić test za pomocą konsoli, używając polecenia użytego w Jenkins. Nie udaje im się ten sam dokładny komunikat o błędzie. Coś dziwnego dzieje się w IntelliJ.
— Christos,

Spójrz na .idea / workspace.xml przy konfiguracji uruchamiania, jest on wewnątrz znacznika <component>. Następnie możesz dowiedzieć się, jak przekształcić ten xml w polecenie bash
— Link182

Czy możesz pokazać komendę Jenkins terminal, która służy do uruchamiania testów? Czy możesz mi również powiedzieć, którego menedżera pakietów używasz?
— Link182,

Jako narzędzie do budowania używam Gradle.
— Christos,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.