知识大全 测试 Java 类的非公有成员变量和方法

Posted 变量

篇首语:学向勤中得,萤窗万卷书。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 测试 Java 类的非公有成员变量和方法相关的知识,希望对你有一定的参考价值。

测试 Java 类的非公有成员变量和方法  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!

  对于软件开发人员来说 单元测试是一项必不可少的工作 它既可以验证程序的有效性 又可以在程序出现 BUG 的时候 帮助开发人员快速的定位问题所在 但是 在写单元测试的过程中 开发人员经常要访问类的一些非公有的成员变量或方法 这给测试工作带来了很大的困 扰 本文总结了访问类的非公有成员变量或方法的四种途径 以方便测试人员在需要访问类非公有成员变量或方法时进行选择         尽管有很多经验丰富的程序员认为不应该提倡访问类的私有成员变量或方法 因为这样做违反了 Java 语言封装性的基本规则 然而 在实际测试中被测试的对象千奇百怪 为了有效快速的进行单元测试 有时我们不得不违反一些这样或那样的规则 本文只讨论如何 访问类的非公有成员变量或方法 至于是否应该在开发测试中这样做 则留给读者自己根据实际情况去判断和选择         方法一 修改访问权限修饰符        先介绍最简单也是最直接的方法 就是利用 Java 语言自身的特性 达到访问非公有成员的目的 说白了就是直接将 private 和 protected 关键字改为 public 或者直接删除 我们建议直接删除 因为在 Java 语言定义中 缺省访问修饰符是包可见的 这样做之后 我们可以另建一个源码目录 test 目录(多数 IDE 支持这么做 如 Eclipse 和 JBuilder) 然后将测试类放到 test 目录相同包下 从而达到访问待测类的成员变量和方法的目的 此时 在其它包的代码依然不能访问这些变量或方法 在一定程度上保障了程序的封装性         下面的代码示例展示了这一方法         清单 原始待测类 A 代码        public class A private String name = null; private void calculate()         清单 针对单元测试修改后的待测类 A 的代码        public class A String name = null; private void calculate()         这种方法虽然看起来简单粗暴 但经验告诉我们这个方法在测试过程中是非常有效的 当然 由于改变了源代码 虽然只是包可见 也已经破坏了对象的封装性 对于多数对代码安全性要求严格的系统此方法并不可取         方法二 利用安全管理器        安全性管理器与反射机制相结合 也可以达到我们的目的 Java 运行时依靠一种安全性管理器来检验调用代码对某一特定的访问而言是否有足够的权限 具体来说 安全性管理器是 java lang SecurityManager 类或扩展自该类的一个类 且它在运行时检查某些应用程序操作的权限 换句话说 所有的对象访问在执行自身逻辑之前都必须委派给安全管理器 当访问受到安全 性管理器的控制 应用程序就只能执行那些由相关安全策略特别准许的操作 因此安全管理器一旦启动可以为代码提供足够的保护 默认情况下 安全性管理器是没 有被设置的 除非代码明确地安装一个默认的或定制的安全管理器 否则运行时的访问控制检查并不起作用 我们可以通过这一点在运行时避开 Java 的访问控制检查 达到我们访问非公有成员变量或方法的目的 为能访问我们需要的非公有成员 我们还需要使用 Java 反射技术 Java 反射是一种强大的工具 它使我们可以在运行时装配代码 而无需在对象之间进行源代码链接 从而使代码更具灵活性 在编译时 Java 编译程序保证了私有成员的私有特性 从而一个类的私有方法和私有成员变量不能被其他类静态引用 然而 通过 Java 反射机制使得我们可以在运行时查询以及访问变量和方法 由于反射是动态的 因此编译时的检查就不再起作用了         下面的代码演示了如何利用安全性管理器与反射机制访问私有变量         清单 利用反射机制访问类的成员变量        //获得指定变量的值public static Object getValue(Object instance String fieldName) throws IllegalAccessException NoSuchFieldException Field field = getField(instance getClass() fieldName) // 参数值为true 禁用访问控制检查 field setAccessible(true) return field get(instance) //该方法实现根据变量名获得该变量的值public static Field getField(Class thisClass String fieldName) throws NoSuchFieldException if (thisClass == null) throw new NoSuchFieldException( Error field ! )         其中 getField(instance getClass() fieldName) 通过反射机制获得对象属性 如果存在安全管理器 方法首先使用 this 和 Member DECLARED 作为参数调用安全管理器的 checkMemberAccess 方法 这里的 this 是 this 类或者成员被确定的父类 如果该类在包中 那么方法还使用包名作为参数调用安全管理器的 checkPackageAccess 方法 每一次调用都可能导致 SecurityException 当访问被拒绝时 这两种调用方式都会产生 securityexception 异常         setAccessible(true) 方法通过指定参数值为 true 来禁用访问控制检查 从而使得该变量可以被其他类调用 我们可以在我们所写的类中 扩展一个普通的基本类 java lang reflect AccessibleObject 类 这个类定义了一种 setAccessible 方法 使我们能够启动或关闭对这些类中其中一个类的实例的接入检测 这种方法的问题在于如果使用了安全性管理器 它将检测正在关闭接入检测的代码是否允许 这样做 如果未经允许 安全性管理器抛出一个例外         除访问私有变量 我们也可以通过这个方法访问私有方法         清单 利用反射机制访问类的成员方法        public static Method getMethod(Object instance String methodName Class[] classTypes)        throws NoSuchMethodException Method accessMethod = getMethod(instance getClass() methodName classTypes)         //参数值为true 禁用访问控制检查 accessMethod setAccessible(true)         return accessMethod;        private static Method getMethod(Class thisClass String methodName Class[] classTypes)        throws NoSuchMethodException if (thisClass == null)                throw new NoSuchMethodException( Error method ! )                 try return thisClass getDeclaredMethod(methodName classTypes)                 catch        (NoSuchMethodException e)                return getMethod(thisClass getSuperclass() methodName classTypes)                

        获得私有方法的原理与获得私有变量的方法相同 当我们得到了函数后 需要对它进行调用 这时我们需要通过 invoke() 方法来执行对该函数的调用 代码示例如下         //调用含单个参数的方法public static Object invokeMethod(Object instance String methodName Object arg)        throws NoSuchMethodException IllegalAccessException InvocationTargetException Object[] args = new Object[ ];        args[ ] = arg;        return invokeMethod(instance methodName args)         //调用含多个参数的方法public static Object invokeMethod(Object instance String methodName Object[] args)        throws NoSuchMethodException IllegalAccessException InvocationTargetException Class[] classTypes = null;        if (args != null)        classTypes = new Class[args length];        for (int i = ;        i < args length;        i++)        if (args[i] != null)        classTypes[i] = args[i] getClass()                         return getMethod(instance methodName classTypes) invoke(instance args)                 利用安全管理器及反射 可以在不修改源码 的基础上访问私有成员 为测试带来了极大的方便 尤其是在编译期间 该方法可以顺利地通过编译 但同时该方法也有一些缺点 第一个是性能问题 用于字段和 方法接入时反射要远慢于直接代码 第二个是权限问题 有些涉及 Java 安全的程序代码并没有修改安全管理器的权限 此时本方法失效         方法三 使用模仿(Mock)对象        在单元测试的过程中模仿对象被广泛使用 它从测试中分离了外部的不需要的因素 并且帮助开发人员专注于被测试的功能 模仿对象(Mock object)的核心是构造一个伪类 在测试中通常用这个构造的伪类替换原来的需要访问相关环境(如应用服务器 数据库等)的需要测试的待测类 这样单元 测试便可以运行在本地环境下(这也是对单元测试的基本要求之一 不依赖于任何特定的环境) 并可以正确的执行 此外 由于 Java 语言不能多继承的特性 使得该方法也可以被用来作为非公有成员变量及方法的访问方法(测试类不能同时继承 TestCase 和待测类) 利用该方法 在模仿对象中改变类成员的访问控制权限 从而达到访问非公有类变量及方法的目的         下面的代码示例演示了模仿对象方法         本方法的应用场景在单元测试中非常常见 即在待测试的公有方法中 有一些受限制的成员变量是由其它私有方法来初始化的 在测试该方法的时候 需要给这个变量置初值才能完成测试         清单 待测类 A        public class A protected String s = null;        public A()        private void method()        s = word ; System out println( this is mock test )                 public void makeWord()        String prefix = s;        System out println( prefix is: + prefix)                         在待测类 A 中 增加工厂方法         清单 包含工厂方法的待测类 A        // 增加工厂方法的类 Apublic class A protected String s = null;        public A getA() return new A()         private void method() s = word ; System out println( this is mock test )         public void makeWord() String prefix = s;        System out println( prefix is: + prefix)         //伪类 在运行时替换类 Apublic class MockA extends A public String s = null; public MockA()        //测试类public class TestA extends TestCase public void setup()        public void teardown() public void makeWordTest() A a = new MockA() a s = test ; a makeWord()                 此方法中有几个值得注意的地方 首先是将 创建代码抽取到工厂方法中 在测试子类中覆蓋该工厂方法 然后令被覆蓋的方法返回模仿对象 如果可以的话 添加需要原始对象的工厂方法的单元测试 以返回 正确类型的对象 模仿对象方法在处理许多对象依赖基础结构的其它对象或层时 可以起到很好的效果 模仿对象符合实际对象的接口 但只要有足够的代码来 欺骗 测试对象并跟踪其行为 例如 在单元测试中需要测试一个使用数据库的对象 或者需要测试连接 J EE 应用服务器的对象 通常的测试用例需要安装 配置和发送本地数据库副本 运行测试然后再卸装本地数据库或者需要安装 配置应用服务器 运行测试然后再卸装 应用服务器 操作可能很麻烦 模仿对象提供了解决这一困难的途径 对于既需要访问相关环境又要访问非公有变量或方法的类来说 模仿对象非常适合 但是 如果只是访问非公有变量或方法 那么传统的模仿对象法显得有些笨重 可以对该法进行简化 不使用工厂方法 达到同样的效果         下面的代码示例演示了经过简化的模仿对象方法         清单 简化的待测类 A 的模仿对象        //伪类 在运行时替换类Apublic class MockA extends A public MockA() super() s = test ;                        //测试类public class TestA extends TestCase public void setup() public void teardown() public void makeWordTest() A a = new MockA()         a makeWord()                         模仿对象方法既能消除运行环境的影响 又能解决多继承的难题 但是由于该方法使用子类的实例来替代父类的实例 对于私有成员变量及方法来说 仍然不能进行访问         方法四 利用字节码技术        Java 编译器把 Java 源代码编译成字节码 bytecode(字节码) 既然在测试中尽量要避免改变原来的代码 那么最直接的改造 Java 类的方法莫过于直接改写 class 文件 通过修改字节码中的关键字 将私有的成员变量及方法改成公有的成员变量及方法 可以做到在不改变源码的情况下访问到需要的成员变量及方法 Java 规范有 class 文件的格式的详细说明 直接编辑字节码确实可以改变 Java 类的行为 但是这也要求使用者对 Java class 文件有较深的理解 目前 比较流行的字节码处理工具有 Javassist BCEL 和 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() 付值 所以 需要在测试中为该私有变量置初值         清单 待测类 A        class A private String number = ;        public void method()        if(number eaquals( prefix ))        System out println( method… +number)         else System out println(number + is null )         private void makePaper()        number= prefix ;        System out println( makePaper… )                         清单 使用字节码访问类 A        //修改变量的修饰符public class AccessClassAdapter extends ClassAdapter public AccessClassAdapter(ClassVisitor cv) super(cv)         public FieldVisitor visitField(final int access String name final String desc final String signature final Object value)        int privateAccess = access;        //找到名字为number的变量 if (name equals( number )) privateAccess = Opcodes ACC_PUBLIC;        //修字段的修饰符为public:在职责链传递过程中替换调用参数 return cv visitField(privateAccess name desc signature value)         public static void main(String[] args) throws Exception ClassReader cr = new ClassReader( A )         ClassWriter cw = new ClassWriter(ClassWriter PUTE_MAXS)         ClassAdapter classAdapter = new AccessClassAdapter(cw)         cr accept(classAdapter ClassReader SKIP_DEBUG)         byte[] data = cw toByteArray()         //生成新的字节码文件 File file = new File( A class )         FileOutputStream fout = new FileOutputStream(file)         fout write(data) fout close()                         执行完该类 将产生一个新的 A class 文件         测试类测试 method 方法 先对变量进行置初值 然后就可以像其他单元测试一样 对 method 方法进行测试         回页首        方法对比        方法 修饰符 使用难度 缺陷        protected 缺省 private        方法一 修改访问权限修饰符 是 是 是 低 有java编程基础即可 由于需要修改源代码 虽然是同包可见 也会带来一些封闭性的问题         方法二 利用安全性管理器 是 是 是 中 需要了解java安全性管理器及反射机制 一些对代码安全有要求的程序 程序员并没有修改security manager的权限 此时 安全管理器方法失效         方法三 使用模仿对象 是 是 否 较高 需要了解设计模式和待测对象的内部实现细节 由于模仿对象要求伪类必需和待测类是继承与被继承的关系 所以当源码以private关键字修饰时 此方法失效         方法四 利用字节码技术 是 是 是 高 需要操作和改写类部分的字节码 学习成本高 需要了解Java字节码技术        总结        在进行单元测试时 我们要尽可能的考虑代码的移植性和通用性 在不修改源程序的前提下达到测试的最佳效果 对于是否应该使用以及如何使用本文中提到的四种方法 需要开发人员根据具体场合谨慎选择 cha138/Article/program/Java/hx/201311/25546

相关参考

知识大全 Java反射机制获取内部类的静态成员

Java反射机制获取内部类的静态成员  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  最近需要些一

知识大全 java的volatile与多线程

  Java语言规范中指出为了获得最佳速度允许线程保存共享成员变量的私有拷贝而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比  Volatile修饰的成员变量在每次被线程访问时都强迫从

知识大全 静态成员

   除了publicprivate和protected以外你还可以把成员说明为static(静态)static修饰符表明一个变量或成员函数对所有类的实例都是相同的你也许想使用一个所有Rectangl

知识大全 java里面如何创建一个内部类的实例?

  要想使用new生成一个内部类的实例需要先指向一个外部类的实例也就是先生成外部类的实例因为内部类可以调用外部类的人员成员当没有外部类实例的时候也就没有这些成员的内存空间内部类在实例化的时候调用外部类

知识大全 构造和析构成员函数

    类有一个特殊的成员函数叫做构造成员函数它通常用于初始化类的数据成员在创建对象时会自动调用类的构造成员函数Java中的构造成员函数必须与该类具有相同的名字另外构造成员函数一般都应用public类

知识大全 类的基本概念

      Java程序的基本单位是类类的定义可以看作是建立对象的原形你建立类之后就可用它来建立许多你需要的对象Java把每一个可执行的成分都变成类使面向对象发展到极点  类的基本定义成员函数如下  

知识大全 重定向输出流实现程序日志

  System类中的out成员变量是java的标准输出流通常用它来输出调试信息out成员变量被定义为final类型无法直接重新复制但是可以通过setOut()方法来设置新的输出流本例通过该方法实现了

知识大全 Java变量的自动递增和递减

Java变量的自动递增和递减  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  和C类似Java提供

知识大全 头文件生成器-javah.exe

  javah头文件生成器  javah程序创建C头文件和存根文件这些是把本地C成员函数包入java所需要的被创建的头文件给出了有关java类的信息这些信息是C成员函数与java类交换数据所必需的存根

知识大全 Java反射访问私有变量和私有方法

Java反射访问私有变量和私有方法  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  引言  对于软