Android單元測試框架源碼分析(三)構建自己的單元測試框架

    分析完之前的源碼後,或許我對Android單元測試有了一定的瞭解,但是如果要深入Android單元測試,就必須嘗試自己編寫Android單元測試框架。現在流行的Android單元測試框架期初都並不完美,我們開始編寫框架時不要考慮太多細節,首先構建測試框架的骨架,所以我們先構建Java單元測試框架,然後在此基礎上修改。

    簡單描述下框架運行流程就是:1. 收集測試信息 2. 運行自定義類加載器 3. 自定義類加載器加載測試用例 4. 原類被替換爲模擬類 5. 返回測試結果

    爲了方便測試,我們假設現在有個天氣預報站,要從數據中心收集數據進行預報,我們要測試預報的準確性,測試思路無非是模擬數據中心的數據,判斷預報的數據和測試中心的數據是否一致。

public class Station
{
    public Object getWeatherData()
    {
        return "WeatherData";
    }
}

public class Broadcast
{
    public String broadcastWeather(Object object)
    {
        System.out.println("broadcastWeather:"+object);
        return "broadcastWeather:"+object;
    }
}

    根據以上信息,我們來設計自己的單元測試框架:



    首先看第一步,收集測試信息

   

public class CustomUnitTestRunner
{
    public static void main(String[] args)
    {
        try
        {
            //通過類加載器加載測試用例
            Class testSuteClazz=UnitTestClassLoader.getInstance().loadClass("customunittestarchitecture.TestSute");
            Object testSute=testSuteClazz.newInstance();

            //運行測試用例
            Method method=testSuteClazz.getMethod("testWeatherForcast");
            method.invoke(testSute);

        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
}

     爲了方便,我並沒有使用@Test來獲取測試用例,而是直接通過反射調用測試用例


    第二步,運行類加載器

public class UnitTestClassLoader extends URLClassLoader
{
    //使用URLClassLoader必須要制定jar包目錄
    private static String defaultWorkhomePath="E:\\workhome\\IDEA\\ExampleForUnitTese\\out\\production\\ExampleForUnitTese\\";
    private static String defaultJarPath1="E:\\workhome\\IDEA\\ExampleForUnitTese\\lib\\cglib-3.2.4.jar";
    private static String defaultJarPath2="E:\\workhome\\IDEA\\ExampleForUnitTese\\lib\\asm-5.2.jar";

    private byte[] _bytes;
    private Class clazz;

    public UnitTestClassLoader(URL[] urls, ClassLoader parent)
    {
        super(urls, parent);
    }

    public UnitTestClassLoader(URL[] urls)
    {
        super(urls);
    }

    public UnitTestClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory)
    {
        super(urls, parent, factory);
    }

    private static UnitTestClassLoader _instance;

    public static UnitTestClassLoader getInstance()
    {
        if(_instance==null)
        {
            try
            {
                //指定jar包目錄
                _instance=new UnitTestClassLoader(new URL[]{
                        new File(defaultJarPath1).toURI().toURL(),
                        new File(defaultJarPath2).toURI().toURL(),
                        new File(defaultWorkhomePath).toURI().toURL()},null);
            }
            catch(Exception e)
            {
                _instance=new UnitTestClassLoader(new URL[]{},null);
                e.printStackTrace();
            }
        }
        return _instance;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException
    {
        //對比模擬類,如果需要模擬,就利用ASM修改字節碼,動態生成模擬類
        if(name.equals(Station.class.getName()))
        {
            byte[] bytes=AsmCore.getClassByteArray(Station.class.getName());
            return defineClass(name,bytes,0,bytes.length);
        }
        return super.findClass(name);
    }
}

    此處使用自定義類加載器是繼承URLClassLoader,使用此URLClassLoader時可以通過直接指定Jar包目錄來指定依賴庫地址。

    使用ClassLoader的關鍵之處在於重寫findClass類,當匹配到的類名爲指定的類名時,則交給ASM來修改字節碼。



    第三步,其實就是上述第一步代碼中的反射調用。

 

   第四部,原始類被替換爲模擬類

public class AsmCore
{
    public static byte[] getClassByteArray(String className)
    {
        try
        {
            //根據傳進來的類名作爲ClassReader的參數
            ClassReader classReader=new ClassReader(className);
            //ASM通過職責鏈的設計模式來訪問類的字節碼
            ClassWriter classWriter=new ClassWriter(classReader,ClassWriter.COMPUTE_MAXS);
            //通過添加職責鏈來控制輸出的字節碼
            ClassVisitor classVisitor=new ChangeClassClassVisitor(Opcodes.ASM5,classWriter);
            classReader.accept(classVisitor,Opcodes.ASM5);

            byte[] bytes=classWriter.toByteArray();
            File file = new File("E:\\workhome\\TestForAsm\\Station$$$.class");
            FileOutputStream fout = new FileOutputStream(file);
            //這裏通過輸出字節碼到文件方便測試
            fout.write(bytes);
            fout.close();

            return bytes;
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        return new byte[]{};
    }

    static class ChangeClassClassVisitor extends ClassVisitor
    {

        public ChangeClassClassVisitor(int i)
        {
            super(i);
        }

        public ChangeClassClassVisitor(int i, ClassVisitor classVisitor)
        {
            super(i, classVisitor);
        }

        @Override
        public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions)
        {
            //ClassVisit裏面捕捉方法信息
            if(name.equals("getWeatherData"))
            {
                MethodVisitor visitor= super.visitMethod(access, name, desc, signature, exceptions);
                return new ChangeMethodMethdVisiter(Opcodes.ASM5,visitor);
            }
            return super.visitMethod(access, name, desc, signature, exceptions);
        }
    }

    static  class ChangeMethodMethdVisiter extends MethodVisitor
    {

        public ChangeMethodMethdVisiter(int i)
        {
            super(i);
        }

        public ChangeMethodMethdVisiter(int i, MethodVisitor methodVisitor)
        {
            super(i, methodVisitor);
        }


        @Override
        public void visitInsn(int i)
        {
            //MethodVisit裏面捕捉返回信息
            if(i==ARETURN)
            {
                mv.visitLdcInsn("MyCustomData");
            }
            super.visitInsn(i);
        }
    }
}

    這裏簡單的使用了ASM,使用ASM找到了getWeatherData方法,並且在返回常量值得時候返回指定的常量,這樣就實現了方法返回值的替換。


    第五步,返回測試信息

public class TestSute
{
    public void testWeatherForcast()
    {
        Station station=new Station();
        Broadcast broadcast=new Broadcast();
        String log=broadcast.broadcastWeather(station.getWeatherData());
        if(log.equals("broadcastWeather:MyCustomData"))
        {
            System.out.println("-----------test success-----------");
        }
        else
        {
            System.out.println("-----------test failure-----------");
        }
    }
}

    這裏直接在測試用例裏面輸出,根據測試輸出判斷測試結果。


    代碼地址:http://download.csdn.net/detail/xiaoshixiu/9765668


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章