1. 程式人生 > 其它 >Java模擬框架Mockito簡單使用

Java模擬框架Mockito簡單使用

日常的開發中,習慣性地寫完需求程式碼後,嗖的一聲執行一個main函式或寫幾個簡單的JUnit的單元測試來跑功能點,多寫幾個單元測試過沒有問題就可以上線了(其實這樣是不規範的),對於需要對接第三方或者驗證不同條件的程式碼分支邏輯時,這種方法就會變得不可取,因為業務邏輯中需要依賴其他的介面,而這時候所依賴的介面還沒有準備好,那應該怎麼辦呢?

這時候該Mockito派上用場了,一方面使用Mockito可以遮蔽依賴介面並返回Mock資料,使得雙方的開發得以同步進行(確定介面的協議)編碼,另一方面使用Mockito驗證業務邏輯,當日後更改到某處程式碼即可迴歸測試用例看改動是否覆蓋到所有的測試點,因此使用Mockito不單單能保證程式碼的質量,更能提高程式碼維護性、提前發現程式碼的bug。

Mock四要素

  • 什麼是Mock

  • 為什麼需要Mock

Mock是為了解決units、程式碼分層開發之間由於耦合而難於被測試的問題,所以mock object是單元測試的一部分

  • Mock的好處是什麼

提前建立測試,提高程式碼質量、TDD(測試驅動開發)

  • 並行工作

建立一個驗證或者演示程式,為無法訪問的資源編寫測試

什麼是Mockito

Mockito是一個非常優秀的模擬框架,可以使用它簡潔的API來編寫漂亮的測試程式碼,它的測試程式碼可讀性高同時會產生清晰的錯誤日誌。

使用Mockito

Mockito官網

1、引入依賴

<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.10.19</version>
    <scope>test</scope>
</dependency>

2、示例程式碼

import com.xxx.common.redis.service.RedisService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;

import java.util.LinkedList;
import java.util.List;

/**
 * @version 1.0
 * @date 2022/5/26 9:58
 * @since : JDK 11
 */
@RunWith(MockitoJUnitRunner.class)
public class MockTest extends Mockito {

    // 模擬物件
    @Mock
    private RedisService redisService;

    // 驗證物件行為Verify
    @Test
    public void testVerify() {
        // 可以mock介面
        List mock = mock(List.class);
        mock.add("1");
        mock.clear();
        // 驗證mock物件前面已經呼叫過add操作
        verify(mock).add("1");
        // 驗證mock呼叫過clear操作
        verify(mock).clear();
        // 使用內建anyInt()引數匹配器,並存根
        when(mock.get(anyInt())).thenReturn("element");
        System.out.println(mock.get(2));
        // 此處輸出為element
        verify(mock).get(anyInt());
    }

    // 存根—stubbing
    // stubbing完全是模擬一個外部依賴,用來提供測試時所需要的測試資料
    // 存根(stub)可以覆蓋:例如測試方法可以覆蓋通用存,一旦做了存根方法將總是返回存根的值,無論這個方法被呼叫多少次
    @Test
    public void testStub() {
        // 可以mock具體的類,而不僅僅是介面
        LinkedList mockedList = mock(LinkedList.class);
        // 存根(stubbing)
        when(mockedList.get(0)).thenReturn("first");
        when(mockedList.get(1)).thenThrow(new RuntimeException());
        // 下面會列印 "first"
        System.out.println(mockedList.get(0));
        // 下面會丟擲執行時異常
        System.out.println(mockedList.get(1));
        // 下面會列印"null" 因為get(999)沒有存根(stub)
        System.out.println(mockedList.get(999));
        doThrow(new RuntimeException()).when(mockedList).clear();
        // 下面會丟擲 RuntimeException:
        mockedList.clear();
    }

    // 存根的連續呼叫
    @Test
    public void testStub1() {
        LinkedList mockedList = mock(LinkedList.class);
        when(mockedList.get(0)).thenThrow(new RuntimeException()).thenReturn("這是返回值");
        try {
            //第一次呼叫:丟擲執行時異常
            mockedList.get(0);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 第二次呼叫: 列印 "foo"
        System.out.println(mockedList.get(0));
        // 任何連續呼叫: 還是列印 "foo" (最後的存根生效).
        System.out.println(mockedList.get(0));
        // 可供選擇的連續存根的更短版本:
        when(mockedList.get(0)).thenReturn(1, 2, 3, 4, 5, 6, 7, 8, 9);
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        when(mockedList.get(anyInt())).thenAnswer(new Answer() {
            public Object answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                Object mock = invocation.getMock();
                return "當呼叫時返回呼叫方法的第一個引數:" + args[0];
            }
        });
        // 當呼叫時返回呼叫方法的第一個引數:1
        System.out.println(mockedList.get(1));

    }

    // 引數匹配
    @Test
    public void testArugument() {
        LinkedList mockedList = mock(LinkedList.class);
        //使用內建anyInt()引數匹配器
        when(mockedList.get(anyInt())).thenReturn("element");
        // 列印 "element"
        System.out.println(mockedList.get(999));
        // 同樣可以用引數匹配器做驗證
        verify(mockedList).get(anyInt());
        // 注意:如果使用引數匹配器,所有的引數都必須通過匹配器提供。
        // 正確 使用引數匹配器匹配引數
        verify(mockedList).get(anyInt());
        // 正確 使用了已經產生互動的模擬資料
        verify(mockedList).get(999);
        // 驗證之前mockedList是否執行過get(0)的操作
        verify(mockedList).get(0);
    }

    // 驗證方法精確呼叫次數/至少X次/從不
    @Test
    public void testVerify1() {
        List mockedList = mock(List.class);
        mockedList.add(1);
        mockedList.add(2);
        mockedList.add(2);
        mockedList.add(3);
        mockedList.add(3);
        mockedList.add(3);
        //下面兩個驗證是等同的 - 預設使用times(1)
        verify(mockedList).add(1);
        verify(mockedList, times(1)).add(1);
        verify(mockedList, times(2)).add(2);
        verify(mockedList, times(3)).add(3);
        // 使用never()來驗證從未執行過的操作,never()相當於times(0)
        verify(mockedList, never()).add("never happened");

        // 使用 atLeast()/atMost()來驗證
        // 最後一次執行的add(3)
        verify(mockedList, atLeastOnce()).add(3);
        // 最少執行2次add(2)
        verify(mockedList, atLeast(2)).add(2);
        // 最多執行5次add(2)
        verify(mockedList, atMost(5)).add(2);
    }

    // 驗證呼叫順序
    // 單個Mock,方法必須以特定順序呼叫
    @Test
    public void testOrder() {
        List mockedList = mock(List.class);
        mockedList.add("was added first");
        mockedList.add("was added second");
        // 為singleMock建立 inOrder 檢驗器
        InOrder inOrder = inOrder(mockedList);
        // 確保add方法第一次呼叫是用"was added first",然後是用"was added
        // second"
        inOrder.verify(mockedList).add("was added first");
        inOrder.verify(mockedList).add("was added second");
    }
}