Powermock介紹
一、為什么要使用Mock工具
在做單元測試的時(shí)候,我們會(huì)發(fā)現(xiàn)我們要測試的方法會(huì)引用很多外部依賴的對象,比如:(發(fā)送郵件,網(wǎng)絡(luò)通訊,遠(yuǎn)程服務(wù), 文件系統(tǒng)等等)。 而我們沒法控制這些外部依賴的對象,為了解決這個(gè)問題,我們就需要用到Mock工具來模擬這些外部依賴的對象,來完成單元測試。
二、PowerMock簡介
PowerMock 也是一個(gè)單元測試模擬框架,它是在其它單元測試模擬框架的基礎(chǔ)上做出的擴(kuò)展。通過提供定制的類加載器以及一些字節(jié)碼篡改技巧的應(yīng)用,PowerMock 現(xiàn)了對靜態(tài)方法、構(gòu)造方法、私有方法以及 Final 方法的模擬支持,對靜態(tài)初始化過程的移除等強(qiáng)大的功能。因?yàn)?PowerMock 在擴(kuò)展功能時(shí)完全采用和被擴(kuò)展的框架相同的 API, 熟悉 PowerMock 所支持的模擬框架的開發(fā)者會(huì)發(fā)現(xiàn) PowerMock 非常容易上手。PowerMock 的目的就是在當(dāng)前已經(jīng)被大家所熟悉的接口上通過添加極少的方法和注釋來實(shí)現(xiàn)額外的功能。
簡而言之,Powermock是mockito的升級(jí)版,但powermock會(huì)依賴于mockito。
使用示例
Maven 包引入
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<powermock.version>2.0.0</powermock.version>
</properties>
<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>
簡單使用
HelloMockitoTest
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
//@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@PowerMockIgnore("javax.management.*")
@PrepareForTest({HelloController.class, TestUtils.class})
@ContextConfiguration(classes = TestBizServer.class)
@ActiveProfiles("local")
public class HelloMockitoTest {
@InjectMocks
private HelloController helloController;
@Mock
private HelloService helloService;
@Before
public void init () {
MockitoAnnotations.initMocks(this);
}
@Test
public void test () throws Exception {
Mockito.when(helloService.sayHi(Mockito.any(String.class))).thenReturn("你好啊!");
String result2 = helloController.testHello();
System.out.println(result2);
assert result2.equals("你好啊!");
}
@Mock
private TestUtils testUtils;
@Test
public void testSome () throws Exception {
TestUtils mock = Mockito.mock(TestUtils.class);
PowerMockito.whenNew(TestUtils.class).withNoArguments().thenReturn(mock);
Mockito.when(mock.testSome()).thenReturn("123456");
String s = helloController.doHttpGet("http://127.0.0.1", Maps.newHashMap());
System.out.println(s);
}
}
HelloController
這里代碼都是偽代碼,不要把controller當(dāng)成一個(gè)rest,當(dāng)成一個(gè)Bean即可。
@RestController
@RequestMapping("/hello")
public class HelloController {
@Autowired(required = false)
HelloService helloService;
@GetMapping("/test")
public String testHello () {
String hello = null;
try {
Thread.sleep(1000);
hello = helloService.sayHi("hello world");
System.out.println("hello param is : " + hello);
} catch (InterruptedException e) {
e.printStackTrace();
}
return hello != null ? hello : "error";
}
@GetMapping("/testSome")
public String doHttpGet(String uri, Map<String, String> getParams) {
TestUtils testUtils = new TestUtils();
String s = testUtils.testSome();
System.out.println("testUtils.testSome return is:" + s);
CloseableHttpResponse response = null;
return s;
}
}
HelloService
@Service
public class HelloService implements InitializingBean {
@Autowired
private HelloDao helloDao;
public String sayHi () {
System.out.println("HI ...");
return "hi";
}
public String sayHi (String name) {
System.out.println("HI ..." + name);
return "hi";
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("我是動(dòng)態(tài)注冊的你,不是容器啟動(dòng)的時(shí)候注冊的你");
}
public boolean deleteOne () {
System.out.println("------------------------------");
return helloDao.deleteOne();
}
}
test
方法運(yùn)行如果沒有寫mock正常會(huì)返回HI,這里會(huì)返回你好啊!
。mockito這中mock方式只適用于Spring中注入代理的Bean才能進(jìn)行正常的mock。如果說這方法體中實(shí)例化了對象或者調(diào)用靜態(tài)方法,這個(gè)時(shí)候就不起作用了。
testSome
中使用了powermock
去處理在方法中實(shí)例化的新對象,把new
對象替換為mock對象(可以理解為一層代理 ),然后讓調(diào)用mock對象的方法返回我們想要的值。
其實(shí)這里可以理解,如果我們想讓某個(gè)對象的方法返回一個(gè)值或者拋出一個(gè)異常,我們就需要把這個(gè)對象替換成mock對象,可以把mock對象理解為代理對象(打樁),這個(gè)是核心思路。
注解講解
@RunWith
在測試類類名上添加 @RunWith(PowerMockRunner.class) 注解代表該測試類使用 PowerMock。必須添加
@PrepareForTest
這個(gè)注解的作用就是告訴 PowerMock 哪些類是需要在字節(jié)碼級(jí)別上進(jìn)行操作的。也就是需要 mock 某些包含 final、static 等方法的類時(shí)使用,使用方法:@PrepareForTest({System.class, LogUtils.class}),在方法中有new對象的時(shí)候也需要在@PrepareForTest中聲明。
@PowerMockIgnore
PowerMock 是使用自定義的類加載器來加載被修改過的類,從而達(dá)到打樁的目的。@PowerMockIgnore 注解告訴 PowerMock 忽略哪些包下的類,從而消除類加載器引入的 ClassCastException。使用方法:@PowerMockIgnore({“javax.management.”, “javax.net.ssl.”, “javax.script.*”})
@SuppressStaticInitializationFor
告訴 PowerMock 哪些包下的類需要被抑制靜態(tài)初始化,包括 static 代碼塊或者 static 變量的初始化。防止因靜態(tài)初始化導(dǎo)致的錯(cuò)誤。使用方法:
@SuppressStaticInitializationFor({“com.xxx.SmsServiceImpl”})
@Mock
mock 待測類的普通屬性,最常見的就是通過 Spring @Autowired 自動(dòng)注入的 bean。這類屬性并非 final,也并非 static,只需要在測試類中使用 @Mock 注解,Pwermock 框架就能自動(dòng)生成 mock 對象,并自動(dòng)注入到 @InjectMock 修飾的待測類中。
也可以使用比較通用的 mock() 方法,調(diào)用 mock(XXX.class),能生成一個(gè) mock 的 XXX 對象,然后通過反射獲取待測類的字段,再將 mock 對象賦給字段。一般用于不是由 Spring @Autowired 注入的普通屬性。
PowerMock簡單實(shí)現(xiàn)原理
1.當(dāng)某個(gè)測試方法被注解@PrepareForTest標(biāo)注以后,在運(yùn)行測試用例時(shí),會(huì)創(chuàng)建一個(gè)新的org.powermock.core.classloader.MockClassLoader實(shí)例,然后加載該測試用例使用到的類(系統(tǒng)類除外)。
2.PowerMock會(huì)根據(jù)你的mock要求,去修改寫在注解@PrepareForTest里的class文件(當(dāng)前測試類會(huì)自動(dòng)加入注解中),以滿足特殊的mock需求。例如:去除’final方法的final標(biāo)識(shí),在靜態(tài)方法的最前面加入自己的虛擬實(shí)現(xiàn)等。
3.如果需要mock的是系統(tǒng)類的final方法和靜態(tài)方法,PowerMock不會(huì)直接修改系統(tǒng)類的class文件,而是修改調(diào)用系統(tǒng)類的class文件,以滿足mock需求。
Api講解
whenNew
當(dāng)創(chuàng)建新對象的時(shí)候
PowerMockito.whenNew(Entity.class).withAnyArguments().thenReturn(entity);
thenThrow
拋出異常when
當(dāng)執(zhí)行xx方法的時(shí)候,一般后面跟鏈?zhǔn)秸{(diào)用處理分支doNothing
什么都不執(zhí)行的時(shí)候,一般后面跟鏈?zhǔn)秸{(diào)用處理分支doReturn
返回,一般后面接when(object).xxxMethod(), 意指讓mock對象調(diào)用某些方法的時(shí)候不做任何處理。
PowerMockito.doNothing().when(userMappers).putUser(Mockito.any(UserDTO.class)); //主要代碼
thenReturn
然后返回mock
創(chuàng)建mock對象mockStatic
mock靜態(tài)class方法屬性,要在類前@PrepareForTest中加入使用SendEmailProxy
的類。
PowerMockito.mockStatic(SendEmailProxy.class);
PowerMockito.when(SendEmailProxy.doSomething()).thenReturn(xxx);
any
模擬參數(shù)
PowerMockito.any(xxx.class)
注意
- 如果項(xiàng)目中增加了
springboot-actuator
包則需要在類前加下述注解配置
@PowerMockIgnore({"javax.management.*"})
- 如果需要測試HttpComponent包則需要在類前加下述注解配置
@PowerMockIgnore({"javax.management.*", "javax.net.ssl.*"})
Powermock與Jacoco兼容問題
如果在@PrepareForTest中修飾了一個(gè)類,這個(gè)類jacoco就沒法進(jìn)行覆蓋率統(tǒng)計(jì)了,powermock是與jacoco的on-the-fly模式?jīng)_突的,但是有辦法可以避免這樣的問題。
原因
JaCoCo和PowerMock都是通過在加載類的時(shí)候修改字節(jié)碼文件來實(shí)現(xiàn)統(tǒng)計(jì)覆蓋率和mock靜態(tài)類的功能。JaCoCo在加載class的時(shí)候會(huì)把統(tǒng)計(jì)代碼插入到class中,而PowerMock當(dāng)使用了@PrepareForTest注解,在加載相關(guān)類的時(shí)候會(huì)從class文件重新讀取字節(jié)碼信息,導(dǎo)致JaCoCo的修改都沒有了,所以就沒辦法統(tǒng)計(jì)到了
on-the-fly模式的解決方案
1.不是用@PrepareForTest修飾要測覆蓋率的類,往需要覆蓋的類底層去mock,比如 A 依賴 B ,B依賴C,C依賴D。加入我們需要得到A和B的覆蓋率,那么我們在@PrepareForTest中就只添加C和D,只去mock C和D即可。
2.上述例子,假如我們A沒有依賴,但A中有成員變量需要mock,這個(gè)時(shí)候我們可以利用反射將成員變量設(shè)置為mock對象。然后再進(jìn)行mock操作,這樣就避免了再@PrepareForTest中加入A.class導(dǎo)致覆蓋率為零。
缺點(diǎn):
1.這樣寫對開發(fā)人員的要求比較高,要熟悉各種類庫(JDK、各種中間件),往代碼深處去mock代理取代在需要單元測試覆蓋率的類中進(jìn)行代理測試。
2.有些底層類不支持mock,比如Charset 的靜態(tài)方法中要求encode參數(shù)必須不能為空,powermock就沒法代理到Charset.encode的調(diào)用。
offline解決方案
使用jacoco的offline模式:文章來源:http://www.zghlxwxcb.cn/news/detail-628395.html
<!--- 定義 jacoco 版本 -->
<properties>
<jacoco.version>0.8.5</jacoco.version>
</properties>
<!--- 定義 jacoco 執(zhí)行 offline 模式 goals -->
<build>
<plugins>
<!-- 注意不是在pluginManagement, pluginManagement中只是聲明 -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<id>default-instrument</id>
<goals>
<goal>instrument</goal>
</goals>
</execution>
<execution>
<id>default-restore-instrumented-classes</id>
<goals>
<goal>restore-instrumented-classes</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<!--離線模式必需指定, 否則到模塊根目錄而不是target目錄了-->
<configuration>
<systemPropertyVariables>
<jacoco-agent.destfile>target/jacoco.exec</jacoco-agent.destfile>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
<!--- 定義 jacoco 依賴 注意不是在dependencyManagement, dependencyManagement中只是聲明-->
<dependencies>
<dependency>
<groupId>org.jacoco</groupId>
<artifactId>org.jacoco.agent</artifactId>
<version>${jacoco.version}</version>
<classifier>runtime</classifier>
</dependency>
</dependencies>
Offline模式單元測試不能跨模塊, 不能源碼在A模塊單測寫在B模塊
比方說:項(xiàng)目分成了四個(gè)模塊,service,utils,dao,controller,你在寫service單測的時(shí)候哪怕調(diào)用了utils的代碼,但是在實(shí)際統(tǒng)計(jì)覆蓋率的時(shí)候是沒辦法統(tǒng)計(jì)到的,需要單獨(dú)針對每一個(gè)模塊寫單測文章來源地址http://www.zghlxwxcb.cn/news/detail-628395.html
到了這里,關(guān)于Springboot 如何使用Powermock做單元測試的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!