首页 > 系统 > Android > 正文

Android单元测试框架源码分析(三)构建自己的单元测试框架

2019-11-06 09:49:29
字体:
来源:转载
供稿:网友

    分析完之前的源码后,或许我对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


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表