Java-Mock簡化單元測試

單元測試目的

維基百科對單元測試的定義:

單元測試(英語:Unit Testing)又稱爲模塊測試,是針對程序模塊(軟件設計的最小單位)來進行正確性檢驗的測試工做。程序單元是應用的最小可測試部件。在過程化編程中,一個單元就是單個程序、函數、過程等;對於面向對象編程,最小單元就是方法,包括基類(超類)、抽象類、或者派生類(子類)中的方法。

單元測試的目標是隔離程序部件並證實這些單個部件是正確的。
  • 畫外音:單元測試是比較細粒度的測試,是對接口、方法、函數的測試,目的是保障代碼按照正確的方式去執行,提升代碼質量。

單元測試實施原則

Mock脫離數據庫 + 不啓動Spring + 優化測試速度 + 不引入項目組件數據庫

單元測試不該該依賴數據,依賴外部服務或組件等,會對其餘數據產生影響的狀況。啓動Spring容器,通常比較慢,可能會啓動消息監聽消費消息,定時任務的執行等,對數據產生影響。編程

Mock測試就是在測試過程當中,對那些當前測試不關心的,不容易構建的對象,用一個虛擬對象來代替測試的情形。網絡

說白了:就是解耦(虛擬化)要測試的目標方法中調用的其它方法,例如:Service的方法調用Mapper類的方法,這時候就要把Mapper類Mock掉(產生一個虛擬對象),這樣咱們能夠自由的控制這個Mapper類中的方法,讓它們返回想要的結果、拋出指定異常、驗證方法的調用次數等等。app

減小單元測試對外部的依賴和反作用,提升單元測試效率函數

  1. 不使用 @Autowired,@Resource, 須要啓動 Spring 容器,測試速度慢,會產生反作用;
  2. 不使用 @SpringBootTest,@SpringBootTest(classes = Application.class), 這會啓動整個 SpringBoot 服務
  3. 不該調用數據庫,除非是作數據庫操做相關的測試,雖然可配置事務回滾,但大多數狀況下仍是會產生髒數據等問題
  4. 使用Assert斷言,用於判斷某個特定條件下某個方法的行爲,爲了證實某段代碼的執行結果和指望的一致
  • 畫外音:單元測試應小而輕,提交測試效率,較少對外部的依賴,好比數據庫、Spring容器、網絡服務等,而只關心咱們本身的代碼,經過Mock來解決對外部的依賴

Mockito的使用

基本使用

  1. 使用靜態方法 mock()
  2. 使用註解 @Mock 標註

若是使用@Mock註解, 必須去觸發所標註對象的建立. 可使用 MockitoRule來實現. 它調用了靜態方法MockitoAnnotations.initMocks(this) 去初始化這個被註解標註的字段.或者也可使用@RunWith(MockitoJUnitRunner.class).單元測試

「when thenReturn」和」when thenThrow」
模擬對象能夠根據傳入方法中的參數來返回不一樣的值, when(….).thenReturn(….)方法是用來根據特定的參數來返回特定的值.測試

咱們也可使用像 anyString 或者 anyInt anyLong any 這樣的方法來定義某個依賴數據類型的方法返回特定的值.優化

「doReturn when」 和 「doThrow when」
doReturn(…).when(…)的方法調用和when(….).thenReturn(….)相似.對於調用過程當中拋出的異常很是有用.而doThrow則也是它的一個變體.this

經常使用註解

@Mock:對函數的調用均執行mock(即虛假函數),不執行真正部分。設計

@Spy:對函數的調用均執行真正部分。

@InjectMocks:建立一個實例,簡單的說是這個Mock能夠調用真實代碼的方法,使用@Mock(或@Spy)註解建立的mock將被注入到用該實例中。

Mockito中的Mock和Spy均可用於攔截那些還沒有實現或不指望被真實調用的對象和方法,併爲其設置自定義行爲。兩者的區別在於Mock不真實調用,Spy會真實調用。

@MockBean: 功能同 @Mock, 只是會將實例放入 Spring 容器管理

@SpyBean: 功能同 @Spy, 只是會將實例放入 Spring 容器管理

  1. Spy 和 Mock 生成的對象不受 Spring 管理
  2. Spy 調用真實方法時,其它 bean 是沒法注入的,要使用注入,要使用 SpyBean
  3. SpyBean 和 MockBean 生成的對象受 Spring 管理,至關於自動替換對應類型 bean 的注入,好比 @Autowired、@Resource 等注入

最佳實踐

// 不使用 @SpringBootTest(classes = Application.class)
@RunWith(SpringRunner.class)
public class ExamAnswerComponentTest {

    // 建立一個實例,會注入Mock變量
    @InjectMocks
    private ExamAnswerComponent examAnswerComponent = new ExamAnswerComponentImpl();

    // 相關操做會被Mock掉
    @Mock
    private ExamAnswerCacheObjectiveDAO examAnswerCacheObjectiveDAO;

    @Before
    public void setUp() {
        // 初始化Mock
        MockitoAnnotations.initMocks(this);

        // given...willReturn 指定方法參數,模擬返回值
        given(examAnswerCacheObjectiveDAO.selectByBizIdAndPaperAndQuestion(any(), any(), any()))
                .willReturn(new ExamAnswerCacheObjectivePO());
        given(examAnswerCacheObjectiveDAO.insert(any())).willReturn(1);
        given(examAnswerCacheObjectiveDAO.updateUserAnswerById(any(), any())).willReturn(1);
    }


    @Test
    public void saveOrUpdateAnswerCacheObjective() {

        ExamAnswerCacheObjectivePO po = new ExamAnswerCacheObjectivePO();
        po.setBizId(100000015L);
        po.setBizType(9);
        po.setUserAnswer("A");
        po.setGroupPaperId(1000320L);
        po.setQuestionId(1000042L);
        po.setQuestionType(1);

        int affect = examAnswerComponent.saveOrUpdateAnswerCacheObjective(po);
        System.out.println("affect = " + affect);
        Assert.assertTrue(affect > 0);
    }

}

參考

https://www.codenong.com/cs10...

相關文章
相關標籤/搜索