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.