【问题标题】:How can I access private class members in Java?如何访问 Java 中的私有类成员?
【发布时间】:2010-11-21 07:50:54
【问题描述】:

我有包含私有字段的数据模型类,这些私有字段是只读的(通过 getter 函数)。这些字段是由我的 JPA 持久性提供程序 (eclipselink) 在正常操作期间使用数据库的内容设置的。对于单元测试,我想将它们设置为来自持久层模型的假值。我怎样才能做到这一点?无论如何,eclipselink 是如何设置这些值的?

简化示例:

@Entity
class MyEntity
{
    @Id
    private Integer _ix;

    public Integer ixGet()
    {
        return this._ix;
    }
}

【问题讨论】:

    标签: java unit-testing jpa mocking private


    【解决方案1】:

    你能模拟实体本身,提供你自己的 getter 实现吗?

    您可以在模拟持久层中创建一个匿名扩展:

    MyEntity x = new MyEntity() {
        public Integer ixGet() { return new Integer(88); }
    };
    

    【讨论】:

    • 那个特定的方法(匿名覆盖 getter)最适合我的特定设置,因为无论如何我有一个工厂交付测试对象。没想到,谢谢!
    • 这样做有问题——它会产生副作用
    • 请详细说明。在测试我编写的一些 Eclipse 插件时,我已经非常广泛地使用了这种技术。没有注意到任何副作用,除了幸福的温暖光芒。
    【解决方案2】:

    我过去使用过的一些方法:

    • 使 _ix 受保护,创建一个子类,在其中实现 setter
    • 创建一个将_ix 的值作为参数的构造函数
    • 使用反射

    【讨论】:

    • 好建议,谢谢!虽然我想保持 _ix 不公开。
    【解决方案3】:

    如果您真的不想公开,另一种选择是创建一个用于测试的子类,并在那里提供公共访问权限。

    你有几个选择:

    • 创建存根来替换您的实体(先提取接口)
    • 使用反射
    • 添加用于测试的公共设置器
    • 将测试保留在包中并使用默认范围

    有关一堆有用的技术,请查看 Michael Feather 的书,Working Effectively With Legacy Code

    【讨论】:

      【解决方案4】:

      您需要使用反射 API。使用 Class.getField() 获取该字段,然后在该字段上调用 ​​setAccessable(true) 以便您可以写入它,即使它是私有的,最后您可以在它上调用 set() 以写入一个新值。

      例如:

      public class A {
          private int i;
      }
      

      您想将字段“i”设置为 3,即使它是私有的:

      void forceSetInt(Object o, String fieldName, int value) {
          Class<?> clazz = o.getClass();
          Field field = clazz.getDeclaredField(fieldName);
          field.setAccessible(true);
          field.set(o, value);
      }
      

      您需要处理许多例外情况。

      【讨论】:

      • 请注意,我是按字面意思回答您的问题。有许多解决方案不需要破坏 Java 语言保护。
      • 通常持久性提供者不会强行绕过权限。他们要么选择一个可写的“字段”,要么寻找一个可调用的“setField”方法。
      • 好吧,他们似乎能够处理我的所有 db 字段设置为私有的实体,并非所有的实体都有设置器,并且那里的设置器不遵守 bean 命名约定(它总是vSetField 而不是 setField 以与我的所有其余代码保持一致)。我现在想知道我是否因为不遵守规则而对我的坚持造成了性能损失。
      【解决方案5】:

      您可以使用像powermock 这样的模拟框架来绕过封装。在 powermock 中,您可以使用 Whitebox.setInternalState(..) 设置私有成员。

      一种侵入性较小的方法是模拟 getter 方法。这是否可行取决于其他取决于内部状态的内容,但如果足够,这是更清洁的解决方案。

      【讨论】:

        【解决方案6】:

        您可以为只读变量添加带参数的构造函数。不要忘记添加一个默认(零参数)构造函数。

        @Entity
        class MyEntity
        {
            @Id
            private Integer _ix;
        
            public MyEntity(Integer ix) {
                _ix = ix;
            }
        
            public MyEntity() {
                /*
                 * Default constructor
                 */
            }
        
            public Integer ixGet()
            {
                return this._ix;
            }
        }
        

        【讨论】:

          【解决方案7】:

          构造函数是我认为的最佳方式。如果该实体必须是只读的(根本不允许在生产代码中创建新实例),您可以创建具有包访问权限的构造函数并仅在测试中使用它。并且有可能即使您将默认构造函数设为私有或使用包访问权限,您的持久性提供程序仍然能够使用此类实体,但不确定 - 请查看 eclipselink 文档。

          【讨论】:

            【解决方案8】:

            您可以使用 Mockito 之类的测试库以读写模式访问对象的内部状态。例如使用 Mockito:

            //read
            Integer i = Whitebox.getInternalState(myEntity,"_ix")
            //Write 
            Whitebox.setInternalState(myEntity,"_ix", 123) 
            

            【讨论】:

            • 谢谢你。我们已经在使用 Mockito,但我不知道存在这个 Whitebox 界面。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-22
            • 2023-03-22
            • 1970-01-01
            • 2020-09-25
            相关资源
            最近更新 更多