Sprigboot+Junit+Mockitoによるユニットテストの例


前言
この文章はSpringboot+Junnit+Mockitoを使ってセルテストをする方法を紹介します。ケースは取引の種類を選んでユニットテストをします。
ユニットテスト前に需要を理解する
良いテストを書くには、まず需要を理解しなければなりません。何をすれば測定が分かりますか?しかし、本文は主にmockitoの使い方を話しています。具体的な需要に注目する必要はありません。したがって、この節は具体的な需要説明を省略する。
外部依存を遮断する
Case 1.被測定類に@Autowiredまたは@Resource注で表示されている依存対象は、戻り値をどのように制御しますか?
測定方法Match ingServiceImpl.javaのmatchingを例にとって
被測定類Match ingServiceImpl

public class MatchingServiceImpl implements MatchingService {
  private static final Logger log = LoggerFactory.getLogger(MatchingServiceImpl.class);
  @Autowired
  private QuoteService quoteService;
  ...
  public MatchingResult matching(MatchingOrder buyOrder, MatchingOrder sellOrder) {
    int currentPrice = quoteService.getCurrentPriceByProduct(buyOrder.getProductCode());
    MatchingResult result = new MatchingResult();
    if (sellOrder != null && buyOrder != null &&
        sellOrder.getPrice() <= buyOrder.getPrice()) {
    ...
  }
}
matching方法における「teService.get Currenent PriceByProduct」;Redisにアクセスして現在のオファーを取得したいです。ここでは外部依存の「Service mock」を落として、get Current PriceByProduct方法の戻り値を制御します。mockitoを使うとできます。具体的には以下の通りです。
テストクラスMatch ServiceImplTest

public class MatchingServiceImplTest extends MockitoBasedTest {
  /**
   *  @Mock            @InjectMocks      
   */
  @Mock
  private QuoteService quoteService;
  /**
   * <pre>
   *     , @InjectMocks  ,   @mock             。
   *           MatchingServiceImpl   new  (Mockito 1.9    new   ),     spring     。         spring   
   *       ,   database ,redis ,zookeeper,mq,     ,    new
   * </pre>
   */
  @InjectMocks
  private MatchingServiceImpl matchingService = new MatchingServiceImpl();
  @Test
  public void testMatching_SuccessWhenCurrentPriceBetweenBuyPriceAndSellPrice() {
    MatchingOrder buyOrder = new MatchingOrder();
    buyOrder.setPrice(1000);
    buyOrder.setCount(23);
    MatchingOrder sellOrder = new MatchingOrder();
    sellOrder.setPrice(800);
    sellOrder.setCount(20);
    //     (Method stubbing)
    // when(x).thenReturn(y) :              
    Mockito.when(quoteService.getCurrentPriceByProduct(Mockito.anyString())).thenReturn(900);
    MatchingResult result = matchingService.matching(buyOrder, sellOrder);
    org.junit.Assert.assertEquals(true, result.isSuccess());//         
    org.junit.Assert.assertEquals(20, result.getTradeCount());//       
    org.junit.Assert.assertEquals(900, result.getTradePrice()); //             
  }
Case 2.被測定関数Aは、測定されたクラスの他の関数Bを呼び出して、関数Bの戻り値をどのように制御しますか?
例えば、Match ingServiceImplにはstartBuyProcessという関数がありますが、その中にはこのクラスの他の関数が呼び出されています。例えば、getTopSellOrder、matchingは、この2つの関数の戻り値をどのように制御しますか?
ここで解決したい問題は、どのように一つのクラスの「部分mock」Cの測定方法(例えば、startByProcess)を実行しますか?他の方法(例えば、get TopSellOrder)は杭を打ちます。
被測定類Match ingServiceImpl

protected void startBuyProcess(MatchingOrder buyOrder, boolean waitForMatching) {
    while (true) {
      //      
      MatchingOrder topSellOrder = getTopSellOrder(buyOrder.getProductCode());
      MatchingResult matchingResult = matching(buyOrder,topSellOrder);
      if(matchingResult.isSuccess()) {
        doMatchingSuccess(buyOrder,topSellOrder,matchingResult,MatchingType.BUY);
        if(buyOrder.getCount() <= 0) {
          break;
        }
      }else {
        if(waitForMatching) {
          //       
          addToMatchingBuy(buyOrder);
        }else {
          //  
          sendCancleMsg(buyOrder);
        }
        break;
      }
    }
  }
Mockito.spyを利用して「部分的なMock」ができます。
テストクラスMatch ServiceImplTest.testStartBuy Process_InCaseOfMatch Success

/**
   *
   *   StartBuyProcess                 ,   startBuyProcess                
   * {@link MatchingServiceImpl#startBuyProcess(MatchingOrder, boolean)}
   *
   * <pre>
   * if (matchingResult.isSuccess()) {
   *
   *   doMatchingSuccess(buyOrder, topSellOrder, matchingResult, MatchingType.BUY);
   *
   *   if (buyOrder.getCount() &lt;= 0) {
   *     break;
   *   }
   * }
   * </pre>
   *
   */
  @Test
  public void testStartBuyProcess_InCaseOfMatchingSuccess() {
    MatchingOrder buyOrder = new MatchingOrder();
    buyOrder.setPrice(700);
    buyOrder.setCount(23);
    //  Mockito.spy() matchingService      
    matchingService = Mockito.spy(matchingService);
    MatchingResult firstMatchingResult = new MatchingResult();
    firstMatchingResult.setSuccess(true);
    firstMatchingResult.setTradeCount(20);
    MatchingResult secondMatchingResult = new MatchingResult();
    secondMatchingResult.setSuccess(false);
    // doReturn(x).when(obj).method()      ,   ,                   ,            
    //   doReturn       matchingService.matching   firstMatchingResult,       secondMatchingResult
    //   startBuyProcess   while  ,       matching  
    Mockito.doReturn(firstMatchingResult).doReturn(secondMatchingResult).when(matchingService)
        .matching(Mockito.any(MatchingOrder.class), Mockito.any(MatchingOrder.class));
    MatchingOrder sellOrder = new MatchingOrder();
    sellOrder.setPrice(600);
    sellOrder.setCount(20);
    //  getTopSellOrder    
    Mockito.doReturn(sellOrder).when(matchingService).getTopSellOrder(Mockito.anyString());
    //      jedis       
    Mockito.when(jedisClient.incrBy(Mockito.anyString(), Mockito.anyLong())).thenReturn(0L);
    // startBuyProcess     ,   ,     
    matchingService.startBuyProcess(buyOrder, true);
    //            doMatchingSuccess      ,          
    // verify     ,             ,     jedisClient.zremFirst      1 
    Mockito.verify(jedisClient, Mockito.times(1)).zremFirst(Mockito.anyString());
    org.junit.Assert.assertEquals(3, buyOrder.getCount());
    org.junit.Assert.assertEquals(0, sellOrder.getCount());
  }
spyの使い方はもうデモンストレーション済みです。以下はtestStartBuy Processから。InCaseOfMatch Successはユニットテストの「粒度」を言います。
testStartBuyProcess_InCaseOfMatch Successの目的はdoMatch ingSuccessを測定することです。私たちは前の準備を全部終わらせてから、doMatch Successを測定することができます。
もっと良い実践は別のテスト方法で単独でdoMatch Successを測定するべきです。注目点も多く集中しています。doMatch Successがカバーされました。startBuyProcessは実際にはそれ自体の判断分岐をカバーするだけでいいです。カバー率は同じです。テストコードも維持しやすいです。testStartBuy Process。InCaseOfMatch Successは考えすぎる職責のため、変化の影響を受けやすく、細かいものが変わると、正常な仕事に影響を与える可能性があります。
テストフレーム導入のMaven依存

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.11</version>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-all</artifactId>
  <version>1.10.19</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-test</artifactId>
  <version>4.2.5.RELEASE</version>
  <scope>test</scope>
</dependency>
springboot+junnit+mockitoのコンテキスト構築
Mockito BasedTest

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestApplication.class)
public abstract class MockitoBasedTest {
  @Before
  public void setUp() throws Exception {
    //           Mockito            
    MockitoAnnotations.initMocks(this);
  }
}
//        MockitoBasedTest
以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。