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

Java 在单元测试中使用反射是不好的做法吗?,java,unit-testing,reflection,Java,Unit Testing,Reflection,在过去的几年中,我一直认为在Java中,反射在单元测试中被广泛使用。由于必须检查的一些变量/方法是私有的,因此有必要读取它们的值。我一直认为反射API也用于此目的 上周我不得不测试一些包,因此编写了一些JUnit测试。像往常一样,我使用反射来访问私有字段和方法。但检查代码的主管对此并不满意,并告诉我反射API不适用于这种“黑客行为”。相反,他建议修改生产代码中的可见性 使用反射真的很糟糕吗?我真不敢相信- 编辑:我应该提到,我被要求所有测试都在一个单独的名为test的包中(因此使用受保护的可视性

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

上周我不得不测试一些包,因此编写了一些JUnit测试。像往常一样,我使用反射来访问私有字段和方法。但检查代码的主管对此并不满意,并告诉我反射API不适用于这种“黑客行为”。相反,他建议修改生产代码中的可见性

使用反射真的很糟糕吗?我真不敢相信-


编辑:我应该提到,我被要求所有测试都在一个单独的名为test的包中(因此使用受保护的可视性,例如,也不是一个可行的解决方案)

IMHO反射实际上应该只是最后的手段,保留用于单元测试遗留代码或您不能更改的API的特殊情况。如果您正在测试自己的代码,那么您需要使用反射这一事实意味着您的设计是不可测试的,因此您应该解决这一问题,而不是求助于反射

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

请注意,在一般情况下使用反射会导致代码更难理解和维护,而且代码也更脆弱。在正常情况下,编译器会检测到一整套错误,但通过反射,它们只会作为运行时异常出现

更新:正如@tackline所指出的,这只涉及在自己的测试代码中使用反射,而不是测试框架的内部。JUnit(可能还有所有其他类似的框架)使用反射来识别和调用您的测试方法——这是反射的合理和本地化使用。如果不使用反射,就很难或不可能提供相同的特性和便利。它完全封装在框架实现中,因此不会使我们自己的测试代码复杂化或受损。

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

使用反射进行单元测试基本上是好的。当然,您应该这样做,以便减少反射

例如,Spring具有
ReflectionTestUtils
。但它的目的是设置依赖项的模拟,spring应该在其中注入依赖项


该主题比“做与不做”更深入,涉及应该测试的内容——对象的内部状态是否需要测试;我们是否应该质疑被测班级的设计;等等。

我认为这是一种不好的做法,但仅仅改变生产代码中的可见性并不是一个好的解决方案,您必须看看原因。要么你有一个不稳定的API(也就是说,该API没有足够的公开来让一个测试获得一个句柄),所以你想改为测试私有状态,要么你的测试与你的实现过于耦合,这将使它们在你重构时只起到很小的作用

在不了解更多关于你的案例的情况下,我不能再多说了,但这确实被认为是一种糟糕的做法。就我个人而言,我宁愿让测试成为被测试类的静态内部类,也不愿求助于反射(如果说API的不稳定部分不在我的控制之下),但是有些地方会有更大的问题,测试代码与生产代码在同一个包中,而不是使用反射

编辑:作为对编辑的回应,这至少和使用反射一样糟糕,可能更糟。通常的处理方式是使用相同的包,但将测试保留在单独的目录结构中。如果单元测试与被测试的类不属于同一个包,我不知道是什么

无论如何,您仍然可以通过使用protected(不幸的是,不是package private,这在这方面非常理想)来绕过这个问题,方法是测试一个子类,如下所示:

 public class ClassUnderTest {
      protect void methodExposedForTesting() {}
 }
在单元测试中

class ClassUnderTestTestable extends ClassUnderTest {
     @Override public void methodExposedForTesting() { super.methodExposedForTesting() }
}
如果您有受保护的构造函数:

ClassUnderTest test = new ClassUnderTest(){};

我不一定推荐在正常情况下使用上述方法,但是要求您使用的限制条件并不是“最佳实践”。

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


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

从TDD(测试驱动设计)的角度来看,这是一种糟糕的做法。我知道你没有给这个TDD贴标签,也没有特别询问它,但TDD是一个很好的实践,这违背了它的本质


在TDD中,我们使用测试来定义类的接口—公共接口—因此我们编写的测试只与公共接口直接交互。我们关心这个接口;适当的访问级别是设计的重要部分,也是良好代码的重要部分。如果你发现自己需要测试一些私密的东西,在我的经验中,通常是一种设计气味。
//in your JUnit code...
public void testSquare()
{
   Class classToTest = SomeApplicationClass.class;
   Method privateMethod = classToTest.getMethod("computeSquare", new Class[]{Integer.class});
   String result = (String)privateMethod.invoke(new Integer(3));
   assertEquals("9", result);
}
这里,我们使用反射来执行私有m
public void testSquare()
{
   String result = new NumberUtils().computeSquare(new Integer(3));
   assertEquals("9", result);
}