? ? ? ? 最近工作中由于要寫單元測試,接觸到了Mockito這門對于筆者來說完全陌生的技術,在學習和使用中也遇到了不少坑,在這里做一下總結(jié)和梳理。
Mockito是做什么用的?
? ? ? ? 與其說Mockito是做什么用的,不如說說單元測試是做什么用的,相信大多數(shù)人和筆者一樣,習慣用PostMan來測試接口的連通性,我們都知道其實PostMan這種自測的方式是完全可以的,但是為什么還會出現(xiàn)單元測試這個東西呢?網(wǎng)上大家的分享其實很多了,但是筆者認為最重要的就是一點:單元測試是寫在當下,但是為的是服務于未來的。假如未來我們的代碼修改了,比如邏輯的一些修改,那么我們的單元測試從原則上說是不用修改的,因為我們需求的結(jié)果不變或者變動較少。而且單元測試保留了我們代碼穩(wěn)固的驗證記錄。而Mockito的假如,則讓單元測試又多了一層意義,那就是在我們可以對代碼進行提前的校驗。比如說我們的A類要依賴B接口中的方法,但是B接口還未開發(fā)完畢,但是我們已經(jīng)通過提前溝通知道了B接口要返回的內(nèi)容,此時便可以用Mockito去模擬B接口的調(diào)用過程。比如我們的項目要連接某個數(shù)據(jù)庫,但是目前數(shù)據(jù)庫的網(wǎng)絡還沒調(diào)通,硬件那邊正在交涉,我們便可以通過使用Mockito來模擬這個數(shù)據(jù)庫的返回結(jié)果,從而進行單元測試。
? ? ? ? 在上段文字中出現(xiàn)了多次的“模擬”二字。是的,Mockito最大的作用就是模擬。從而達到我們在單元測試中想寫什么寫什么,缺什么就用Mockito來模擬就好了!
Mockito怎么用?
? ? ? ? 1.TestA mockA = Mockito.mock(TestA.class);
? ? ? ? 這便是Mockito最簡單的使用方法,我們的TestA就是我們想要模擬的一個類。而mockA便是一個我們模擬出來的一個TestA類型的對象。
? ? ? ? 這個模擬類有什么特點呢?首先,無法set任何值,也就是說即使你這個類是有一個setId的方法的,但是你在調(diào)用了mockA.setId(10);的情況下,當我們的單元測試執(zhí)行到真實的業(yè)務代碼中,是不生效的。
? ? ? ? 2.不生效?那模擬它有什么用?用處來了。模擬對象的意義在于,可以使用一個叫“打樁”的操作,stub,其實它也有存根的意思,但是我看到其他帖子稱之為打樁的場景最多,所以我們這里就稱這個行為是打樁好了。打樁怎么用:Mockito.when(mock.getId()).thenReturn(10);這段代碼的含義是當我們的mock對象執(zhí)行到了getId這個方法的時候,就返回“10”這個值。這個when(XXX).thenReturn(XXX)的寫法,就叫做“打樁”,也是Mockito的一個核心用法。
? ? ? ? 打樁好在哪?比如你需要連數(shù)據(jù)庫,需要通過某個查詢語句去數(shù)據(jù)庫查詢你的數(shù)據(jù),然后再返回一個值,但是現(xiàn)在連接不到數(shù)據(jù)庫,或者說連數(shù)據(jù)庫你需要去公司連內(nèi)網(wǎng)才可以,你本地又懶得建一個類似的表了,那么你就可以通過使用打樁操作,來返回一個你需要的值。
? ? ? ? 打樁需要注意的點:
? ? ? ? ? ? ? ? ? ? ? ? ? ? 1.when里面的對象,一定要是Mockito類型的虛擬對象,你放一個真實對象進去,會報類型錯誤。
? ? ? ? ? ? ? ? ? ? ? ? ? ? 2.我進行了打樁操作,但是實際業(yè)務代碼中沒有執(zhí)行我的打樁操作,沒有返回我的預期結(jié)果怎么辦?這種情況一般是出現(xiàn)在你的打樁的when里面的條件和實際的業(yè)務代碼匹配不上才會發(fā)生。這里傳授幾個小技巧,比如說方法的參數(shù)是一個String類型的對象,那么在when中,就使用Mockito.anyString()來代替。如果實際業(yè)務代碼中就是一個String.class,那么在when方法中就使用Mockito.eq(String.class)即可。有時候我們使用Mockito.when(mock(Mockito.any()))都無法匹配上,也是這個原因。Moktio還給我們提供了一個方法,來檢驗我們的打樁操作是否被執(zhí)行了,具體使用如下:
????????????// 創(chuàng)建一個 mock 對象
????????????List<String> mockedList = mock(List.class);
? ? ? ? ? ? ?// 打樁 mockedList.size() 方法返回 100
????????????when(mockedList.size()).thenReturn(100);
????????????// 調(diào)用 mock 對象
????????????int size = mockedList.size();
????????????// 驗證 mockedList.size() 方法是否被調(diào)用
????????????verify(mockedList).size();
在我的業(yè)務代碼中,有些地方我想用真實數(shù)據(jù),有些地方想用模擬數(shù)據(jù),怎么辦?
? ? ? ? ? ? 比如說我的代碼要檢驗一個文件,從文件里拿出來一些需要的內(nèi)容,然后再去數(shù)據(jù)庫查詢對應的數(shù)據(jù),接下來再做一些別的處理。我現(xiàn)在就想測測這個我檢驗文件的格式的代碼是否正確,其他的部分,比如說數(shù)據(jù)庫查詢這種我想用模擬數(shù)據(jù)來解決。也就是說檢驗文件格式的這部分的代碼是不能被模擬的,而且我本地確實也準備了一個真實的文件。這種情況下就得提到Mockito的另一個API了,它叫spy。
? ? ? ? 比如Person person = new Person(); person.setName("張三");? ?Person spyPerson=Mockito.spy(person);上面這幾句代碼是什么意思呢,首先我們新建了一個真實的person類,然后給這個類設置了一個名字,然后又在這個類的基礎上創(chuàng)建了spy類,也就是Mockito的模擬類。這么做有什么用呢,首先是在之后的測試中,我們的業(yè)務代碼碰到了需要去getName的時候,返回的會是我們提前在測試代碼中設置的這個張三。如果是碰到了getAge()的話,我們又可以通過使用Mocktio.when(spyPerson.getAge()).thenReturn(18);來返回18這個值了。
? ? ? ? 也就是說,通過spy新產(chǎn)生的對象,既具有之前的真實類的屬性,也可以執(zhí)行我們新加的打樁操作。但是有一點,就是我們傳入Person類的時候,傳入的需要時spyPerson這個類。
?Mockito的靜態(tài)方法的使用
? ? ? ? 在上面的內(nèi)容,不管是普通的Mockito的類也好,還是通過spy模擬產(chǎn)生的類也罷,都是需要通過將Mockito對象作為入?yún)魅雽嶋H的業(yè)務代碼中去執(zhí)行的方法。但是我們也有一些類,是不通過入?yún)魅氲?。比如我們的業(yè)務代碼中直接通過調(diào)用其他類的靜態(tài)方法,這種無法通過入?yún)魅耄趺慈ツM呢?答案就是使用Mocktio.mockStatic()這個API。
? ? ? ? 具體使用方法try(MockedStatic<MyClassWithStaticMethods> mockedStatic = mockStatic(MyClassWithStaticMethods.class);)
{
? ? mockedStatic.when(MyClassWithStaticMethods.getXXX()).then(XXX);
};
? ? ? ? 可以發(fā)現(xiàn),是加了一個try的語句塊在上面。把涉及這個模擬的靜態(tài)類要去執(zhí)行的方法寫在大括號里,這么做有一個好處,就是執(zhí)行完了大括號里的內(nèi)容后,這個靜態(tài)方法會自動關閉,不影響這個被我們模擬的類的靜態(tài)方法在別的地方調(diào)用,就是說在別的地方如果再調(diào)這個類的靜態(tài)方法,就會回歸它本身的真實方法了。還有一種方法,就是通過調(diào)用mockedStatic.close()來關閉對于這個對象的調(diào)用。如果以上兩種方法都沒有使用,當我們進行項目打包的時候,就會報錯,比如A和B兩個測試類中都有使用Mockito.mockStatic的話,就會提示說MockitoStatic已經(jīng)在存在了。
通過一些鏈式調(diào)用解決業(yè)務的場景
? ? ????比如現(xiàn)在有一個學校類,學校類里有學生類,學生自己又有姓名和年齡這兩個屬性。我們現(xiàn)在Mockito產(chǎn)生的是學校類,我們一開始傳入實際業(yè)務代碼中的入?yún)⒁彩菍W校類,但是實際需要的卻是學生類的姓名,該如何去做?
? ? ? ? 在測試類中,1.首先模擬一個學校對象:School school = Mockito.mock(School.class);2.再模擬一個學生類:Student student = Mockito.mock(Student.class);3.打樁一個操作,當學校類獲取學生類的時候,就返回我們模擬的這個學生類:Mockito.when(school.getStudent()).thenReturn(student);4.打樁一個操作,當學生類進行獲取姓名的時候,就返回我們的設置值:Mockito.when(student.getName()).thenReturn("張三");
? ? ? ? 我們進行了上述操作后,再看看我們實際業(yè)務代碼中的操作:? String name =? school.getStudent().getName();結(jié)合兩段操作下來,這里的name的值就是張三了。我們通過鏈式調(diào)用的辦法,將通過學校獲取學生并且通過學生獲取姓名的方法進行了模擬的拆解。
? ? ? ? 好了,以上就是我的一些分享,都是一些Mockito的基礎的使用,希望大家可以喜歡。