Mockito를 사용하여 추상 클래스 테스트


213

추상 클래스를 테스트하고 싶습니다. 물론 클래스에서 상속받은 모형수동으로 작성할있습니다 .

내 모의를 만드는 대신 모의 프레임 워크 (Mockito를 사용하고 있음)를 사용 하여이 작업을 수행 할 수 있습니까? 어떻게?


2
Mockito 현재 1.10.12 , Mockito 지원 직접 추상 클래스를 조롱 / 감시 :SomeAbstract spy = spy(SomeAbstract.class);
pesche

6
Mockito 2.7.14부터 다음을 통해 생성자 인수가 필요한 추상 클래스를 조롱 할 수도 있습니다.mock(MyAbstractClass.class, withSettings().useConstructor(arg1, arg2).defaultAnswer(CALLS_REAL_METHODS))
Gediminas Rimsa

답변:


315

다음 제안에서는 "실제"서브 클래스를 작성하지 않고 추상 클래스를 테스트 할 수 있습니다 . Mock 서브 클래스입니다.

을 사용한 Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS)다음 호출되는 추상 메소드를 조롱하십시오.

예:

public abstract class My {
  public Result methodUnderTest() { ... }
  protected abstract void methodIDontCareAbout();
}

public class MyTest {
    @Test
    public void shouldFailOnNullIdentifiers() {
        My my = Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS);
        Assert.assertSomething(my.methodUnderTest());
    }
}

참고 :이 솔루션의 장점은 결코 호출되지 않는 한 추상 메소드를 구현할 필요가 없다는 입니다.

정직한 견해로는 스파이가 인스턴스를 필요로하기 때문에 스파이를 사용하는 것보다 깔끔합니다. 즉, 추상 클래스의 인스턴스화 가능한 서브 클래스를 만들어야합니다.


14
아래에서 언급했듯이, 추상 클래스가 테스트를 위해 추상 메소드를 호출하면 작동하지 않습니다.
Richard Nichols

11
이것은 추상 클래스가 추상 메소드를 호출 할 때 실제로 작동합니다. 추상 메소드를 스텁 할 때 Mockito.do 대신 doReturn 또는 doNothing 구문을 사용하고 구체적인 호출을 스텁하는 경우 추상 호출을 먼저 스텁해야합니다.
Gonen I

2
이런 종류의 객체 (실제 메서드를 호출하는 모의 추상 클래스)에 종속성을 어떻게 주입 할 수 있습니까?
사무엘

2
문제의 클래스에 인스턴스 이니셜 라이저가있는 경우 예기치 않은 방식으로 작동합니다. Mockito는 목업의 이니셜 라이저를 건너 뜁니다. 즉, 인라인으로 초기화 된 인스턴스 변수가 예기치 않게 null이되어 NPE가 발생할 수 있습니다.
digitalbath

1
추상 클래스 생성자가 하나 이상의 매개 변수를 사용하면 어떻게됩니까?
SD

68

초록을 건드리지 않고 구체적인 방법 중 일부를 테스트 해야하는 경우 CALLS_REAL_METHODS( 모르 텐의 답변 참조)을 사용할 수 있지만 테스트중인 구체적인 방법이 초록 또는 구현되지 않은 인터페이스 방법 중 일부를 호출하면 작동하지 않습니다. -Mockito는 "자바 인터페이스에서 실제 메소드를 호출 할 수 없습니다"라고 불평합니다.

(예, 디자인이 거칠지 만 Tapestry 4와 같은 일부 프레임 워크는 사용자에게 영향을줍니다.)

해결 방법은이 접근 방식을 뒤집는 것입니다. 일반적인 모의 동작 (예 : 모든 모의 / 스터브)을 사용 doCallRealMethod()하고 테스트중인 구체적인 방법을 명시 적으로 호출하는 데 사용 합니다. 예 :

public abstract class MyClass {
    @SomeDependencyInjectionOrSomething
    public abstract MyDependency getDependency();

    public void myMethod() {
        MyDependency dep = getDependency();
        dep.doSomething();
    }
}

public class MyClassTest {
    @Test
    public void myMethodDoesSomethingWithDependency() {
        MyDependency theDependency = mock(MyDependency.class);

        MyClass myInstance = mock(MyClass.class);

        // can't do this with CALLS_REAL_METHODS
        when(myInstance.getDependency()).thenReturn(theDependency);

        doCallRealMethod().when(myInstance).myMethod();
        myInstance.myMethod();

        verify(theDependency, times(1)).doSomething();
    }
}

다음을 추가하도록 업데이트되었습니다.

무효가 아닌 방법의 경우 thenCallRealMethod()대신 다음 을 사용해야 합니다.

when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod();

그렇지 않으면 Mockito는 "완료되지 않은 스터 빙이 감지되었습니다"라고 불평합니다.


9
이것은 어떤 경우에는 작동하지만 Mockito는이 메소드를 사용하여 기본 추상 클래스의 생성자를 호출하지 않습니다. 예기치 않은 시나리오가 작성되어 "실제 방법"이 실패 할 수 있습니다. 따라서이 방법은 모든 경우에 작동하지 않습니다.
Richard Nichols

3
그러나 객체의 상태를 전혀 믿을 수 없으며 호출되는 메소드의 코드 만 믿을 수 있습니다.
David Moles

그래서 객체 메소드는 상태에서 분리됩니다.
haelix

17

스파이를 사용하여이를 달성 할 수 있습니다 (최신 버전의 Mockito 1.8 이상 사용).

public abstract class MyAbstract {
  public String concrete() {
    return abstractMethod();
  }
  public abstract String abstractMethod();
}

public class MyAbstractImpl extends MyAbstract {
  public String abstractMethod() {
    return null;
  }
}

// your test code below

MyAbstractImpl abstractImpl = spy(new MyAbstractImpl());
doReturn("Blah").when(abstractImpl).abstractMethod();
assertTrue("Blah".equals(abstractImpl.concrete()));

14

모의 프레임 워크는 테스트중인 클래스의 종속성을 더 쉽게 모의 할 수 있도록 설계되었습니다. 모의 프레임 워크를 사용하여 클래스를 모의하는 경우 대부분의 프레임 워크는 동적으로 서브 클래스를 만들고 메소드 구현을 코드 호출로 대체하여 메소드 호출시기 및 위조 값을 리턴합니다.

추상 클래스를 테스트 할 때 SUT (Subject Under Test)의 비추 상 메소드를 실행하려고하므로 조롱 프레임 워크가 원하는 것은 아닙니다.

혼란의 일부는 당신이 연결 한 질문에 대한 대답이 당신의 추상 클래스에서 확장되는 모의를 수공으로 만들었다는 것입니다. 나는 그런 클래스를 모의라고 부르지 않을 것입니다. 모의는 의존성을 대체하기 위해 사용되는 클래스로, 기대치로 프로그래밍되며, 이러한 기대치가 충족되는지를 쿼리 할 수 ​​있습니다.

대신 테스트에서 추상 클래스의 비 추상 서브 클래스를 정의하는 것이 좋습니다. 코드가 너무 많으면 클래스를 확장하기 어렵다는 신호일 수 있습니다.

대안 솔루션은 SUT 작성을위한 추상적 인 방법으로 테스트 케이스 자체를 추상적으로 만드는 것입니다 (즉, 테스트 케이스는 템플리트 메소드 디자인 패턴을 사용함 ).


8

맞춤 답변을 사용해보십시오.

예를 들면 다음과 같습니다.

import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class CustomAnswer implements Answer<Object> {

    public Object answer(InvocationOnMock invocation) throws Throwable {

        Answer<Object> answer = null;

        if (isAbstract(invocation.getMethod().getModifiers())) {

            answer = Mockito.RETURNS_DEFAULTS;

        } else {

            answer = Mockito.CALLS_REAL_METHODS;
        }

        return answer.answer(invocation);
    }
}

추상 메소드의 모의를 반환하고 구체적인 메소드의 실제 메소드를 호출합니다.


5

추상 클래스를 조롱하는 것에 대해 정말로 기분이 좋지 않은 것은 기본 생성자 YourAbstractClass ()가 호출되지 않거나 (mock에서 super () 누락) Mockito에서 모의 ​​속성을 기본으로 초기화하는 방법이없는 것입니다 (예 : List 속성) 빈 ArrayList 또는 LinkedList로).

내 추상 클래스 (기본적으로 클래스 소스 코드가 생성됨)는 목록 요소에 대한 종속성 설정 기 주입을 제공하지 않으며 목록 요소를 초기화하는 생성자 (수동으로 추가하려고 시도한 생성자)를 제공하지 않습니다.

클래스 속성 만 기본 초기화를 사용합니다. private List dep1 = new ArrayList; 개인 목록 dep2 = 새로운 ArrayList

따라서 실제 객체 구현 (예 : 단위 테스트 클래스의 내부 클래스 정의, 추상 메소드 재정의) 및 실제 객체 (적절한 필드 초기화 수행)를 사용하지 않고 추상 클래스를 조롱하는 방법은 없습니다.

PowerMock만이 여기에서 더 도움이된다는 것이 너무 나쁩니다.


2

테스트 클래스가 테스트중인 클래스와 동일한 패키지 (다른 소스 루트 아래)에 있다고 가정하면 모의 객체를 만들 수 있습니다.

YourClass yourObject = mock(YourClass.class);

다른 메소드와 마찬가지로 테스트하려는 메소드를 호출하십시오.

슈퍼 메소드를 호출하는 구체적인 메소드에 대한 기대와 함께 호출되는 각 메소드에 대한 기대치를 제공해야합니다 .Mockito로 어떻게 수행할지 확실하지 않지만 EasyMock으로 가능하다고 생각합니다.

이 모든 것은 구체적인 인스턴스를 생성하고 YouClass각 추상 메소드의 빈 구현을 제공하는 노력을 절약하는 것입니다.

옆으로, 나는 종종 테스트에서 추상 클래스를 구현하는 것이 유용하다는 것을 알았습니다. 추상 클래스가 제공하는 기능에 달려 있지만 공용 인터페이스를 통해 테스트하는 구현 예제입니다.


3
그러나 모의를 사용하여 YourClass의 구체적인 메소드를 테스트하지 않습니까, 아니면 잘못 되었습니까? 이것은 내가 추구하는 것이 아닙니다.
ripper234

1
맞습니다. 추상 클래스에서 구체적인 메소드를 호출하려는 경우 위의 내용이 작동하지 않습니다.
Richard Nichols

사과, 나는 추상에 대한 것이 아니라 호출하는 각 메소드에 필요한 기대에 대한 비트를 편집 할 것이다.
Nick Holt

그러나 구체적인 방법이 아닌 여전히 모의를 테스트하고 있습니다.
Jonatan Cloutier

2

테스트에서 익명 클래스를 사용하여 추상 클래스를 확장 할 수 있습니다. 예를 들어 (Junit 4 사용) :

private AbstractClassName classToTest;

@Before
public void preTestSetup()
{
    classToTest = new AbstractClassName() { };
}

// Test the AbstractClassName methods.

2

Mockito는 @Mock주석 을 사용하여 추상 클래스를 조롱 할 수 있습니다 .

public abstract class My {

    public abstract boolean myAbstractMethod();

    public void myNonAbstractMethod() {
        // ...
    }
}

@RunWith(MockitoJUnitRunner.class)
public class MyTest {

    @Mock(answer = Answers.CALLS_REAL_METHODS)
    private My my;

    @Test
    private void shouldPass() {
        BDDMockito.given(my.myAbstractMethod()).willReturn(true);
        my.myNonAbstractMethod();
        // ...
    }
}

단점은 생성자 매개 변수가 필요한 경우 사용할 수 없다는 것입니다.


0

익명 클래스를 인스턴스화하고 모형을 주입 한 다음 해당 클래스를 테스트 할 수 있습니다.

@RunWith(MockitoJUnitRunner.class)
public class ClassUnderTest_Test {

    private ClassUnderTest classUnderTest;

    @Mock
    MyDependencyService myDependencyService;

    @Before
    public void setUp() throws Exception {
        this.classUnderTest = getInstance();
    }

    private ClassUnderTest getInstance() {
        return new ClassUnderTest() {

            private ClassUnderTest init(
                    MyDependencyService myDependencyService
            ) {
                this.myDependencyService = myDependencyService;
                return this;
            }

            @Override
            protected void myMethodToTest() {
                return super.myMethodToTest();
            }
        }.init(myDependencyService);
    }
}

가시성은 추상 클래스 protected의 속성 myDependencyService에 대한 것이 어야합니다 ClassUnderTest.


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