在intellij越來越普及的情況下,利用JUnit在intellij中進行測試就顯得很基礎了,但網上的資料總有誤導的地方,這里記錄一下。
總體而言,要開始單元測試,可以分為三步,添加相關的插件,添加相關的依賴,編寫測試方法,下面依序說下。
一、添加相關的插件
在intellij中利用JUnit進行測試,需要三個插件,Junit,用來執行測試用例,JUnitGenerator V2.0,用來生成測試用例,Coverage,用來生成測試報告。
安裝插件完畢,還需要對JUnit進行適當的設置:
Junit Generator設置
Setting --》 Other Setting--》 Junit Generator
更改輸出路徑,
Output Path:
${SOURCEPATH}/../../test/java/${PACKAGE}/${FILENAME}
更改默認單元測試框架,
Default Template:
Junit 4
更改JUnit4的默認模板,
Junit 4
test. $entry.packageName $entry.packageName
<pre>$date</pre> <pre>$today</pre>
二,添加相關的依賴
在maven項目中,添加如下的依賴:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.28.2</version>
<scope>test</scope>
</dependency>
三、開始編寫測試方法
1.步驟
1)查看項目的project structure,確保:
Source Folders: src\main\java
Test Source Folders: src\test\java
2)在被測試的類中,選擇 generate -> JUnit Test -> JUnit 4
3)在生成的測試類中編寫相關的測試代碼
四、JUnit的注解
1.斷言
編寫單元測試方法的一個根本不同在於要用斷言來表達測試是否通過。
@Test public void testAdd() throws Exception { int value = new Util().add(2,4); Assert.assertEquals(6,value); } org.junit.Assert assertTrue(String message, boolean condition) assertEquals(String message, Object expected, Object actual) assertArrayEquals(String message, Object[] expecteds, Object[] actuals) assertNull(String message, Object object) assertSame(String message, Object expected, Object actual) assertThat(T actual, Matcher<? super T> matcher)
JUnit提供了各式各樣的斷言供使用,選擇合適的即可。
2.@test的子屬性
如果要測試異常或者時間,則可以利用@test注解的子屬性。
excepted屬性,異常測試 @Test (expected = Exception.class) public void testDivideException() throws Exception { new Junit_Test().divide(3,0); fail("除數為零沒有拋出異常"); } timeout屬性,超時測試
@Test (timeout = 1000) public void testDivideTimeout() throws Exception { new Junit_Test_Demo().divide(6,3); }
3.常用注解
@Test,標明是一個測試方法
@Before,在每個測試方法執行前都執行
@After,在每個測試方法執行完都執行
@BeforeClass,在測試類一開始就執行
@AfterClass,在測試類執行完畢執行
@Ignore,暫時忽略這個測試方法
4.高級注解
Rule:可以用來擴展JUnit的功能,改變測試方法的行為;
@ClassRule,類級別,執行測試類的時候只調用一次被注解的Rule
@Rule,方法級別,每個測試方法執行的時候都會調用被注解的Rule
內置的Rule
TemporaryFolder,創建臨時目錄或文件
ExternalResource,在測試之前創建資源,並在測試完成后銷毀
TestName ,獲取目前測試方法的名字
TestWatcher ,在每個觸發點執行自定義的邏輯
Verifier ,在測試執行完成之后做一些校驗,以驗證測試結果是不是正確
ErrorCollector ,收集多個錯誤,並在測試執行完后一次過顯示出來
@RunWith:默認BlockJunit4ClassRunner,可以指定特殊的Runner;
Suit,一次執行多個類的測試用例
Parameterized,批量指定多個待測參數
Category,對測試類中的被測試方法進行分類執行
Theories,為待測方法提供一組參數的排列組合
五,Mock
說到單元測試,就不能不提Mock,常用的mock框架很多,比較多用的是mockito和powermock。
Mockito
Mock技術框架,能讓我們隔離外部依賴以便對我們自己的業務邏輯代碼進行單元測試,在編寫單元測試時,不需要再進行繁瑣的初始化工作,在需要調用某一個接口時,直接模擬一個假方法,並任意指定方法的返回值。 Mockito的工作原理是通過創建依賴對象的proxy,所有的調用先經過proxy對象,proxy對象攔截了所有的請求再根據預設的返回值進行處理。Mockito對於Java接口使用接口代理的方式來模擬,對於Java類使用繼承的方式來模擬(也即會創建一個新的Class類)。
PowerMock
PowerMock則在Mockito原有的基礎上做了擴展,通過修改類字節碼並使用自定義ClassLoader加載運行的方式來實現mock靜態方法、final方法、private方法、系統類的功能。 從兩者的項目結構中就可以看出,PowerMock直接依賴於Mockito,所以如果項目中已經導入了PowerMock包就不需要再單獨導入Mockito包,如果兩者同時導入還要小心PowerMock和Mockito不同版本之間的兼容問題。
1.流程
Mockito Mock mock(Class classToMock); mock(Class classToMock, String name) Stub when(mock.someMethod()).thenReturn(value) when(mock.someMethod()).thenThrow(new RuntimeException) when(mock.someMethod()).thenAnswer() exec
首先要利用mock來構造依賴,其次利用when語句來構造stub,然后就可以執行測試方法了。
其實還可以利用spy來構造依賴,但與mock構造有不同的地方:
mock
對於未指定處理規則的調用會按方法返回值類型返回該類型的默認值(如int、long則返回0,boolean則返回false,對象則返回null,void則什么都不做)
spy
未指定處理規則時則會直接調用真實方法
2.最佳實踐
如何進行單元測試,每個公司都有不同的做法,一些最佳實踐總結如下:
AIR原則
automatic,必須使用斷言,禁止使用輸出進行驗證
independent,UT之間沒有調用關系和前后關系
repeatable,不與外界環境耦合,可隨時執行
粒度
只對單個的類進行測試,不檢查跨類或跨系統的交互
只對公開的接口進行測試
維護
新增代碼需要補充單元測試
變更代碼需要修正單元測試
編寫單元測試的BCDE原則
border,邊界測試,特殊取值、特殊時間點、數據順序等
correct,正確的輸入,並得到預期的結果
design,與設計文檔結合,編寫單元測試
error,強制錯誤信息輸入(非法數據,異常流程),並得到預期結果
覆蓋率
語句覆蓋率達到70%,分支覆蓋率達到100%
3.利用Mock的一個例子
假如有person,dao和service三個類,其中dao是個接口,service依賴於dao,具體如下:
public class Person { private final int id; private final String name; public Person(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public String getName() { return name; } }
public interface IPersonDao { Person getPerson(int id); boolean update(Person person); }
public class PersonService { private final IPersonDao personDao; public PersonService(IPersonDao personDao) { this.personDao = personDao; } public boolean update(int id, String name) { Person person = personDao.getPerson(id); if (person == null) { return false; } Person personUpdate = new Person(person.getId(), name); return personDao.update(personUpdate); } }
利用mock來測試
1 import org.junit.Test; 2 import org.junit.Before; 3 import org.junit.After; 4 import static org.junit.Assert.*; 5 import static org.mockito.Mockito.*; 6 7 import org.mockito.Mock; 8 import org.mockito.Mockito.*; 9 import org.mockito.MockitoAnnotations; 10 11 import java.util.List; 12 13 /** 14 * PersonService Tester. 15 * 16 * @author <Authors name> 17 * @since <pre>07/01/2019</pre> 18 * @version 1.0 19 */ 20 public class PersonServiceTest { 21 22 private IPersonDao mockDao; 23 private PersonService personService; 24 25 @Before 26 public void before() throws Exception { 27 mockDao = mock(IPersonDao.class); 28 when(mockDao.getPerson(1)).thenReturn(new Person(1,"mst")); 29 when(mockDao.update(isA(Person.class))).thenReturn(true); 30 31 personService = new PersonService(mockDao); 32 } 33 34 @After 35 public void after() throws Exception { 36 } 37 38 /** 39 * 40 * Method: update(int id, String name) 41 * 42 */ 43 @Test 44 public void testUpdate() throws Exception { 45 boolean result = personService.update(1,"md"); 46 47 assertTrue(result); 48 49 //驗證是否執行過一次getPerson(1) 50 verify(mockDao,times(1)).getPerson(eq(1)); 51 //驗證是否執行過一次update 52 verify(mockDao,times(1)).update(isA(Person.class)); 53 } 54 55 @Test 56 public void testUpdateNotFind() throws Exception { 57 boolean result = personService.update(2, "md"); 58 59 assertFalse( result); 60 61 //驗證是否執行過一次getPerson(1) 62 verify(mockDao, times(1)).getPerson(eq(2)); 63 //驗證是否執行過一次update 64 verify(mockDao, never()).update(isA(Person.class)); 65 } 66 67 68 @Mock 69 List list; 70 71 public PersonServiceTest(){ 72 MockitoAnnotations.initMocks(this); 73 } 74 75 @Test 76 public void testList(){ 77 when(list.add(isA(Object.class))).thenReturn(true); 78 list.add(2); 79 list.add(5); 80 boolean re = list.add(1); 81 assertTrue(re); 82 } 83 84 }
其中演示了兩種mock對象的構造,一種是手動構造,如22和27行,一種是注解構造,如69和72行。
在before方法中進行了依賴的mock和stub操作,進而在testUpdate中利用相應的依賴和stub進行測試邏輯的執行。
