Android单元测试框架源码分析(三)构建自己的单元测试框架
2017-02-27 21:43
501 查看
分析完之前的源码后,或许我对Android单元测试有了一定的了解,但是如果要深入Android单元测试,就必须尝试自己编写Android单元测试框架。现在流行的Android单元测试框架期初都并不完美,我们开始编写框架时不要考虑太多细节,首先构建测试框架的骨架,所以我们先构建Java单元测试框架,然后在此基础上修改。
简单描述下框架运行流程就是:1. 收集测试信息 2. 运行自定义类加载器 3. 自定义类加载器加载测试用例 4. 原类被替换为模拟类 5. 返回测试结果
为了方便测试,我们假设现在有个天气预报站,要从数据中心收集数据进行预报,我们要测试预报的准确性,测试思路无非是模拟数据中心的数据,判断预报的数据和测试中心的数据是否一致。
根据以上信息,我们来设计自己的单元测试框架:
首先看第一步,收集测试信息
为了方便,我并没有使用@Test来获取测试用例,而是直接通过反射调用测试用例
第二步,运行类加载器
此处使用自定义类加载器是继承URLClassLoader,使用此URLClassLoader时可以通过直接指定Jar包目录来指定依赖库地址。
使用ClassLoader的关键之处在于重写findClass类,当匹配到的类名为指定的类名时,则交给ASM来修改字节码。
第三步,其实就是上述第一步代码中的反射调用。
第四部,原始类被替换为模拟类
这里简单的使用了ASM,使用ASM找到了getWeatherData方法,并且在返回常量值得时候返回指定的常量,这样就实现了方法返回值的替换。
第五步,返回测试信息
这里直接在测试用例里面输出,根据测试输出判断测试结果。
代码地址:http://download.csdn.net/detail/xiaoshixiu/9765668
简单描述下框架运行流程就是: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
相关文章推荐
- Android单元测试框架源码分析(一)浅析Mockito
- Android单元测试框架源码分析(二)浅析Robolectric
- 构建自己的Android账户与内容同步机制,例程SampleSyncAdapter的分析
- Android图片异步加载框架Universal Image Loader的源码分析
- Android Sqlite框架 GreenDao的源码分析笔记
- android轻量级开源缓存框架——ASimpleCache(ACache)源码分析
- 如何构建自己的游戏框架并且制作游戏(二)(附源码)
- cxxtest单元测试框架源码分析(一):类的组成关系
- Android网络框架-Volley(二) RequestQueue源码分析以及建立一个RequestQueue
- android2.3 View视图框架源码分析之一:android是如何创建一个view的?
- 【Android开源项目分析】android轻量级开源缓存框架——ASimpleCache(ACache)源码分析
- android2.3 View视图框架源码分析之一:android是如何创建一个view的?
- android adb 源码框架分析(5 客户端)
- android2.3 View视图框架源码分析之一:android是如何创建一个view的?
- Android网络框架-Volley(三) CacheDispatcher和NetworkDispatcher源码分析
- Android 最好的数据库框架 ORMLite的分析 (附时序图和自己写的DEMO)
- android adb 源码框架分析(1 系统)
- 如何构建自己的游戏框架并且制作游戏(一)(附源码)
- View视图框架源码分析之一:android是如何创建一个view
- Android 轻量级ORM数据库开源框架ActiveAndroid 源码分析