Mockito는 로컬 최종 클래스를 조롱하지만 Jenkins에서는 실패합니다.


11

정적 메소드에 대한 단위 테스트를 작성했습니다. 정적 메소드는 하나의 인수 만 사용합니다. 인수의 유형은 최종 클래스입니다. 코드 측면에서 :

public class Utility {

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

public final class Customer {
   // class definition
}

그래서위한 Utility클래스 I는 테스트 클래스 생성 한 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는 다음과 같은 이유로 조롱 / 스파이 할 수 없습니다 :-final class

내가 무엇을 시도 했습니까?

솔루션을 찾기 위해 조금 검색했지만 만들지 않았습니다. 나는 내가 점에 유의 하지 사실 변경할 수 CustomerA는 최종 클래스를. 이 외에도, 나는 디자인 을 전혀 바꾸지 않기를 원합니다 (예 : 인터페이스 만들기, 고객 클래스가 모의하고 싶은 메소드를 보유하고 싶어합니다. 논평). 내가 시도한 것은 mockito-final 에서 언급 한 두 번째 옵션 입니다. 이로 인해 문제가 해결되었다는 사실에도 불구하고 다른 단위 테스트를 제동합니다.

질문

여기 내가 가진 두 가지 질문이 있습니다.

  1. 처음에는 어떻게 가능합니까? 테스트가 로컬과 Jenkins에서 모두 실패하지 않아야합니까?
  2. 위에서 언급 한 제약 조건에 따라 어떻게 해결할 수 있습니까?

도움을 주셔서 감사합니다.


1
내 추측은 enable final구성이 작업 공간에서 작동하지만 실행 Jenkins시이 파일을 찾을 수 없다는 것입니다. Jenkins파일을 찾는 위치 와 실제로 있는지 여부를 확인하십시오 .

:이 다른 스레드가 자원 디렉토리 아래 mockito 구성 파일을 추가하여, Mockito 2에서 최종 클래스 조롱을 사용하는 방법을 설명 stackoverflow.com/questions/14292863/...
호세 Tepedino을

3
다루는 코드에서 Customer 클래스에서 인터페이스를 추출하여 ICustomer라고 말하고 Utility 클래스에서 사용할 수 있습니까? 그런 다음 구체적인 최종 클래스 대신 인터페이스를 조롱 할 수 있습니다
Jose Tepedino

@JoseTepedino 이것은 유효한 포인트입니다. 그것은 전적으로 의미가 있으며 분명히이 문제를 극복하는 우아한 방법입니다. 그러나 다른 방법이 있고 더 중요한 것이 있는지 궁금합니다. 현재 접근 방식이 왜 로컬에서 성공하고 Jenkins에서 실패하는지 이해하고 싶습니다.
Christos

1
Customer논리가 있습니까 아니면 바보 같은 데이터 클래스입니까? 게터와 세터가있는 필드가 많으면 인스턴스화 할 수 있습니다.
Willis Blackburn

답변:


2

다른 방법은 'method to class'패턴을 사용하는 것입니다.

  1. 고객 클래스에서 메소드를 다른 클래스 / 클래스로 옮기십시오 (예 : CustomerSomething eg / CustomerFinances).
  2. Customer에 생성자를 추가하십시오.
  3. 이제 CustomerSomething 클래스 만 고객을 조롱 할 필요가 없습니다! 외부 종속성이없는 경우이를 조롱 할 필요가 없습니다.

다음은 주제에 대한 좋은 블로그입니다. https://simpleprogrammer.com/back-to-basics-mock-eliminating-patterns/


1
답변 주셔서 감사합니다 (+1). 문제를 해결하는 방법을 찾았습니다 (두 번째 질문에 대한 답변). 그러나 IntelliJ 내에서 테스트가 실패하는 이유는 여전히 명확하지 않습니다. 또한 나는 더 이상 그것을 재현 할 수 없습니다 (IntelliJ 내부의 실패).
Christos

1

처음에는 어떻게 가능합니까? 테스트가 로컬과 Jenkins에서 모두 실패하지 않아야합니까?

분명히 일종의 환경 적 특성입니다. 유일한 질문은 차이의 원인을 결정하는 방법입니다.

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 내부의 실패).
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

테스트를 실행하는 데 사용되는 jenkins 터미널 명령을 보여줄 수 있습니까? 또한 어떤 패키지 관리자를 사용하고 있는지 알려줄 수 있습니까?
링크 182

빌드 도구로 Gradle을 사용합니다.
Christos
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.