jmockit使用總結-MockUp重點介紹


  公司對開發人員的單元測試要求比較高,要求分支覆蓋率、行覆蓋率等要達到60%以上等等。項目中已經集成了jmockit這個功能強大的mock框架,學會使用這個框架勢在必行。從第一次寫一點不會,到完全可以應付工作要求,期間踩了好多坑,學到了不少東西。下面簡單總結一下jmockit這個框架的使用,重點介紹MockUp的使用,因為項目中都采用此種方式模擬方法。

一、框架集成

添加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>

 

二、@Mocked模擬方式介紹

@Mocked模擬,由錄制、回放、驗證三步驟完成,是對某個類的所有實例的所有方法進行完整的模擬方式。

/**
 * 被測試類
 */
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;
        }};

    }
}

 

三、@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;
            }
        };

    }
}

 

四、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,未被模擬,方法實際值

    }
}

 

五、MockUp局部模擬,可重寫原有方法的邏輯,比較靈活,推薦使用

/**
 * 測試類
 */
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,未被模擬,方法實際值

    }
}

 

六、MockUp如何模擬私有方法、靜態方法、靜態塊、構造函數等

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
    }
    
}

 

七、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
    }
}

 

八、MockUp單元測試用例單個跑正常,批量跑失敗可能的原因

1.測試方法使用了共享變量,相互影響。

2.在一個測試方法里多次MockUp同一個類,將某個類需要mock的方法均寫在一個new MockUp里即可解決,原因未知。

3.在測試方法里的MockUp,在方法結束前,調用一下mockUp.tearDown。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM