Java-Mock簡(jiǎn)化單元測(cè)試

373814.jpg

單元測(cè)試目的

維基百科對(duì)單元測(cè)試的定義:

單元測(cè)試(英語(yǔ):Unit Testing)又稱為模塊測(cè)試,是針對(duì)程序模塊(軟件設(shè)計(jì)的最小單位)來(lái)進(jìn)行正確性檢驗(yàn)的測(cè)試工作。程序單元是應(yīng)用的最小可測(cè)試部件。在過(guò)程化編程中,一個(gè)單元就是單個(gè)程序、函數(shù)、過(guò)程等;對(duì)于面向?qū)ο缶幊蹋钚卧褪欠椒?,包括基類(超類)、抽象類、或者派生類(子類)中的方法?
單元測(cè)試的目標(biāo)是隔離程序部件并證明這些單個(gè)部件是正確的。
  • 畫(huà)外音:?jiǎn)卧獪y(cè)試是比較細(xì)粒度的測(cè)試,是對(duì)接口、方法、函數(shù)的測(cè)試,目的是保障代碼按照正確的方式去執(zhí)行,提高代碼質(zhì)量。

單元測(cè)試實(shí)施原則

Mock脫離數(shù)據(jù)庫(kù) + 不啟動(dòng)Spring + 優(yōu)化測(cè)試速度 + 不引入項(xiàng)目組件

單元測(cè)試不應(yīng)該依賴數(shù)據(jù),依賴外部服務(wù)或組件等,會(huì)對(duì)其他數(shù)據(jù)產(chǎn)生影響的情況。啟動(dòng)Spring容器,一般比較慢,可能會(huì)啟動(dòng)消息監(jiān)聽(tīng)消費(fèi)消息,定時(shí)任務(wù)的執(zhí)行等,對(duì)數(shù)據(jù)產(chǎn)生影響。

Mock測(cè)試就是在測(cè)試過(guò)程中,對(duì)那些當(dāng)前測(cè)試不關(guān)心的,不容易構(gòu)建的對(duì)象,用一個(gè)虛擬對(duì)象來(lái)代替測(cè)試的情形。

說(shuō)白了:就是解耦(虛擬化)要測(cè)試的目標(biāo)方法中調(diào)用的其它方法,例如:Service的方法調(diào)用Mapper類的方法,這時(shí)候就要把Mapper類Mock掉(產(chǎn)生一個(gè)虛擬對(duì)象),這樣我們可以自由的控制這個(gè)Mapper類中的方法,讓它們返回想要的結(jié)果、拋出指定異常、驗(yàn)證方法的調(diào)用次數(shù)等等。

減少單元測(cè)試對(duì)外部的依賴和副作用,提高單元測(cè)試效率

  1. 不使用 @Autowired,@Resource, 需要啟動(dòng) Spring 容器,測(cè)試速度慢,會(huì)產(chǎn)生副作用;
  2. 不使用 @SpringBootTest,@SpringBootTest(classes = Application.class), 這會(huì)啟動(dòng)整個(gè) SpringBoot 服務(wù)
  3. 不應(yīng)調(diào)用數(shù)據(jù)庫(kù),除非是做數(shù)據(jù)庫(kù)操作相關(guān)的測(cè)試,雖然可配置事務(wù)回滾,但大多數(shù)情況下還是會(huì)產(chǎn)生臟數(shù)據(jù)等問(wèn)題
  4. 使用Assert斷言,用于判斷某個(gè)特定條件下某個(gè)方法的行為,為了證明某段代碼的執(zhí)行結(jié)果和期望的一致
  • 畫(huà)外音:?jiǎn)卧獪y(cè)試應(yīng)小而輕,提交測(cè)試效率,較少對(duì)外部的依賴,比如數(shù)據(jù)庫(kù)、Spring容器、網(wǎng)絡(luò)服務(wù)等,而只關(guān)心我們自己的代碼,通過(guò)Mock來(lái)解決對(duì)外部的依賴

Mockito的使用

基本使用

  1. 使用靜態(tài)方法 mock()
  2. 使用注解 @Mock 標(biāo)注

如果使用@Mock注解, 必須去觸發(fā)所標(biāo)注對(duì)象的創(chuàng)建. 可以使用 MockitoRule來(lái)實(shí)現(xiàn). 它調(diào)用了靜態(tài)方法MockitoAnnotations.initMocks(this) 去初始化這個(gè)被注解標(biāo)注的字段.或者也可以使用@RunWith(MockitoJUnitRunner.class).

“when thenReturn”和”when thenThrow”
模擬對(duì)象可以根據(jù)傳入方法中的參數(shù)來(lái)返回不同的值, when(….).thenReturn(….)方法是用來(lái)根據(jù)特定的參數(shù)來(lái)返回特定的值.

我們也可以使用像 anyString 或者 anyInt anyLong any 這樣的方法來(lái)定義某個(gè)依賴數(shù)據(jù)類型的方法返回特定的值.

“doReturn when” 和 “doThrow when”
doReturn(…).when(…)的方法調(diào)用和when(….).thenReturn(….)類似.對(duì)于調(diào)用過(guò)程中拋出的異常非常有用.而doThrow則也是它的一個(gè)變體.

常用注解

@Mock:對(duì)函數(shù)的調(diào)用均執(zhí)行mock(即虛假函數(shù)),不執(zhí)行真正部分。

@Spy:對(duì)函數(shù)的調(diào)用均執(zhí)行真正部分。

@InjectMocks:創(chuàng)建一個(gè)實(shí)例,簡(jiǎn)單的說(shuō)是這個(gè)Mock可以調(diào)用真實(shí)代碼的方法,使用@Mock(或@Spy)注解創(chuàng)建的mock將被注入到用該實(shí)例中。

Mockito中的Mock和Spy都可用于攔截那些尚未實(shí)現(xiàn)或不期望被真實(shí)調(diào)用的對(duì)象和方法,并為其設(shè)置自定義行為。二者的區(qū)別在于Mock不真實(shí)調(diào)用,Spy會(huì)真實(shí)調(diào)用。

@MockBean: 功能同 @Mock, 只是會(huì)將實(shí)例放入 Spring 容器管理

@SpyBean: 功能同 @Spy, 只是會(huì)將實(shí)例放入 Spring 容器管理

  1. Spy 和 Mock 生成的對(duì)象不受 Spring 管理
  2. Spy 調(diào)用真實(shí)方法時(shí),其它 bean 是無(wú)法注入的,要使用注入,要使用 SpyBean
  3. SpyBean 和 MockBean 生成的對(duì)象受 Spring 管理,相當(dāng)于自動(dòng)替換對(duì)應(yīng)類型 bean 的注入,比如 @Autowired、@Resource 等注入

最佳實(shí)踐

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

    // 創(chuàng)建一個(gè)實(shí)例,會(huì)注入Mock變量
    @InjectMocks
    private ExamAnswerComponent examAnswerComponent = new ExamAnswerComponentImpl();

    // 相關(guān)操作會(huì)被Mock掉
    @Mock
    private ExamAnswerCacheObjectiveDAO examAnswerCacheObjectiveDAO;

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

        // given...willReturn 指定方法參數(shù),模擬返回值
        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/cs106503150/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容