通过反射在单元测试中访问私有映射

3

我需要访问一个私有地图进行单元测试,但无法实现。

主类:

public class TestMe{
private Map<String, SomeObject> testMap = new HashMap<String, SomeObject>();
}

单元测试:

public class TestMeTester{
   public testTestMe(){
      TestMe obj = new TestMe();

      Class clazz = obj.getClass();

      Field field = clazz.getDeclaredField("testMap");
      field.setAccessible(true);
      Map<String, SomeObject> refMap = (HashMap<String, SomeObject>) field.get(new HashMap<String, Object>());
            System.out.println("==>" + refMap);
   }
}

异常:

java.lang.IllegalArgumentException: Can not set java.util.Map field com.xx.TestMe.testMap to java.util.HashMap
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:164)
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:168)
    at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:55)
    at sun.reflect.UnsafeObjectFieldAccessorImpl.get(UnsafeObjectFieldAccessorImpl.java:36)
4个回答

3
您需要使用TestMe对象调用Field.get()
TestMe obj = new TestMe(); 
....
Map<String, SomeObject> refMap = (HashMap<String, SomeObject>) = field.get(obj);

您的代码在一个新的HashMap实例上调用了Field.get()

field.get(new HashMap<String, Object>()); // This will not work in your case

注释编辑:这不起作用。

我尝试找出什么不能工作。以下是我的测试代码,它可以正常工作。

public class TestMeTester {

    public static class TestMe {
        private Map<String, String> testMap = new HashMap<String, String>();
    }

    public static void main(String[] args) throws IllegalArgumentException,
        IllegalAccessException, SecurityException, NoSuchFieldException {
        TestMe obj = new TestMe();
        Class clazz = obj.getClass();
        Field field = clazz.getDeclaredField("testMap");
        field.setAccessible(true);
        Map<String, String> refMap = (HashMap<String, String>) field.get(obj);
        System.out.println("==>" + refMap);
    }
}

@JSS 我添加了我用来找出可能无法工作的代码。但它是有效的。你能解释一下什么没有起作用吗? - René Link

0

所以我通过一些代码试验弄明白了:

如果你和我一样需要使用Java反射,请继续阅读,否则,@Duncan已经提到了一种有效的方法,只需为获取测试HashMap创建一个方法。

现在,你要做的是:

  1. 声明类型Field作为你的私有Map的引用,并初始化包含它的类:
    private Map: Field map = ClassName.class.getDeclaredField(String "name of map")

  2. 将你的Field实例设置为可访问:map.setAccessible(true)

  3. 由于你知道你的Field是一个Map,你可以将Object转换为Map,然后在通过你的Field实例传递的参数对象上调用Map方法:
    (Map) map.get(classNameInstance)

  4. 在测试中,我得到了一个错误,指出我的情况下assertEquals(Object, Object)。为了避免这个问题,我简单地将它转换为原始类型。我还检查了它对于String也起作用,因此归纳起来,它应该适用于所有其他定义的对象。

这里有一个编码示例供参考。在我的情况下,我不得不使用一个将字符串映射到整数的HashMap,我的最终转换错误是我无法使用assertEquals比较int和Integer,所以我将Integer强制转换为int:
public class KeywordCollection{
  private static HashMap<String, Integer> map = new HashMap<String, Integer>();
  //Etc. for KeywordCollection, only the private initialization is relevant
}

public void addTests() throws SecurityException, NoSuchFieldException,
        Exception, IllegalAccessException {
    KeywordCollection keys1 = new KeywordCollection();
    Field map = KeywordCollection.class.getDeclaredField("keywordTable");
    map.setAccessible(true);

    // Test if add works trivially
    assertTrue(keys1.addKeyword("First"));
    assertFalse(keys1.addKeyword("First"));
    assertFalse(keys1.addKeyword("FIrST"));
    assertTrue(keys1.addKeyword("Inductive"));
    assertTrue(keys1.addKeyword("Not in keys2"));
    System.out.println(map.get(keys1).toString());
    output: {Not in keys2=3, Inductive=2, First=1}

    //For getting values
    assertEquals(1,(int) ((HashMap<String, Integer>) map.get(keys1)).get("First"));

所以你可以看到使用强制转换调用(HashMap<String,Integer>) map.get(keys1)后,我将Field map实例设置为引用我的对象类KeywordCollection,该类有我的HashMap,然后将其从对象转换为HashMap,以便我可以访问我的HashMap方法。否则只能访问Object方法。使用这种方法,您可以像通常一样运行对映射的测试(访问map.keySet,contains,get(key)等等)。
此外,不要担心所有异常。我在Eclipse中编写代码,因此会收到未处理异常的警告,因此我已经设置为根据API抛出方法可能抛出的异常。它们与Field变量相关,如果您知道要在调用的方法中使用的字段,则不应该使用此方法时遇到问题。
NoSuchFieldException表示在调用的类中没有按名称传递的变量。而IllegalAccessException表示您没有权限访问该Field(即您没有将setAccessible设置为true)。
还要记得导入您的包。对于Field,您需要import java.lang.reflect.Field(我喜欢使用*)。
希望有所帮助。

0

我认为你应该这样做

Map<String, SomeObject> refMap = (HashMap<String, SomeObject>) field.get(obj);

如下(可运行)代码所示:

import java.lang.reflect.Field;

public class Test {
    static public void main(String[] args) throws Exception {
        Foo f = new Foo();
        Field x = f.getClass().getDeclaredField("x");
        x.setAccessible(true);
        System.out.println("Before: " + x.get(f)); // prints 1
        x.set(f, (Integer)2);
        System.out.println("After: " + x.get(f)); // prints 2
    }
}

class Foo {
    private Integer x = 1;
}

否则你试图检索不存在的 HashMaptestMap 字段。
除了通过反射访问私有字段的问题,编写依赖这些实现细节(即私有字段的存在)的单元测试很可能很快就会失败。 我建议查看以下链接:

http://xunitpatterns.com/Fragile%20Test.html#Overcoupled%20Software http://xunitpatterns.com/Principles%20of%20Test%20Automation.html#Use%20the%20Front%20Door%20First


@JSS 请看一下我的最新更新。我的代码片段应该可以直接运行。 - Raffaele Rossi

0
如果您需要在单元测试中访问您的地图,则添加一个默认访问 getter 方法。编写支持单元测试的代码是没有问题的。
Map<String, SomeObject> getTestMap() {...}

反射应该是你最后的选择,通常情况下是当你没有机会编辑目标类时。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接