測(cè)試場(chǎng)景及問題
筆者在對(duì)某個(gè)JAVA socket通信程序進(jìn)行UT的時(shí)候浊猾,遇到過以下一個(gè)場(chǎng)景槐沼,
客戶端發(fā)出登陸請(qǐng)求,然后每隔500ms監(jiān)查一下底層通信機(jī)的登陸狀態(tài)润绵,如果登陸成功线椰,底層通信機(jī)會(huì)將其狀態(tài)修改為L(zhǎng)OGIN_SUCCESS/LOGIN_FAILED〕九危客戶端檢查時(shí)如果發(fā)現(xiàn)登陸狀態(tài)不是上述兩個(gè)狀態(tài)憨愉,則線程休眠500ms然后繼續(xù)監(jiān)查。上述邏輯要重復(fù)30次卿捎,也就是15秒后配紫,如果登陸狀態(tài)不是上述成功/失敗的狀態(tài),則表示未收到登陸答復(fù)等邏輯午阵,需要切換服務(wù)器繼續(xù)登陸躺孝。
一個(gè)簡(jiǎn)化的過程如下:
public class SystemClass {
int j=0;
public void callThead() {
for(int i=0;i<30;i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
//e.printStackTrace();
break;
}
//end for
j++;
}
}
為了模擬登陸請(qǐng)求未收到答復(fù)的場(chǎng)景(如網(wǎng)絡(luò)延遲),需要走完整個(gè)循環(huán)底桂,也就是說(shuō)需要等待500*30ms=15s的時(shí)間括细。對(duì)于UT 來(lái)說(shuō),這個(gè)時(shí)間是無(wú)法接受的戚啥。
解決辦法及代碼
因此奋单,我們需要對(duì)Thread.class進(jìn)行mock,縮短等待時(shí)間猫十。 方案是在Thread.sleep方法被調(diào)用時(shí)览濒,直接拋出InterruptedException 呆盖,讓程序退出整個(gè)循環(huán)。示例程序如下:
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.rule.PowerMockRule;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.doThrow;
import static org.junit.Assert.assertEquals;
/**
* Test class to demonstrate static mocking with PowerMockito.
*/
//@RunWith(PowerMockRunner.class) // using @Rule
@PrepareForTest( { SystemClass.class})
public class SystemClassTest {
@Rule
public PowerMockRule rule = new PowerMockRule();
@Test
public void testMockStaticThatThrowsException() throws Exception {
spy(Thread.class);
// Expectations
doThrow(new InterruptedException()).when(Thread.class);
Thread.sleep(Mockito.anyLong());
//Test
SystemClass systemClass = new SystemClass();
systemClass.callThead();
assertEquals(0, systemClass.j);
}
}
案例分析
對(duì)于mock的挑戰(zhàn)有兩個(gè)贷笛,首先Thread.sleep是一個(gè)靜態(tài)方法应又;其次,該方法沒有返回值乏苦。對(duì)于Mockito等mock工具來(lái)說(shuō)株扛,這就是無(wú)法解決的問題了。得益于Powermockito對(duì)于靜態(tài)方法進(jìn)行Mock的能力汇荐,使用如下格式洞就,就實(shí)現(xiàn)了預(yù)期的調(diào)用Thread.sleep時(shí)拋出中斷異常的行為,從而實(shí)現(xiàn)了大大縮短程序執(zhí)行時(shí)間的目標(biāo)掀淘。
// Expectations
doThrow(new InterruptedException()).when(Thread.class);
Thread.sleep(Mockito.anyLong());
當(dāng)然旬蟋,為了能夠mock某個(gè)類的靜態(tài)方法,需要在測(cè)試類上加上一下注解革娄,
@PrepareForTest( { SystemClass.class})
并且使用Powermockito.spy這個(gè)方法來(lái)部分mock Thread這個(gè)類倾贰。
spy(Thread.class);
另外,通過以下斷言也能證實(shí)上述mock效果的有效性拦惋。
assertEquals(0, systemClass.j);
由于在循環(huán)的第一次執(zhí)行過程中就中斷退出匆浙,因此變量j==0;
實(shí)際程序中的斷言也可參考這種方式。
遺留問題
由于Thread.class來(lái)自java.lang厕妖,是Java語(yǔ)言的一部分吞彤,因此對(duì)于Thread的mock會(huì)引起同樣需要修改java 字節(jié)碼的jacoco覆蓋率統(tǒng)計(jì)工具的沖突,導(dǎo)致無(wú)法dump測(cè)試覆蓋率報(bào)告叹放。該問題將另文介紹饰恕。