추상 클래스를 테스트하고 싶습니다. 물론 클래스에서 상속받은 모형 을 수동으로 작성할 수 있습니다 .
내 모의를 만드는 대신 모의 프레임 워크 (Mockito를 사용하고 있음)를 사용 하여이 작업을 수행 할 수 있습니까? 어떻게?
mock(MyAbstractClass.class, withSettings().useConstructor(arg1, arg2).defaultAnswer(CALLS_REAL_METHODS))
추상 클래스를 테스트하고 싶습니다. 물론 클래스에서 상속받은 모형 을 수동으로 작성할 수 있습니다 .
내 모의를 만드는 대신 모의 프레임 워크 (Mockito를 사용하고 있음)를 사용 하여이 작업을 수행 할 수 있습니까? 어떻게?
mock(MyAbstractClass.class, withSettings().useConstructor(arg1, arg2).defaultAnswer(CALLS_REAL_METHODS))
답변:
다음 제안에서는 "실제"서브 클래스를 작성하지 않고 추상 클래스를 테스트 할 수 있습니다 . 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());
}
}
참고 :이 솔루션의 장점은 결코 호출되지 않는 한 추상 메소드를 구현할 필요가 없다는 것 입니다.
정직한 견해로는 스파이가 인스턴스를 필요로하기 때문에 스파이를 사용하는 것보다 깔끔합니다. 즉, 추상 클래스의 인스턴스화 가능한 서브 클래스를 만들어야합니다.
초록을 건드리지 않고 구체적인 방법 중 일부를 테스트 해야하는 경우 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는 "완료되지 않은 스터 빙이 감지되었습니다"라고 불평합니다.
스파이를 사용하여이를 달성 할 수 있습니다 (최신 버전의 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()));
모의 프레임 워크는 테스트중인 클래스의 종속성을 더 쉽게 모의 할 수 있도록 설계되었습니다. 모의 프레임 워크를 사용하여 클래스를 모의하는 경우 대부분의 프레임 워크는 동적으로 서브 클래스를 만들고 메소드 구현을 코드 호출로 대체하여 메소드 호출시기 및 위조 값을 리턴합니다.
추상 클래스를 테스트 할 때 SUT (Subject Under Test)의 비추 상 메소드를 실행하려고하므로 조롱 프레임 워크가 원하는 것은 아닙니다.
혼란의 일부는 당신이 연결 한 질문에 대한 대답이 당신의 추상 클래스에서 확장되는 모의를 수공으로 만들었다는 것입니다. 나는 그런 클래스를 모의라고 부르지 않을 것입니다. 모의는 의존성을 대체하기 위해 사용되는 클래스로, 기대치로 프로그래밍되며, 이러한 기대치가 충족되는지를 쿼리 할 수 있습니다.
대신 테스트에서 추상 클래스의 비 추상 서브 클래스를 정의하는 것이 좋습니다. 코드가 너무 많으면 클래스를 확장하기 어렵다는 신호일 수 있습니다.
대안 솔루션은 SUT 작성을위한 추상적 인 방법으로 테스트 케이스 자체를 추상적으로 만드는 것입니다 (즉, 테스트 케이스는 템플리트 메소드 디자인 패턴을 사용함 ).
맞춤 답변을 사용해보십시오.
예를 들면 다음과 같습니다.
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);
}
}
추상 메소드의 모의를 반환하고 구체적인 메소드의 실제 메소드를 호출합니다.
추상 클래스를 조롱하는 것에 대해 정말로 기분이 좋지 않은 것은 기본 생성자 YourAbstractClass ()가 호출되지 않거나 (mock에서 super () 누락) Mockito에서 모의 속성을 기본으로 초기화하는 방법이없는 것입니다 (예 : List 속성) 빈 ArrayList 또는 LinkedList로).
내 추상 클래스 (기본적으로 클래스 소스 코드가 생성됨)는 목록 요소에 대한 종속성 설정 기 주입을 제공하지 않으며 목록 요소를 초기화하는 생성자 (수동으로 추가하려고 시도한 생성자)를 제공하지 않습니다.
클래스 속성 만 기본 초기화를 사용합니다. private List dep1 = new ArrayList; 개인 목록 dep2 = 새로운 ArrayList
따라서 실제 객체 구현 (예 : 단위 테스트 클래스의 내부 클래스 정의, 추상 메소드 재정의) 및 실제 객체 (적절한 필드 초기화 수행)를 사용하지 않고 추상 클래스를 조롱하는 방법은 없습니다.
PowerMock만이 여기에서 더 도움이된다는 것이 너무 나쁩니다.
테스트 클래스가 테스트중인 클래스와 동일한 패키지 (다른 소스 루트 아래)에 있다고 가정하면 모의 객체를 만들 수 있습니다.
YourClass yourObject = mock(YourClass.class);
다른 메소드와 마찬가지로 테스트하려는 메소드를 호출하십시오.
슈퍼 메소드를 호출하는 구체적인 메소드에 대한 기대와 함께 호출되는 각 메소드에 대한 기대치를 제공해야합니다 .Mockito로 어떻게 수행할지 확실하지 않지만 EasyMock으로 가능하다고 생각합니다.
이 모든 것은 구체적인 인스턴스를 생성하고 YouClass
각 추상 메소드의 빈 구현을 제공하는 노력을 절약하는 것입니다.
옆으로, 나는 종종 테스트에서 추상 클래스를 구현하는 것이 유용하다는 것을 알았습니다. 추상 클래스가 제공하는 기능에 달려 있지만 공용 인터페이스를 통해 테스트하는 구현 예제입니다.
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();
// ...
}
}
단점은 생성자 매개 변수가 필요한 경우 사용할 수 없다는 것입니다.
익명 클래스를 인스턴스화하고 모형을 주입 한 다음 해당 클래스를 테스트 할 수 있습니다.
@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
.
SomeAbstract spy = spy(SomeAbstract.class);