在单元测试中使用Reflection是不好的做法吗?

Rof*_*ion 94 java reflection unit-testing

在过去的几年里,我一直认为在Java中,Reflection在单元测试中被广泛使用.由于某些必须检查的变量/方法是私有的,因此必须以某种方式读取它们的值.我一直认为Reflection API也用于此目的.

上周我不得不测试一些软件包,因此编写了一些JUnit测试.像往常一样,我使用Reflection来访问私有字段和方法.但我检查代码的主管对此并不满意,并告诉我,Reflection API并不适合用于此类"黑客攻击".相反,他建议修改生产代码中的可见性.

使用Reflection是不是很糟糕的做法?我真的不相信 -

编辑:我应该提到我需要所有测试都在一个名为test的独立包中(所以使用受保护的visibilty例如也不是一个可能的解决方案)

Pét*_*rök 76

恕我直言反思应该只是最后的手段,保留用于单元测试遗留代码或您无法更改的API的特殊情况.如果您正在测试自己的代码,那么您需要使用Reflection意味着您的设计不可测试,因此您应该修复它而不是使用Reflection.

如果您需要在单元测试中访问私有成员,通常意味着有问题的类具有不合适的接口,和/或尝试做太多.因此,要么修改其接口,要么将某些代码提取到单独的类中,这些有问题的方法/字段访问器可以公开.

请注意,使用Reflection通常会导致代码除了更难理解和维护之外,还会更加脆弱.有一整套错误,在正常情况下会由编译器检测到,但是使用Reflection它们只会出现运行时异常.

更新:正如@tackline所说,这只涉及在一个人自己的测试代码中使用Reflection,而不是测试框架的内部.JUnit(可能还有所有其他类似的框架)使用反射来识别和调用您的测试方法 - 这是对反射的合理和本地化使用.如果不使用Reflection,很难或不可能提供相同的功能和便利.OTOH它完全封装在框架实现中,因此它不会使我们自己的测试代码复杂化或妥协.

  • 反射在调试测试和模拟接口的测试框架中显然是有用的,作为编译时注释处理器的替代方法.但应该清楚地注明帽子作为一种单独的用法. (9认同)
  • @tackline,这就是我真正的意思,感谢您指出。我对我的答案进行了澄清。 (2认同)

Boz*_*zho 63

仅仅为了测试而修改生产API的可见性是非常糟糕的.出于正当理由,该可见性可能被设置为其当前值,并且不会被更改.

使用反射进行单元测试大多都很好.当然,您应该设计可测试性的类,以便减少反射.

以春天为例ReflectionTestUtils.但它的目的是设置依赖的模拟,春天应该注入它们.

主题是深于"做与不要",并认为什么应该被测试-是否需要测试或没有对象的内部状态; 我们是否应该负责质疑被测班级的设计; 等等

  • 生产API的变化有两个方面.仅仅针对测试更改它是不好的,同时更新您的API /设计以使您的代码更易于测试(因此您不会仅仅针对测试更改您的可见性)是一种很好的做法. (6认同)
  • 如果您确定您正在做的事情没有副作用 - 是的。;) (2认同)
  • 但请注意,将成员的可见性从 `private` 更改为 `package-private` 对包的使用者没有任何影响,除非他们在包的命名空间中编写代码。这种对范围的特殊更改可能是可以容忍的。 (2认同)

Car*_*ter 30

从TDD - 测试驱动设计的角度来看- 这是一种不好的做法.我知道你没有标记这个TDD,也没有特别询问它,但TDD是一个很好的做法,这与它的背景有关.

在TDD中,我们使用我们的测试来定义类的接口 - 公共接口 - 因此我们编写的测试仅直接与公共接口交互.我们关心那个界面; 适当的访问级别是设计的重要部分,是良好代码的重要组成部分.如果你发现自己需要测试私人物品,根据我的经验,通常会有设计气味.

  • 只要对象的外部行为是正确的,对象的内部状态就无关紧要了. (5认同)
  • 同意.将被测对象视为黑盒子 - 确保输出与输入匹配.如果他们不知道您对实施的内部存在问题. (3认同)

Yis*_*hai 7

我认为这是一个不好的做法,但只是改变生产代码中的可见性不是一个好的解决方案,你必须看看原因.要么你有一个不可测试的API(那就是API没有公开足以让测试得到一个句柄)所以你正在寻找测试私有状态,或者你的测试与你的实现太耦合,这将使它们成为重构时只能边际使用.

在不了解你的情况的情况下,我真的不能说更多,但使用反射确实被认为是一种不好的做法.就个人而言,我宁愿让测试成为被测试类的静态内部类,而不是依赖于反射(如果说API的不稳定部分不在我的控制范围内),但有些地方的测试代码会有更大的问题.生产代码与使用反射相同的包.

编辑:为了回应你的编辑,至少与使用Reflection的做法差,可能更糟.通常处理的方式是使用相同的包,但将测试保存在单独的目录结构中.如果单元测试不属于与被测试类相同的包,我不知道是什么.

无论如何,你仍然可以通过使用protected来解决这个问题(遗憾的是不是package-private,这是非常理想的),通过测试子类如下:

 public class ClassUnderTest {
      protect void methodExposedForTesting() {}
 }
Run Code Online (Sandbox Code Playgroud)

在你的单元测试中

class ClassUnderTestTestable extends ClassUnderTest {
     @Override public void methodExposedForTesting() { super.methodExposedForTesting() }
}
Run Code Online (Sandbox Code Playgroud)

如果你有一个受保护的构造函数:

ClassUnderTest test = new ClassUnderTest(){};
Run Code Online (Sandbox Code Playgroud)

我不一定会在正常情况下推荐上述内容,但是要求您使用限制而不是"最佳实践".


sim*_*ico 6

我想到了Bozho:

仅仅为了测试而修改生产API的可见性是非常糟糕的.

但是如果你尝试做一些可能有效的最简单的事情,那么最好编写Reflection API样板文件,至少在你的代码仍然是新的/更改的时候.我的意思是,每次更改方法名称或params时,手动更改测试反射调用的负担是恕我直言,太多的负担和错误的焦点.

陷入了放松可访问性的陷阱,只是为了测试,然后无意中从其他生产代码访问was-private方法我想到了dp4j.jar:它注入了Reflection API代码(Lombok风格),所以你不要改变它生产代码并且不要自己编写Reflection API; dp4j在编译时使用等效的反射API替换单元测试中的直接访问.这是一个带有JUnit的dp4j例子.


Ric*_*d R 5

我认为您的代码应该以两种方式进行测试。您应该通过单元测试测试您的公共方法,这将作为我们的黑盒测试。由于您的代码被分解为可管理的功能(良好的设计),您需要使用反射对各个部分进行单元测试以确保它们独立于流程工作,我能想到的唯一方法是使用反射,因为他们是私人的。

至少,这是我在单元测试过程中的想法。