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

    这个问题在这里已有答案:                           如何测试私有函数或具有私有方法,字段或内部类的类?                                      46个答案                                    
已邀请:
恕我直言反思应该只是最后的手段,保留用于单元测试遗留代码或您无法更改的API的特殊情况。如果您正在测试自己的代码,那么您需要使用Reflection意味着您的设计不可测试,因此您应该修复它而不是使用Reflection。 如果您需要在单元测试中访问私有成员,通常意味着有问题的类具有不合适的接口,和/或尝试做太多。因此,要么修改其接口,要么将某些代码提取到单独的类中,这些有问题的方法/字段访问器可以公开。 请注意,使用Reflection通常会导致代码除了更难理解和维护之外,还会更加脆弱。有一整套错误,在正常情况下会由编译器检测到,但是使用Reflection它们只会出现运行时异常。 更新:正如@tackline所说,这只涉及在一个人自己的测试代码中使用Reflection,而不是测试框架的内部。 JUnit(可能还有所有其他类似的框架)使用反射来识别和调用您的测试方法 - 这是对反射的合理和本地化使用。如果不使用Reflection,很难或不可能提供相同的功能和便利。 OTOH它完全封装在框架实现中,因此它不会使我们自己的测试代码复杂化或妥协。     
仅仅为了测试而修改生产API的可见性是非常糟糕的。出于正当理由,该可见性可能被设置为其当前值,并且不会被更改。 使用反射进行单元测试大多都很好。当然,您应该设计可测试性的类,以便减少反射。 例如,春天有
ReflectionTestUtils
。但它的目的是设置依赖的模拟,春天应该注入它们。 该主题比“do& do not”更深入,并且考虑应该测试的内容 - 是否需要测试对象的内部状态;我们是否应该负责质疑被测班级的设计;等等     
从TDD - 测试驱动设计的角度来看 - 这是一种不好的做法。我知道你没有标记这个TDD,也没有特别询问它,但TDD是一个很好的做法,这与它的背景有关。 在TDD中,我们使用我们的测试来定义类的接口 - 公共接口 - 因此我们编写的测试仅直接与公共接口交互。我们关心那个界面;适当的访问级别是设计的重要部分,是良好代码的重要组成部分。如果你发现自己需要测试私人物品,根据我的经验,通常会有设计气味。     
我认为这是一个不好的做法,但只是改变生产代码中的可见性不是一个好的解决方案,你必须看看原因。要么你有一个不可测试的API(那就是API没有公开足以让测试得到一个句柄)所以你正在寻找测试私有状态,或者你的测试与你的实现太耦合,这将使它们成为重构时只能边际使用。 在不了解你的情况的情况下,我真的不能说更多,但使用反射确实被认为是一种不好的做法。就个人而言,我宁愿让测试成为被测试类的静态内部类,而不是依赖于反射(如果说API的不稳定部分不在我的控制范围内),但有些地方的测试代码会有更大的问题。生产代码与使用反射相同的包。 编辑:为了回应你的编辑,这至少与使用Reflection的做法差,可能更糟。通常处理的方式是使用相同的包,但将测试保存在单独的目录结构中。如果单元测试不属于与被测试类相同的包,我不知道是什么。 无论如何,你仍然可以通过使用protected来解决这个问题(遗憾的是不是package-private,这是非常理想的),通过测试子类如下:
 public class ClassUnderTest {
      protect void methodExposedForTesting() {}
 }
在你的单元测试中
class ClassUnderTestTestable extends ClassUnderTest {
     @Override public void methodExposedForTesting() { super.methodExposedForTesting() }
}
如果你有一个受保护的构造函数:
ClassUnderTest test = new ClassUnderTest(){};
我不一定会在正常情况下推荐上述内容,但是要求您使用限制而不是“最佳实践”。     
我想到了Bozho:   仅仅为了测试而修改生产API的可见性是非常糟糕的。 但是如果你尝试做一些可能有效的最简单的事情,那么最好编写Reflection API样板文件,至少在你的代码仍然是新的/更改的时候。我的意思是,每次更改方法名称或params时,手动更改测试反射调用的负担是恕我直言,太多的负担和错误的焦点。 陷入了放松可访问性的陷阱,只是为了测试,然后无意中从其他生产代码访问was-private方法我想到了dp4j.jar:它注入了Reflection API代码(Lombok风格),所以你不要改变它生产代码并且不要自己编写Reflection API; dp4j在编译时使用等效的反射API替换单元测试中的直接访问。这是一个带有JUnit的dp4j的例子。     
我认为您的代码应该以两种方式进行测试。您应该通过单元测试测试您的公共方法,这将作为我们的黑盒测试。由于您的代码被分解为可管理的功能(良好的设计),您将需要使用反射对各个部分进行单元测试,以确保它们独立于流程工作,我能想到这样做的唯一方法就是使用反射他们是私人的。 至少,这是我在单元测试过程中的想法。     
要添加其他人所说的内容,请考虑以下事项:
//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);
}
在这里,我们使用反射来执行私有方法SomeApplicationClass.computeSquare(),传入一个Integer并返回一个String结果。这导致JUnit测试,如果发生以下任何一种情况,它将编译正常但在执行期间失败: 方法名称“computeSquare”已重命名 该方法采用不同的参数类型(例如,将Integer更改为Long) 参数数量发生变化(例如传递另一个参数) 方法的返回类型更改(可能从String更改为Integer) 相反,如果没有简单的方法来证明computeSquare已经通过公共API完成了它应该做的事情,那么你的类可能会尝试做很多事情。将此方法拉入一个新类,它将为您提供以下测试:
public void testSquare()
{
   String result = new NumberUtils().computeSquare(new Integer(3));
   assertEquals("9", result);
}
现在(特别是当您使用现代IDE中提供的重构工具时),更改方法的名称对您的测试没有影响(因为您的IDE也将重构JUnit测试),同时更改参数类型,数量方法的参数或返回类型将在Junit测试中标记编译错误,这意味着您不会检查编译但在运行时失败的JUnit测试。 我的最后一点是,有时,特别是在使用遗留代码时,并且需要添加新功能时,在单独的可测试的良好编写的类中可能并不容易。在这种情况下,我的建议是将新的代码更改隔离到受保护的可见性方法,您可以直接在JUnit测试代码中执行这些方法。这允许您开始构建测试代码的基础。最终,您应该重构该类并提取您添加的功能,但与此同时,对新代码的受保护可见性有时可能是您在没有重大重构的可测试性方面的最佳前进方式。     

要回复问题请先登录注册