【Java精品源码栏目提醒】:网学会员鉴于大家对Java精品源码十分关注,论文会员在此为大家搜集整理了“如何测试Java的变量和方法 - 其它资料”一文,供大家参考学习
如何测试
Java 的变量和方法 浏览: 1 更新: 2013-05-20 14:3100对 于软件开发人员来说,单元测试是一项必不可少的工作。
它既可以验证程序的有效性,又可以在程序出现 BUG的时候,帮助开发人员快速的定位问题所在。
但是,在写单元测试的过程中,开发人员经常要访问类的一些非公有的成员变量或方法,这给测试工作带来了很大的困扰。
本文总结了访问类的非公有成员变量或方法的四种途径,以方便测试人员在需要访问类非公有成员变量或方法时进行选择。
尽 管有很多经验丰富的程序员认为不应该提倡访问类的私有成员变量或方法,因为这样做违反了
Java 语言封装性的基本规则。
然而,在实际测试中被测试的对象千奇百怪,为了有效快速的进行单元测试,有时我们不得不违反一些这样或那样的规则。
本文只讨论如何访问类的非公有成员变量或方法,至于是否应该在开发测试中这样做,则留给读者自己根据实际情况去判断和选择。
方法一:修改访问权限修饰符 先 介绍最简单也是最直接的方法,就是利用
Java语言自身的特性,达到访问非公有成员的目的。
说白了就是直接将 private 和 protected关键字改为 public或者直接删除。
我们建议直接删除,因为在
Java 语言定义中,缺省访问修饰符是包可见的。
这样做之后,我们可以另建一个
源码目录 -- test 目录多数 IDE支持这么做,如 Eclipse 和 JBuilder,然后将测试类放到 test目录相同包下,从而达到访问待测类的成员变量和方法的目的。
此时,在其它包的代码依然不能访问这些变量或方法,在一定程度上保障了程序的封装性。
下面的代码示例展示了这一方法。
清单 1. 原始待测类 A 代码 public class A private String name nullprivate void calculate 清单 2. 针对单元测试修改后的待测类 A 的代码 public class A String name null privatevoid calculate 这种方法虽然看起来简单粗暴,但经验告诉我们这个方法在测试过程中是非常有效的。
当然,由于改变了源代码,虽然只是包可见,也已经破坏了对象的封装性,对于多数对代码安全性要求严格的系统此方法并不可取。
方法二:利用安全管理器 安 全性管理器与反射机制相结合,也可以达到我们的目的。
Java运行时依靠一种安全性管理器来检验调用代码对某一特定的访问而言是否有足够的权限。
具体来说,安全性管理器是
java.lang.SecurityManager类或扩展自该类的一个类,且它在运行时检查某些应用程序操作的权限。
换句话说,所有的对象访问在执行自身逻辑之前都必须委派给安全管理器,当访问受到安全性管理器的控制,应用程序就只能执行那些由相关安全策略特别准许的操作。
因此安全管理器一旦启动可以为代码提供足够的保护。
默认情况下,安全性管理器是没有被设置的,除非代码明确地安装一个默认的或定制的安全管理器,否则运行时的访问控制检查并不起作用。
我们可以通过这一点在运行时避开
Java的访问控制检查,达到我们访问非公有成员变量或方法的目的。
为能访问我们需要的非公有成员,我们还需要使用
Java 反射技术。
Java反射是一种强大的工具,它使我们可以在运行时装配代码,而无需在对象之间进行源代码链接,从而使代码更具灵活性。
在编译时,
Java编译程序保证了私有成员的私有特性,从而一个类的私有方法和私有成员变量不能被其他类静态引用。
然而,通过
Java反射机制使得我们可以在运行时查询以及访问变量和方法。
由于反射是动态的,因此编译时的检查就不再起作用了。
下面的代码演示了如何利用安全性管理器与反射机制访问私有变量。
清单 3. 利用反射机制访问类的成员变量 //获得指定变量的值 public static ObjectgetValueObject instance String fieldName throws IllegalAccessExceptionNoSuchFieldException Field field getFieldinstance.getClass, fieldName// 参数值为 true禁用访问控制检查 field.setAccessibletrue return field.getinstance //该方法实现根据变量名获得该变量的值 public static Field getFieldClass thisClass StringfieldName throws NoSuchFieldException if thisClass null throw newNoSuchFieldExceptionError field 其中 getFieldinstance.getClass, fieldName通 过 反 射 机 制 获 得 对 象 属 性 , 如 果 存 在 安 全 管 理 器 , 方 法 首 先 使 用 this 和Member.DECLARED 作为参数调用安全管理器的checkMemberAccess 方法,这里的 this 是 this 类或者成员被确定的父类。
如果该类在包中,那么方法还使用包名作为参数调用安全管理器的checkPackageAccess 方法。
每一次调用都可能导致 SecurityException.当访问被拒绝时,这两种调用方式都会产生securityexception 异常 . setAccessibletrue 方法通过指定参数值为 true来禁用访问控制检查,从而使得该变量可以被其他类调用。
我们可以在我们所写的类中,扩展一个普通的基本类
java.lang.reflect.AccessibleObject 类。
这个类定义了一种 setAccessible方法,使我们能够启动或关闭对这些类中其中一个类的实例的接入检测。
这种方法的问题在于如果使用了安全性管理器,它将检测正在关闭接入检测的代码是否允许这样做。
如果未经允许,安全性管理器抛出一个例外。
除访问私有变量,我们也可以通过这个方法访问私有方法。
清单 4. 利用反射机制访问类的成员方法 public static Method getMethodObjectinstance String methodName Class classTypes throws NoSuchMethodException Method accessMethod getMethodinstance.getClass, methodName classTypes// 参 数 值 为 true 禁 用 访 问 控 制 检 查 accessMethod.setAccessibletrue returnaccessMethodprivate static Method getMethodClass thisClass String methodName ClassclassTypes throws NoSuchMethodException if thisClass null throw newNoSuchMethodExceptionError method try returnthisClass.getDeclaredMethodmethodName classTypes catchNoSuchMethodException e return getMethodthisClass.getSuperclass,methodName classTypes 获得私有方法的原理与获得私有变量的方法相同。
当我们得到了函数后,需要对它进行调用,这时我们需要通过invoke 方法来执行对该函数的调用,代码示例如下: //调用含单个参数的方法 public static ObjectinvokeMethodObject instance String methodName Object arg throwsNoSuchMethodException IllegalAccessException InvocationTargetException Object args new Object1 args0 arg return invokeMethodinstancemethodName args //调用含多个参数的方法 public static Object invokeMethodObjectinstance String methodName Object args throws NoSuchMethodExceptionIllegalAccessException InvocationTargetException Class classTypes nullif args null classTypes new Classargs.length for int i 0 i args.length i if argsi null classTypesi argsi.getClass return getMethodinstance methodName classTypes。
invokeinstance args 利用安全管理器及反射,可以在不修改
源码的基础上访问私有成员,为测试带来了极大的方便。
尤其是在编译期间,该方法可以顺利地通过编译。
但同时该方法也有一些缺点。
第一个是性能问题,用于字段和方法接入时反射要远慢于直接代码。
第二个是权限问题,有些涉及
Java 安全的程序代码并没有修改安全管理器的权限,此时本方法失效。
方法三:使用模仿Mock对象 在单元测试的过程中模仿对象被广泛使用。
它从测试中分离了外部的不需要的因素,并且帮助开发人员专注于被测试的功能。
模仿对象Mockobject的核心是构造一个伪类,在测试中通常用这个构造的伪类替换原来的需要访问相关环境如应用服务器,数据库等的需要测试的待测类,这样单元测试便可以运行在本地环境下这也是对单元测试的基本要求之一,不依赖于任何特定的环境,并可以正确的执行。
此外, 由于
Java语言不能多继承的特性,使得该方法也可以被用来作为非公有成员变量及方法的访问方法测试类不能同时继承 TestCase和待测类,利用该方法,在模仿对象中改变类成员的访问控制权限,从而达到访问非公有类变量及方法的目的。
下面的代码示例演示了模仿对象方法。
本方法的应用场景在单元测试中非常常见,即在待测试的公有方法中,有一些受限制的成员变量是由其它私有方法来初始化的,在测试该方法的时候,需要给这个变量置初值才能完成测试。
清单 5. 待测类 A public class A protected String s nullpublic A private void method s word System.out.printlnthis ismock test public void makeWord String prefix sSystem.out.printlnprefix is: prefix 在待测类 A 中,增加工厂方法。
清单 6. 包含工厂方法的待测类 A // 增加工厂方法的类 Apublic class A protectedString s null public A getA return new A private void method s word System.out.printlnthis is mock test public void makeWord String prefix s System.out.printlnprefix is: prefix //伪类,在运行时替换类Apublic class MockA extends A public String s null public MockA//测试类 public class TestA extends TestCase public void setup public voidteardown public void makeWordTest A a new MockA a.s testa.makeWord 此方法中有几个值得注意的地方,首先是将创建代码抽取到工厂方法中,在测试子类中覆盖该工厂方法,然后令被覆盖的方法返回模仿对象。
如果可以的话,添加需要原始对象的工厂方法的单元测试,以返回正确类型的对象。
模仿对象方法在处理许多对象依赖基础结构的其它对象或层时,可以起到很好的效果。
模仿对象符合实际对象的接口,但只要有足够的代码来欺骗测试对象并跟踪其行为。
例如,在单元测试中需要测试一个使用数据库的对象,或者需要测试连接 J2EE应用服务器的对象,通常的测试用例需要安装、配置和发送本地数据库副本、运行测试然后再卸装本地数据库或者需要安装、配置应用服务器、运行测试然后再卸装应用服务器,操作可能很麻烦,.模仿对象提供了解决这一困难的途径。
对于既需要访问相关环境又要访问非公有变量或方法的类来说,模仿对象非常适合,但是,如果只是访问非公有变量或方法,那么传统的模仿对象法显得有些笨重,可以对该法进行简化,不使用工厂方法,达到同样的效果。
下面的代码示例演示了经过简化的模仿对象方法: 清单 7. 简化的待测类 A 的模仿对象 //伪类,在运行时替换类 Apublic class MockA extends Apublic MockA super s test //测试类 public class TestA extends TestCasepublic void setup public void teardown public void makeWordTest A a new MockA a.makeWord 模仿对象方法既能消除运行环境的影响,又能解决多继承的难题,但是由于该方法使用子类的实例来替代父类的实例,对于私有成员变量及方法来说,仍然不能进行访问。
方法四:利用字节码技术
Java 编译器把
Java 源代码编译成字节码bytecode字节码,既然在测试中尽量要避免改变原来的代码,那么最直接的改造
Java类的方法莫过于直接改写 class文件。
通过修改字节码中的关键字,将私有的成员变量及方法改成公有的成员变量及方法,可以做到在不改变
源码的情况下访问到需要的成员变量及方法。
Java 规范有class 文件的格式的详细说明,直接编辑字节码确实可以改变
Java 类的行为,但是这也要求使用者对
Java class文件有较深的理解。
目前,比较流行的字节码处理工具有 JavassistBCEL 和 ASM等。
这几种工具各有特点,适合于不同的应用场景,如果读者对字节码技术感兴趣,可以阅读后面的参考文献。
本文选择利用字节码工具 ASM. ASM 能被用来动态生成类或者修改既有类的功能。
它可以直接产生二进制 class文件,也可以在类被加载入
Java 虚拟机之前动态改变类行为。
Java class 被存储在严格格式定义的 .class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及
Java 字节码指令。
ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
class。
ASM 作为
Java字节码操控框架,是所有同类工具中效率最高的一个,并且由于其采用了基于 Vistor模式的框架设计,它也是同类工具中最轻巧灵活的,尽管它的学习台阶相对要高一些,它仍然是达到本文目的的首选。
利 用 ASM访 问 私 有 变 量 及 方 法 , 需 要 了 解 的 比 较 重 要 的 几 个 类 : ClassReader 、 ClassVistor 、MethodVisitor、 FieldVisitor 和ClassAdaptor 等。
ClassReader 类可以直接由字节数组或由 class 文件间接的获得字节码数据,它能正确的分析字节码,通过调用accept 方 法 接 受 一 个 ClassVisitor 接 口 的 实 现 类 实 例 作 为 参 数 , 然 后 依 次 调 用ClassVisitor 接口的各个方法ClassVisitor接口中 定义了 对应
Java 类各 个成员 的访 问函 数,比 如 visitMethod 会 返 回一个 实现MethordVisitor 接口的实例,visitField会返回一个实现 FieldVisitor 接口的实例。
不同 Visitor 的组合,可以非常简单的封装对字节码的各种修改ClassAdaptor 类为ClassVisitor 接口提供了一个默认实现。
创建一个 ClassAdaptor 对象实例时,需要传入一个 ClassVisitor接口的实现类实例来访问字节吗。
因此当我们需要对字节码进行调整时,只需从ClassAdaptor类派生出一个子类,覆写需要修改的方法,完成相应功能后再把调用传递到下一个需要修改的 visitor 即可。
本例的应用场景为,要对公有方法 method进行单元测试,但是,该方法中有一个私有变量 number 是由另一个私有方法 makePaper付值,所以,需要在测试中为该私有变量置初值。
清单 8. 待测类 A class A private String number publicvoid method ifnumber.eaqualsprefixSystem.out.printlnmethod…number else System.out.printlnnumber isnull private void makePaper numberprefixSystem.out.printlnmakePaper… 清单 9. 使用字节码访问类 A //修改变量的修饰符 public class AccessClassAdapterextends ClassAdapter public AccessClassAdapterClassVisitor cv supercv public FieldVisitor visitFieldfinal int access String name final String descfinal String signature final Object value int privateAccess access// 找 到 名 字 为 number 的 变 量 if name.equalsnumber privateAccess Opcodes.ACC_PUBLIC// 修 字 段 的 修 饰 符 为 public: 在 职 责 链 传 递 过 程 中 替 换 调 用 参 数 returncv.visitFieldprivateAccess name descsignature value public static void mainString args throws Exception ClassReader cr new ClassReaderA ClassWriter cw newClassWriterClassWriter.COMPUTE_MAXS ClassAdapter classAdapter newAccessClassAdaptercw cr.acceptclassAdapter ClassReader.SKIP_DEBUG bytedata cw.toByteArray //生成新的字节码文件 File file new FileA.classFileOutputStream fout new FileOutputStreamfile fout.writedatafout.close 执行完该类,将产生一个新的 A.class 文件。
测试类测试 method 方法,先对变量进行置初值,然后就可以像其他单元测试一样,对method 方法进行测试。
回页首 方法对比 方法 修饰符 使用难度 缺陷 protected 缺省 private 方法一:修改访问权限修饰符 是 是 是 低,有
java 编程基础即可。
由于需要修改源代码,虽然是同包可见,也会带来一些封闭性的问题。
方法二:利用安全性管理器 是 是 是 中,需要了解
java 安全性管理器及反射机制。
一些对代码安全有要求的程序,程序员并没有修改 security manager 的权限,此时,安全管理器方法失效。
方法三:使用模仿对象 是 是 否 较高,需要了解设计模式和待测对象的内部实现细节。
由于模仿对象要求伪类必需和待测类是继承与被继承的关系,所以当
源码以 private 关键字修饰时,此方法失效。
方法四:利用字节码技术 是 是 是 高,需要操作和改写类部分的字节码。
学习成本高,需要了解
Java 字节码技术
上一篇:
java中map集合的用法
下一篇:
下鼻甲不同术式对鼻黏膜纤毛功能的影响