一、前言
有同學(xué)可能看到這個(gè)標(biāo)題,又以為是標(biāo)題黨了,你真莫笑,你可以小聲去問問你在中小型公司的同學(xué),有多少在寫單測?可能有的同學(xué)就只知道Junit,連Mockito是什么都不知道,希望本文能幫助你深入的了解單測框架。
相信做過開發(fā)的同學(xué),都多多少少寫過下面的代碼,很長一段時(shí)間我一直以為這就是單元測試...
@SpringBootTest@RunWith(SpringRunner.class)publicclassUnitTest1{@AutowiredprivateUnitService unitService;@Testpublicvoid test() {System.out.println("----------------------");System.out.println(unitService.sayHello());System.out.println("----------------------");? ? }}
但這是單元測試嘛?unitService 中可能還依賴了 Dao 的操作;如果是微服務(wù),可能還要起注冊中心。那么這個(gè)“單元”也太大了吧!如果把它稱為集成測試,可能更恰當(dāng)一點(diǎn),那么有沒有可能最小粒度進(jìn)行單元測試嘛?
單元測試應(yīng)該是一個(gè)帶有隔離性的功能測試。在單元測試中,應(yīng)盡量避免其他類或系統(tǒng)的副作用影響。
單元測試的目標(biāo)是一小段代碼,例如方法或類。方法或類的外部依賴關(guān)系應(yīng)從單元測試中移除,而改為測試框架創(chuàng)建的 mock 對象來替換依賴對象。
單元測試一般由開發(fā)人員編寫,通過驗(yàn)證或斷言目標(biāo)的一些行為或狀態(tài)來達(dá)到測試的目的。
二、JUnit 框架
JUnit 是一個(gè)測試框架,它使用注解來標(biāo)識測試方法。JUnit 是 Github 上托管的一個(gè)開源項(xiàng)目。
一個(gè) JUnit 測試指的是一個(gè)包含在測試類中的方法,要定義某個(gè)方法為測試方法,請使用 @Test 注解標(biāo)注該方法。該方法執(zhí)行被測代碼,可以使用 JUnit 或另一個(gè) Assert 框架提供的 assert 方法來檢查預(yù)期結(jié)果與實(shí)際結(jié)果是否一致,這些方法調(diào)用通常稱為斷言或斷言語句。
publicclassUnitTest2{@Testpublicvoidtest(){String sayHello ="Hello World";Assert.assertEquals("Hello World", sayHello);? ? }}
以下是一些常用的 JUnit 注解:
注解描述
@Test將方法標(biāo)識為測試方法
@Before在每次測試之前執(zhí)行。用于準(zhǔn)備測試環(huán)境(例如,讀取輸入數(shù)據(jù),初始化類)
@After每次測試之后執(zhí)行。用于清理測試環(huán)境(例如,刪除臨時(shí)數(shù)據(jù),恢復(fù)默認(rèn)值)
@BeforeClass用于 static方法,在所有測試開始之前執(zhí)行一次。它用于執(zhí)行耗時(shí)的活動(dòng),例如:連接到數(shù)據(jù)庫
@AfterClass用于 static方法,在完成所有測試之后,執(zhí)行一次。它用于執(zhí)行清理活動(dòng),例如:與數(shù)據(jù)庫斷開連接
@Ignore指定要忽略的測試
@Test(expected = Exception.class)如果該方法未引發(fā)命名異常,則失敗
@Test(timeout=100)如果該方法花費(fèi)的時(shí)間超過100毫秒,則失敗
以下是一些常用的 Assert 斷言:
聲明描述
fail([message])使方法失敗。在執(zhí)行測試代碼之前,可用于檢查未到達(dá)代碼的特定部分或測試失敗
assertTrue([message,]布爾條件)檢查布爾條件是否為真
assertFalse([message,]布爾條件)檢查布爾條件是否為假
assertEquals([message,]預(yù)期,實(shí)際)測試兩個(gè)值是否相同。注意:對于數(shù)組,會檢查引用而不是數(shù)組的內(nèi)容
assertNull([message,]對象)檢查對象是否為空
assertNotNull([message,]對象)檢查對象是否不為空
assertSame([message,]預(yù)期,實(shí)際)檢查兩個(gè)變量是否引用同一對象
assertNotSame([message,]預(yù)期,實(shí)際)檢查兩個(gè)變量是否引用了不同的對象
三、Mockito 框架
從上面的介紹我們可以認(rèn)識到,如何減少對外部的依賴才是實(shí)踐單元測試的關(guān)鍵。而這正是?Mockito?的使命,Mockito 是一個(gè)流行的 mock 框架,可以與 JUnit 結(jié)合使用,Mockito 允許我們創(chuàng)建和配置 mock 對象,使用 Mockito 將大大簡化了具有外部依賴項(xiàng)的類的測試開發(fā)。spring-boot-starter-test 中默認(rèn)集成了 Mockito,不需要額外引入。
在測試中使用 Mockito,通常會:
mock 外部依賴關(guān)系并將 mock 對象插入待測代碼
執(zhí)行被測代碼
驗(yàn)證代碼是否正確執(zhí)行
3.1 使用 Mockito 創(chuàng)建 mock 對象
Mockit o提供了幾種創(chuàng)建 mock 對象的方法:
使用靜態(tài) mock() 方法
使用 @Mock 注解
如果使用 @Mock 注解,則必須觸發(fā)創(chuàng)建帶有 @Mock 注解的對象。使用 MockitoRule 可以做到,它通過調(diào)用靜態(tài)方法 MockitoAnnotations.initMocks(this) 來填充帶 @Mock 注解的字段。或者可以使用 @RunWith(MockitoJUnitRunner.class)。
publicclassUnitTest3{// 觸發(fā)創(chuàng)建帶有 @Mock 注解的對象@RulepublicMockitoRule mockitoRule = MockitoJUnit.rule();// 1. 使用 @Mock 注解創(chuàng)建 mock 對象@MockprivateUnitDao unitDao;@Testpublicvoidtest(){// 2. 使用靜態(tài) mock() 方法創(chuàng)建 mock 對象? ? ? ? Iterator iterator = mock(Iterator.class);// when...thenReturn / doReturn...when 模擬依賴調(diào)用when(iterator.next()).thenReturn("hello");doReturn(1).when(unitDao).delete(anyLong());// 斷言Assert.assertEquals("hello", iterator.next());Assert.assertEquals(newInteger(1), unitDao.delete(1L));? ? }}
3.2 使用 mock 對象實(shí)踐單元測試
我們要單元測試的內(nèi)容,常常包含著對數(shù)據(jù)庫的訪問等等,那么我們要如何 mock 掉這部分調(diào)用呢?我們可以使用 @InjectMocks 注解創(chuàng)建實(shí)例并使用 mock 對象進(jìn)行依賴注入。
@ServicepublicclassUnitServiceImplimplementsUnitService {@AutowiredprivateUnitDao unitDao;@OverridepublicStringsayHello() {Integerdelete= unitDao.delete(1L);System.out.println(delete);return"hello unit";? ? }}
@RunWith(MockitoJUnitRunner.class)publicclassUnitTest2{? ? @MockprivateUnitDao unitDao;@InjectMocksprivateUnitServiceImpl unitService;@TestpublicvoidunitTest(){// mock 調(diào)用when(unitDao.delete(anyLong())).thenReturn(1);Assert.assertEquals("hello unit", unitService.sayHello());? ? }}
Mockito 還有很多有趣的實(shí)踐,比如:@Spy或spy()方法、verify()驗(yàn)證等等,鑒于篇幅原因,讀者可自行挖掘。
3.3 使用 PowerMock mock 靜態(tài)方法。
Mockito 也有一些局限性。例如:不能 mock 靜態(tài)方法和私有方法。這個(gè)時(shí)候我們就要用到 PowerMock,PowerMock 支持 JUnit 和 TestNG,擴(kuò)展了 EasyMock 和 Mockito 框架,增加了mock static、final 方法的功能。
首先需要引入 PowerMock 的依賴:
<!-- PowerMock -->org.powermockpowermock-module-junit42.0.7testorg.powermockpowermock-api-mockito22.0.7
接下來就能愉快的 mock 靜態(tài)方法了。
@RunWith(PowerMockRunner.class)@PrepareForTest({StringUtils.class})publicclassUnitTest4{@Testpublicvoid test() {mockStatic(StringUtils.class);when(StringUtils.getFilename(anyString())).thenReturn("localhost");Assert.assertEquals("localhost", StringUtils.getFilename(""));? ? }}