您的位置:首页 > 移动开发 > Android开发

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

2017-02-27 21:43 501 查看
    分析完之前的源码后,或许我对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-----------");
}
el
4000
se
{
System.out.println("-----------test failure-----------");
}
}
}


    这里直接在测试用例里面输出,根据测试输出判断测试结果。

    代码地址:http://download.csdn.net/detail/xiaoshixiu/9765668
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息