Mockito-スパイがオブジェクトの実際のメソッドを呼び出すのに対し、モックはdoubleオブジェクトのメソッドを呼び出すことを理解しています。また、コードの臭いがない限り、スパイは避けてください。しかし、スパイはどのように機能し、いつ実際に使用する必要がありますか?それらはモックとどう違うのですか?
Mockito-スパイがオブジェクトの実際のメソッドを呼び出すのに対し、モックはdoubleオブジェクトのメソッドを呼び出すことを理解しています。また、コードの臭いがない限り、スパイは避けてください。しかし、スパイはどのように機能し、いつ実際に使用する必要がありますか?それらはモックとどう違うのですか?
回答:
技術的に言えば、「モック」と「スパイ」はどちらも特別な種類の「テストダブル」です。
Mockitoは残念ながらその区別を奇妙にしています。
mockitoのモックは、他のモックフレームワークの通常のモックです(呼び出しをスタブ化できます。つまり、メソッド呼び出しから特定の値を返します)。
mockitoのスパイは、他のモックフレームワークの部分的なモックです(オブジェクトの一部はモックされ、一部は実際のメソッド呼び出しを使用します)。
どちらもメソッドまたはフィールドのモックに使用できます。違いは、モックでは、スパイ中に完全なモックまたは偽のオブジェクトを作成し、実際のオブジェクトがあり、その特定のメソッドをスパイまたはスタブするだけであるということです。
もちろん、スパイオブジェクト内では、これは実際のメソッドであるため、メソッドをスタブしていない場合は、実際のメソッドの動作を呼び出します。メソッドを変更してモックしたい場合は、スタブする必要があります。
比較として、以下の例を検討してください。
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class MockSpy {
@Mock
private List<String> mockList;
@Spy
private List<String> spyList = new ArrayList();
@Test
public void testMockList() {
//by default, calling the methods of mock object will do nothing
mockList.add("test");
Mockito.verify(mockList).add("test");
assertEquals(0, mockList.size());
assertNull(mockList.get(0));
}
@Test
public void testSpyList() {
//spy object will call the real method when not stub
spyList.add("test");
Mockito.verify(spyList).add("test");
assertEquals(1, spyList.size());
assertEquals("test", spyList.get(0));
}
@Test
public void testMockWithStub() {
//try stubbing a method
String expected = "Mock 100";
when(mockList.get(100)).thenReturn(expected);
assertEquals(expected, mockList.get(100));
}
@Test
public void testSpyWithStub() {
//stubbing a spy method will result the same as the mock object
String expected = "Spy 100";
//take note of using doReturn instead of when
doReturn(expected).when(spyList).get(100);
assertEquals(expected, spyList.get(100));
}
}
モックやスパイを使用する必要がありますか?安全を確保し、外部サービスの呼び出しを避け、ユニット内のロジックをテストしたい場合は、モックを使用してください。外部サービスを呼び出して実際の依存関係の呼び出しを実行する場合、または単にプログラムをそのまま実行し、特定のメソッドをスタブするだけの場合は、spyを使用します。これが、mockitoのスパイとモックの違いです。
ここで実行可能な例を作成しましたhttps://www.surasint.com/mockito-with-spy/
その一部をここにコピーします。
このコードのようなものがある場合:
public void transfer( DepositMoneyService depositMoneyService,
WithdrawMoneyService withdrawMoneyService,
double amount, String fromAccount, String toAccount) {
withdrawMoneyService.withdraw(fromAccount,amount);
depositMoneyService.deposit(toAccount,amount);
}
DepositMoneyServiceとWithdrawMoneyServiceをモックするだけなので、スパイは必要ないかもしれません。
しかし、一部のレガシーコードでは、依存関係は次のようなコードにあります。
public void transfer(String fromAccount, String toAccount, double amount) {
this.depositeMoneyService = new DepositMoneyService();
this.withdrawMoneyService = new WithdrawMoneyService();
withdrawMoneyService.withdraw(fromAccount,amount);
depositeMoneyService.deposit(toAccount,amount);
}
はい、最初のコードに変更できますが、その後APIが変更されます。この方法が多くの場所で使用されている場合は、すべてを変更する必要があります。
別の方法は、次のように依存関係を抽出できることです。
public void transfer(String fromAccount, String toAccount, double amount){
this.depositeMoneyService = proxyDepositMoneyServiceCreator();
this.withdrawMoneyService = proxyWithdrawMoneyServiceCreator();
withdrawMoneyService.withdraw(fromAccount,amount);
depositeMoneyService.deposit(toAccount,amount);
}
DepositMoneyService proxyDepositMoneyServiceCreator() {
return new DepositMoneyService();
}
WithdrawMoneyService proxyWithdrawMoneyServiceCreator() {
return new WithdrawMoneyService();
}
次に、スパイを使用して、次のように依存関係を挿入できます。
DepositMoneyService mockDepositMoneyService = mock(DepositMoneyService.class);
WithdrawMoneyService mockWithdrawMoneyService = mock(WithdrawMoneyService.class);
TransferMoneyService target = spy(new TransferMoneyService());
doReturn(mockDepositMoneyService)
.when(target)
.proxyDepositMoneyServiceCreator();
doReturn(mockWithdrawMoneyService)
.when(target)
.proxyWithdrawMoneyServiceCreator();
上記のリンクで詳細をご覧ください。
開始するのに最適な場所は、おそらくmockitoのドキュメントです。
一般的な注意点として、mockitoモックではスタブを作成できます。
たとえば、スタブメソッドがコストのかかる操作を行う場合は、そのメソッドを作成します。たとえば、データベース接続を取得し、データベースから値を取得して、呼び出し元に返します。データベース接続の取得には30秒かかる場合があり、コンテキストが切り替わる(またはテストの実行を停止する)可能性が高くなるまでテストの実行が遅くなります。
テストしているロジックがデータベース接続を気にしない場合は、そのメソッドをハードコードされた値を返すスタブに置き換えることができます。
mockitoスパイを使用すると、メソッドが他のメソッドを呼び出すかどうかを確認できます。これは、レガシーコードをテスト対象にするときに非常に役立ちます。
副作用を克服する方法をテストしている場合は、モッキートスパイを使用すると便利です。これにより、実際のオブジェクトへの呼び出しが委任され、メソッドの呼び出し、呼び出された回数などを確認できます。
要するに:
@Spy
と@Mock
コードのテストで頻繁に使用されますが、開発者はそれらの1つを使用する場合に混乱するため、開発者は@Mock
安全のために使用することになります。
@Mock
、外部で機能をテストする場合に使用します。@Spy
機能をテストする場合に使用します。以下は、私がアメリカでElection20xxのシナリオをとった例です。
有権者は、に応じて分割することが可能VotersOfBelow21
とVotersOfABove21
。
理想的な出口調査では、トランプが選挙に勝つと言われています。なぜならVotersOfBelow21
、VotersOfABove21
両方が「トランプ大統領を選出した」と言ってトランプに投票するからです。
しかし、これは実際のシナリオではありません。
両方の年齢層の有権者は、トランプ氏以外に効果的な選択肢がなかったため、トランプに投票しました。
では、どのようにテストしますか?
public class VotersOfAbove21 {
public void weElected(String myVote){
System.out.println("Voters of above 21 has no Choice Than Thrump in 20XX ");
}
}
public class VotersOfBelow21 {
public void weElected(String myVote){
System.out.println("Voters of below 21 has no Choice Than Thrump in 20XX");
}
}
public class ElectionOfYear20XX {
VotersOfAbove21 votersOfAbove21;
VotersOfBelow21 votersOfBelow21;
public boolean weElected(String WeElectedTrump){
votersOfAbove21.weElected(WeElectedTrump);
System.out.println("We elected President Trump ");
votersOfBelow21.weElected(WeElectedTrump);
System.out.println("We elected President Trump ");
return true;
}
}
さて、上記の最初の2つのクラスで、両方の年齢層の人々が、トランプよりも良い選択はないと言っていることに注意してください。これは明らかに、彼らが選択の余地がなかったという理由だけでトランプに投票したことを意味します。
今、ElectionOfYear20XX
両方の年齢層が圧倒的に彼に投票したので、トランプが勝ったと言います。
ElectionOfYear20XX
@Mockでをテストする場合、トランプが勝った本当の理由を取得できない可能性があります。外部の理由をテストするだけです。
ElectionOfYear20XX
@Spyでテストすると、トランプが外部出口調査の結果、つまり内部+外部で勝った本当の理由がわかります。
私たちのELectionOfYear20XX_Test
クラス:
@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {
@Mock
VotersOfBelow21 votersOfBelow21;
@Mock
VotersOfAbove21 votersOfAbove21;
@InjectMocks
ElectionOfYear20XX electionOfYear20XX;
@Test
public void testElectionResults(){
Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
}
}
これにより、ロジックテストの結果、つまり外部チェックのみが出力されます。
We elected President Trump
We elected President Trump
@Spy
実際のメソッド呼び出しを使用して、外部および内部でテストします。
@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {
@Spy
VotersOfBelow21 votersOfBelow21;
@Spy
VotersOfAbove21 votersOfAbove21;
@InjectMocks
ElectionOfYear20XX electionOfYear20XX;
@Test
public void testElectionResults(){
Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
}
}
出力:
Voters of above 21 has no Choice Than Thrump in 20XX
We elected President Trump
Voters of below 21 has no Choice Than Thrump in 20XX
We elected President Trump
私はこの推奨事項の単純さが好きです:
- 安全を確保し、外部サービスの呼び出しを避け、ユニット内のロジックをテストしたい場合は、モックを使用してください。
- 外部サービスを呼び出して実際の依存関係の呼び出しを実行する場合、または単にプログラムをそのまま実行し、特定のメソッドをスタブするだけの場合は、spyを使用します。
ソース:https://javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/
一般的な違いは次のとおりです。
- 依存関係のメソッドを直接スタブ化する場合は、その依存関係をモックします。
- すべてのメソッドが必要なテスト値を返すように依存関係のデータをスタブ化する場合は、その依存関係をスパイします。