爲什麼要寫單元測試
- 優點:單元測試可以減少bug率,提升代碼的質量。還可以通過單元測試來熟悉業務。
- 公司硬性要求:有些公司可能還會強制要求,每次新增代碼、或者變更代碼單測覆蓋率要達到多少比例才能申請代碼合併請求。
選擇哪個單元測試框架
目前應用比較普遍的java單元測試工具 junit4+Mock(Mockito、jmock、EasyMock、powermock)。爲什麼會選擇powermock?
在做單元測試的時候,我們會發現我們要測試的方法會有很多外部依賴的對象或者一些其他服務的調用比如說(發送郵件,網絡通訊,soa調用)。 而我們沒法控制這些外部依賴的對象。 爲了解決這個問題,我們需要用到Mock來模擬這些外部依賴的對象,從而控制它們。只關心我們自己的業務邏輯是否正確。而這時powermock就起作用了,它不僅可以mock外部的依賴,還可以mock私有方法、final方法,總之它的功能很強大。
什麼是powerMocker
PowerMock是一個框架,它以更強大的功能擴展了其他模擬庫,例如EasyMock。 PowerMock使用自定義的類加載器和字節碼操作來模擬靜態方法,構造函數, 最終類和方法,私有方法,刪除靜態初始化程序等。通過使用自定義類加載器,無需對IDE或持續集成服務器進行任何更改,從而簡化了採用過程。熟悉受支持的模擬框架的開發人員會發現PowerMock易於使用,因爲整個期望API都是相同的,
無論是靜態方法還是構造函數。PowerMock 旨在通過少量方法和註釋擴展現有的API,以啓用額外的功能。
常用註解
- @RunWith(PowerMockRunner.class)
告訴JUnit使用PowerMockRunner進行測試 - @PrepareForTest({DemoDao.class})
所有需要測試的類列在此處,適用於模擬final類或有final, private, static, native方法的類 - @PowerMockIgnore({“javax.management.", "javax.net.ssl.”})
爲了解決使用powermock後,提示classloader錯誤 - @SuppressStaticInitializationFor
不讓靜態代碼加載
其他更多註解可以參考:https://github.com/powermock/powermock/wiki/Suppress-Unwanted-Behavior
如何開始
JUnit 4.4及以上
<properties>
<powermock.version>2.0.2</powermock.version>
</properties>
<dependencies>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
powerMock樣例
這是一個需要被mock的類裏面有私有方法、靜態方法、等等下面一一來演示各個方法的mock功能。
/**
*
* @Date: 2020/3/31
* @Description:
*/
@Repository
public class DemoDao {
public String mockPublicMethod(String type) throws Throwable {
throw new Throwable();
}
public final String mockFinalMethod(String type) throws Throwable {
throw new Throwable();
}
public static String mockStaticMethod(String type) throws Throwable {
throw new Throwable();
}
}
/**
* @Date: 2020/3/31 11:34
* @Description:
*/
@Component
public class DemoService extends AbstractDemo{
@Autowired
private DemoDao demoDao;
public String mockPublicMethod() throws Throwable {
return demoDao.mockPublicMethod("demo");
}
public String mockFinalMethod() throws Throwable {
return demoDao.mockFinalMethod("demo");
}
public String mockStaticMethod() throws Throwable {
return DemoDao.mockStaticMethod("demo");
}
private String callPrivateMethod(String type) {
return type;
}
public String mockPublicMethodCallPrivateMethod(String type) throws Throwable {
return callPrivateMethodThrowable(type);
}
private String callPrivateMethodThrowable(String type) throws Throwable {
throw new Throwable();
}
public String mockExtendMethod(String type) throws Throwable {
return getExtendMethod();
}
public static String UUID = "uuid";
}
mock普通公共方法
/**
* @Date: 2020/4/24
* @Description:
*/
@RunWith(PowerMockRunner.class)
public class DemoServiceTest {
@InjectMocks
private DemoService demoService;
@Mock
private DemoDao demoDao;
/**
* mock 普通方法
* @throws Throwable
*/
@Test
public void mockPublicMethod() throws Throwable {
String type = UUID.randomUUID().toString();
PowerMockito.when(demoDao.mockPublicMethod(any())).thenReturn(type);
String result = demoService.mockPublicMethod();
Assert.assertEquals(type, result);
}
mock Final方法
跟普通方法是一樣的,唯一的區別是需要在類上加入PrepareForTest註解
@RunWith(PowerMockRunner.class)
@PrepareForTest(DemoDao.class)
public class DemoServiceTest {
@InjectMocks
private DemoService demoService;
@Mock
private DemoDao demoDao;
/**
* mock final方法
* @throws Throwable
*/
@Test
public void mockFinalMethod() throws Throwable {
String type = UUID.randomUUID().toString();
PowerMockito.when(demoDao.mockFinalMethod(any())).thenReturn(type);
String result = demoService.mockFinalMethod();
Assert.assertEquals(type, result);
}
mock靜態方法(使用 PowerMockito.mockStatic)被mock的類也要用PrepareForTest註解修飾。
@RunWith(PowerMockRunner.class)
@PrepareForTest(DemoDao.class)
public class DemoServiceTest {
@InjectMocks
private DemoService demoService;
@Mock
private DemoDao demoDao;
/**
* mock 靜態方法
* @throws Throwable
*/
@Test
public void mockStaticMethod() throws Throwable {
String type = UUID.randomUUID().toString();
PowerMockito.mockStatic(DemoDao.class);
PowerMockito.when(DemoDao.mockStaticMethod(any())).thenReturn(type);
String result = demoService.mockStaticMethod();
Assert.assertEquals(type, result);
}
調用 private方法
/**
* 調用私有方法
*
* @throws Throwable
*/
/**
* 調用私有方法
*
* @throws Throwable
*/
@Test
public void callPrivateMethod() throws Throwable {
// 第一種方式
String type = UUID.randomUUID().toString();
Method method = PowerMockito.method(DemoService.class, "callPrivateMethod", String.class);
String result = (String) method.invoke(demoService, type);
Assert.assertEquals(type, result);
//第二種方式
String result1 = Whitebox.invokeMethod(demoService, "callPrivateMethod", type);
Assert.assertEquals(type, result1);
}
mock 私有方法(被mock的類也要用PrepareForTest註解修飾。)
/**
* mock私有方法
*
* @throws Throwable
*/
@Test
public void mockPrivateMethod() throws Throwable {
String type = UUID.randomUUID().toString();
// 重點這一句
demoService = PowerMockito.spy(demoService);
PowerMockito.doReturn(type).when(demoService,"callPrivateMethodThrowable",type);
String result = demoService.mockPublicMethodCallPrivateMethod(type);
Assert.assertEquals(type, result);
}
mock父類方法
/**
* mock父類方法
*
* @throws Throwable
*/
@Test
public void mockExtendMethod() throws Throwable {
String type = UUID.randomUUID().toString();
// 需要mock的父類的方法
Method method = PowerMockito.method(AbstractDemo.class, "getExtendMethod");
// InvocationHandler
PowerMockito.replace(method).with((proxy, method1, args) -> type);
String result = demoService.mockExtendMethod(type);
Assert.assertEquals(type, result);
}
mock構造方法
public DemoService() {
throw new NullPointerException();
}
@Test
public void mockConstructorMethod() throws Throwable {
PowerMockito.whenNew(DemoService.class).withNoArguments().thenReturn(demoService);
}
mock字段
/**
* mock 字段
*/
@Test
public void mockFiled(){
String uuid = UUID.randomUUID().toString();
Whitebox.setInternalState(DemoService.class, "UUID",uuid);
Assert.assertEquals(DemoService.UUID, uuid);
}
結束
- 由於自己才疏學淺,難免會有紕漏,假如你發現了錯誤的地方,還望留言給我指出來,我會對其加以修正。
- 如果你覺得文章還不錯,你的轉發、分享、讚賞、點贊、留言就是對我最大的鼓勵。
- 感謝您的閱讀,十分歡迎並感謝您的關注。