Mockito yerel olarak son sınıfa alay ediyor ancak Jenkins'te başarısız oluyor


11

Statik bir yöntem için bazı birim testleri yazdım. Statik yöntem yalnızca bir bağımsız değişken alır. Argümanın türü son sınıftır. Kod açısından:

public class Utility {

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

public final class Customer {
   // class definition
}

Yani için Utilitysınıfın bir test sınıfı oluşturduk UtilityTests, bu yöntem için testler yazdım hangi getName. Birim test çerçevesi TestNG'dir ve kullanılan alay kitaplığıdır Mockito. Yani tipik bir test aşağıdaki yapıya sahiptir:

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

Sorun nedir ?

Testler yerel olarak, IntelliJ içinde başarılı bir şekilde yürütülürken, Jenkins üzerinde başarısız olurlar (kodumu uzak dalda ittiğimde, bir yapı tetiklenir ve birim testleri sonunda çalışır). Hata mesajı aşağıdaki gibidir:

org.mockito.exceptions.base.MockitoException: alay / casus sınıfı com.packagename.Müşteri Mockito alay edemez / casus olamaz çünkü: - son sınıf

Ne denedim?

Bir çözüm bulmak için biraz araştırdım ama başaramadım. Ben olduğumu Burada dikkat değil gerçeğini değiştirmez izin Customerbir olan nihai sınıfı. Buna ek olarak, mümkünse tasarımını hiç değiştirmemek istiyorum (örneğin, alay etmek istediğim yöntemleri tutan bir arayüz oluşturmak ve Müşteri sınıfının doğru bir şekilde Jose'nin işaret ettiği gibi Müşteri sınıfının bu arayüzü uyguladığını belirtmek istiyorum. yorum Yap). Denediğim şey mockito-final'de ikinci seçenek . Bunun sorunu çözmesine rağmen, diğer bazı birim testlerini frenliyor :(, hiçbir şekilde düzeltilemez.

Sorular

İşte iki sorum var:

  1. İlk etapta bu nasıl mümkün olabilir? Test hem yerel olarak hem de Jenkins'te başarısız olmamalı mı?
  2. Bu, yukarıda bahsettiğim kısıtlamalara dayanarak nasıl düzeltilebilir?

Herhangi bir yardım için şimdiden teşekkürler.


1
Tahminimce enable finalyapılandırma çalışma alanınızda çalışır, ancak çalıştırıldığında Jenkinsbu dosyayı bulamıyor. JenkinsDosyayı nerede aradığını ve gerçekte orada olup olmadığını kontrol edin .
ikinci

Bu diğer iş parçacığı, kaynak dizini altına bir mockito yapılandırma dosyası ekleyerek Mockito 2'de son sınıf alayının nasıl etkinleştirileceğini açıklar: stackoverflow.com/questions/14292863/…
Jose Tepedino

3
Karşılaştığınız kodda, Müşteri sınıfından bir arayüz çıkarmak, ICustomer demek ve bunu Utility sınıfında kullanmak mümkün müdür? Sonra somut son sınıf yerine bu arayüzü alay edebilirsin
Jose Tepedino

@JoseTepedino Bu geçerli bir nokta. Tamamen mantıklı ve bu sorunun üstesinden gelmenin kesinlikle zarif bir yolu. Ancak başka bir yol olup olmadığını merak ediyorum ve daha da önemlisi, mevcut yaklaşımın neden yerel olarak başarılı olduğunu ve Jenkins'te başarısız olduğunu anlamak istiyorum.
Christos

1
Customerİçinde herhangi bir mantık var mı , yoksa sadece aptal bir veri sınıfı mı? Sadece alıcılar ve ayarlayıcılar içeren bir grup alansa, o zaman bunu başlatabilirsiniz.
Willis Blackburn

Yanıtlar:


2

Alternatif bir yaklaşım 'sınıflandırma yöntemi' örüntüsünü kullanmak olacaktır.

  1. Yöntemleri müşteri sınıfından başka bir sınıfa / sınıfa taşıyın.
  2. Müşteriye bir kurucu ekleyin.
  3. Artık Müşteri ile alay etmek zorunda değilsiniz, sadece CustomerSomething sınıfı! Dış bağımlılıkları yoksa, bunu yapmanıza gerek olmayabilir.

İşte konu hakkında iyi bir blog: https://simpleprogrammer.com/back-to-basics-mock-eliminating-patterns/


1
Cevabınız için teşekkürler (+1). Bunu düzeltmenin bir yolunu buldum (ikinci soruya cevap). Ancak testlerin IntelliJ içinde başarısız olmasının nedeni hala net değil. Dahası, artık onu tutamam (IntelliJ içindeki hata), tamamen garip.
Christos

1

İlk etapta bu nasıl mümkün olabilir? Test hem yerel olarak hem de Jenkins'te başarısız olmamalı mı?

Açıkçası bir tür env-spesifiktir. Tek soru - farkın nedeninin nasıl belirleneceği.

org.mockito.internal.util.MockUtil#typeMockabilityOfYöntemi kontrol etmenizi ve mockMakerher iki ortamda da neyin gerçekten kullanıldığını karşılaştırmanızı öneririm .

Eğer mockMakeraynıysa - yüklü sınıfları karşılaştır - IDE-Clientvs Jenkins-Client- testin yürütülmesi sırasında herhangi bir farkları var mıdır.

Yukarıda bahsettiğim kısıtlamalara dayanarak bu nasıl düzeltilebilir?

Aşağıdaki kod OpenJDK 12 ve Mockito 2.28.2 varsayımıyla yazılmıştır, ancak gerçekten kullanılan herhangi bir sürüme ayarlayabileceğinize inanıyorum.

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

Satır içi alaylar için ayrı bir kuralla:

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

Cevabınız için teşekkürler (+1). Bunu düzeltmenin bir yolunu buldum (ikinci soruya cevap). Ancak testlerin IntelliJ içinde başarısız olmasının nedeni hala net değil. Dahası, artık onu tutamam (IntelliJ içindeki hata), tamamen garip.
Christos

1

Testi aynı argümanlarla yaptığınızdan emin olun. İntellij run yapılandırmalarınızın cenkins ile eşleşip eşleşmediğini kontrol edin. https://www.jetbrains.com/help/idea/creating-and-editing-run-debug-configurations.html . Jenkinlerle (terminalden) aynı argümanlarla yerel makinede test çalıştırmayı deneyebilirsiniz, eğer başarısız olursa sorunun argümanlarda olduğu anlamına gelir


Dosya org.mockito.plugins.MockMakerjenkins makinesinde de var. Bot makinelerinde aynı JVM'yi kullanıyorum. İşaret ettiğin 3'ü kontrol edeceğim. Teşekkürler
Christos

Jenkins'te kullanılan komutu kullanarak testi konsoldan çalıştırmayı denedim. Aynı hata iletisiyle başarısız olurlar. IntelliJ'in içinde garip bir şey oluyor.
Christos

Çalışma konfigürasyonunuzda .idea / workspace.xml dosyasına bir göz atın, <component> etiketinin içindedir. Bundan sonra bu
xml'yi

Testleri çalıştırmak için kullanılan jenkins terminal komutunu gösterebilir misiniz? Ayrıca bana hangi paket yöneticisini kullandığını söyleyebilir misin?
Link182

Bir derleme aracı olarak Gradle kullanıyorum.
Christos
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.