公司對開發人員的單元測試要求比較高,要求分支覆蓋率、行覆蓋率等要達到60%以上等等。項目中已經集成了jmockit這個功能強大的mock框架,學會使用這個框架勢在必行。從第一次寫一點不會,到徹底能夠應付工做要求,期間踩了好多坑,學到了很多東西。下面簡單總結一下jmockit這個框架的使用,重點介紹MockUp的使用,由於項目中都採用此種方式模擬方法。json
1、框架集成app
添加maven依賴框架
<dependencies> <!-- jmockit必須寫在junit以前 --> <dependency> <groupId>org.jmockit</groupId> <artifactId>jmockit</artifactId> <version>1.16</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
2、@Mocked模擬方式介紹maven
@Mocked模擬,由錄製、回放、驗證三步驟完成,是對某個類的全部實例的全部方法進行完整的模擬方式。ide
/** * 被測試類 */ public class App { public String say() { return "Hello World"; } public String say2(){ return "Hello World 2"; } public static String staticSay() { return "Still hello world"; } }
/** * 測試類 */ public class AppTest { /** * 針對類及全部實例的的總體模擬,未寫錄製的方法默認返回0,null等 */ @Mocked App app; @Test public void testSay() { //錄製,定義被模擬的方法的返回值,能夠錄製多個行爲,寫在一個大括號裏也能夠,多個大括號隔開也能夠 new Expectations() {{ app.say(); result = "say"; }}; //回放,調用模擬的方法 System.out.println(app.say()); //say System.out.println(new App().say()); //say System.out.println(App.staticSay()); //null //驗證 new Verifications() {{ //驗證say模擬方法被調用,且調用了2次 app.say(); times = 2; //驗證staticSay模擬方法被調用,且調用了1次 App.staticSay(); times = 1; }}; } }
3、@Injectable模擬方式介紹函數
@Injectable和@Mocked的方式很像,區別是@Injectable僅僅對當前實例進行模擬。 單元測試
/** * 測試類 */ public class AppTest001 { /** * 僅針對當前實例的總體模擬 */ @Injectable App app; @Test public void testSay() { //錄製 new Expectations() {{ app.say(); result = "say"; }}; //回放 System.out.println(app.say()); //say,模擬值 System.out.println(app.say2()); //null,模擬默認值 final App appNew = new App(); System.out.println(appNew.say()); //Hello World,未被模擬,方法實際值 System.out.println(App.staticSay()); //Still hello world,未被模擬,方法實際值 //驗證 new Verifications() { { //驗證say模擬方法被調用 app.say(); times = 1; } { appNew.say(); times = 1; } { //驗證staticSay模擬方法被調用,且調用了1次 App.staticSay(); times = 1; } }; } }
4、Expectations傳參,局部模擬測試
/** * 測試類 */ public class AppTest002 { @Test public void testSay() { final App app = new App(); //錄製,帶參數表示局部模擬【針對全部實例的局部模擬,具體到某個方法的模擬,其它方法不模擬】 new Expectations(App.class) {{ app.say(); result = "say"; }}; //回放 System.out.println(app.say()); //say,模擬值 System.out.println(app.say2()); //Hello World 2 ,未被模擬,方法實際值 System.out.println(new App().say()); //say,模擬值 System.out.println(App.staticSay()); //Still hello world,未被模擬,方法實際值 } }
5、MockUp局部模擬,可重寫原有方法的邏輯,比較靈活,推薦使用spa
/** * 測試類 */ public class AppTest003 { @Test public void testSay() { //局部模擬【針對全部實例的局部模擬,具體到某個方法的模擬,其它方法不模擬】 new MockUp<App>(App.class){ @Mock String say(){ return "say"; } }; //回放 System.out.println(new App().say()); //say,模擬值 System.out.println(new App().say2()); //Hello World 2,未被模擬,方法實際值 System.out.println(App.staticSay()); //Still hello world,未被模擬,方法實際值 } }
6、MockUp如何模擬私有方法、靜態方法、靜態塊、構造函數等code
1.模擬私有屬性(實例屬性和類屬性),MockUp不支持,採用以下方式
//模擬實例的字段 Deencapsulation.setField(Object objectWithField, String fieldName, Object fieldValue) //模擬類的靜態字段 Deencapsulation.setField(Class<?> classWithStaticField, String fieldName, Object fieldValue)
2.模擬私有方法,MockUp不支持,採用以下方式
//模擬實例方法,注意參數不能爲null,若是要傳null請使用帶參數類型的另外一個重載方法 Deencapsulation.invoke(Object objectWithMethod, String methodName, Object... nonNullArgs) //模擬類方法 Deencapsulation.invoke(Class<?> classWithStaticMethod, String methodName, Object... nonNullArgs)
3.模擬靜態方法
和模擬實例方法同樣,去掉static便可
4.模擬靜態塊
//mock靜態代碼塊 @Mock void $clinit(Invocation invocation){ }
5.模擬實例塊和構造函數
//mock代碼塊和構造函數 @Mock void $init(Invocation invocation) { }
6.模擬接口,MockUp不支持,採用@Capturing
/** * 被模擬的接口 */ public interface IUserService { String getUserName( ); }
public class UserServiceImpl implements IUserService { @Override public String getUserName() { return "Bob"; } }
/** * 接口模擬測試 */ public class IUserServiceTest { /** * MockUp不能mock接口方法,能夠用來生成接口實例 */ @Test public void getUserNameTest001(){ MockUp<IUserService> mockUp = new MockUp<IUserService>(){ @Mock String getUserName( ){ return "Jack"; } }; IUserService obj = new UserServiceImpl(); System.out.println(obj.getUserName()); //Bob,mock失敗 obj = mockUp.getMockInstance(); System.out.println(obj.getUserName()); //Jack,mockUp生成的實例,和本身寫一個接口實現同樣 obj = new UserServiceImpl(); System.out.println(obj.getUserName()); //Bob,mock失敗 } /** * @Capturing 註解能夠實現mock接口,全部實現類的實例均被mock * @param base */ @Test public void getUserNameTest002(@Capturing final IUserService base){ IUserService obj = new UserServiceImpl(); System.out.println(obj.getUserName()); //mock成功,返回模擬默認值null //錄製 new Expectations(){ { base.getUserName(); result = "Jack"; } }; System.out.println(obj.getUserName()); //Jack obj = new IUserService() { @Override public String getUserName() { return "Alice"; } }; System.out.println(obj.getUserName()); //Jack } }
7、MockUp模擬方法中調用原方法
/** * 模擬方法調用原方法邏輯測試 */ public class JSONObjectTest { @Test public void getTest(){ JSONObject jsonObject = new JSONObject(); jsonObject.put("a","A"); jsonObject.put("b","B"); jsonObject.put("c","C"); System.out.println(jsonObject.get("a")); //A System.out.println(jsonObject.get("b")); //B System.out.println(jsonObject.get("c")); //C new MockUp<JSONObject>(){ @Mock Object get(Invocation invocation,Object key){ if("a".equals(key)){ return "aa"; }else{ //調用原邏輯 return invocation.proceed(key); } } }; System.out.println(jsonObject.get("a")); //aa System.out.println(jsonObject.get("b")); //B System.out.println(jsonObject.get("c")); //C } }
8、MockUp單元測試用例單個跑正常,批量跑失敗可能的緣由
1.測試方法使用了共享變量,相互影響。
2.在一個測試方法裏屢次MockUp同一個類,將某個類須要mock的方法均寫在一個new MockUp裏便可解決,緣由未知。