抽象クラスをテストしたいと思います。確かに、クラスから継承するモックを手動で作成できます。
モックを手作りする代わりに、モッキング フレームワーク (私は Mockito を使用しています) を使用してこれを行うことはできますか? どのように?
抽象クラスをテストしたいと思います。確かに、クラスから継承するモックを手動で作成できます。
モックを手作りする代わりに、モッキング フレームワーク (私は Mockito を使用しています) を使用してこれを行うことはできますか? どのように?
次の提案では、「実際の」サブクラスを作成せずに抽象クラスをテストできます。モックはサブクラスです。
を使用し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
( Morten の回答を参照)。 -- Mockito は「Java インターフェースで実際のメソッドを呼び出せません」と文句を言うでしょう。
(はい、これはお粗末な設計ですが、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();
}
}
追加するために更新されました:
非 void メソッドの場合は、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()));
モッキング フレームワークは、テストしているクラスの依存関係を簡単にモックできるように設計されています。モッキング フレームワークを使用してクラスをモックする場合、ほとんどのフレームワークはサブクラスを動的に作成し、メソッドの実装を、メソッドが呼び出されたときに検出して偽の値を返すコードに置き換えます。
抽象クラスをテストするときは、Subject Under Test (SUT) の非抽象メソッドを実行する必要があるため、モッキング フレームワークは必要ありません。
混乱の一部は、あなたがリンクした質問への答えが、抽象クラスから拡張されたモックを手作りすると言ったことです。私はそのようなクラスをモックとは呼びません。モックは、依存関係の代わりとして使用されるクラスであり、期待値でプログラムされ、それらの期待値が満たされているかどうかを確認するために照会できます。
代わりに、テストで抽象クラスの非抽象サブクラスを定義することをお勧めします。その結果コードが多すぎる場合は、クラスを拡張するのが難しいという兆候である可能性があります。
別の解決策は、SUT を作成するための抽象メソッドを使用して、テスト ケース自体を抽象化することです (つまり、テスト ケースはTemplate Methodデザイン パターンを使用します)。
カスタム回答を使用してみてください。
例えば:
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()
が呼び出されない (super()
モックにない) ことも、Mockito でモック プロパティをデフォルトで初期化する方法もないように思われることです (たとえばList
、空ArrayList
またはのプロパティLinkedList
)。
私の抽象クラス(基本的にクラスのソースコードが生成されます)は、リスト要素の依存関係セッター注入も、リスト要素を初期化するコンストラクターも提供しません(手動で追加しようとしました)。
クラス属性のみがデフォルトの初期化を使用します。
private List<MyGenType> dep1 = new ArrayList<MyGenType>();
private List<MyGenType> dep2 = new ArrayList<MyGenType>();
したがって、実際のオブジェクトの実装 (ユニット テスト クラスの内部クラス定義、抽象メソッドのオーバーライドなど) を使用せずに抽象クラスをモックする方法はなく、実際のオブジェクトをスパイします (適切なフィールドの初期化を行います)。
PowerMockだけがここでさらに役立つのは残念です。
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();
// ...
}
}
欠点は、コンストラクターのパラメーターが必要な場合に使用できないことです。
テストで無名クラスを使用して抽象クラスを拡張できます。例 (Junit 4 を使用):
private AbstractClassName classToTest;
@Before
public void preTestSetup()
{
classToTest = new AbstractClassName() { };
}
// Test the AbstractClassName methods.
テスト クラスがテスト対象のクラスと同じパッケージ (異なるソース ルートの下) にあると仮定すると、単純にモックを作成できます。
YourClass yourObject = mock(YourClass.class);
他のメソッドと同じように、テストするメソッドを呼び出します。
スーパーメソッドを呼び出す具体的なメソッドを期待して、呼び出される各メソッドに期待値を提供する必要があります.Mockitoでそれを行う方法はわかりませんが、EasyMockでは可能だと思います.
これが行っているのは、の具体的なインスタンスを作成し、YouClass
各抽象メソッドの空の実装を提供する労力を節約することだけです。
余談ですが、テストで抽象クラスを実装すると便利なことがよくあります。これは、抽象クラスによって提供される機能に依存しますが、パブリック インターフェイスを介してテストする実装例として機能します。