在unit testing中使用reflection是不好的做法?
在过去的几年中,我一直认为在Java中,reflection在unit testing中被广泛使用。 由于一些必须检查的variables/方法是私人的,因此需要读取它们的值。 我一直认为Reflection API也用于这个目的。
上周我不得不testing一些包,因此写了一些JUnittesting。 和往常一样,我使用Reflection来访问私有字段和方法。 但是,我的主pipe检查了代码并不是很满意,并告诉我Reflection API不是用来做这种“黑客行为”的。 相反,他build议修改生产代码中的可视性。
使用reflection真的是不好的做法吗? 我不能相信 –
编辑:我应该提到,我被要求,所有的testing都在一个单独的包称为testing(所以使用受保护的visibilty,例如也不是一个可能的解决scheme)
恕我直言reflection应该真的只是最后的手段,为unit testing遗留代码的特殊情况保留或一个API,你不能改变。 如果您正在testing自己的代码,那么您需要使用“reflection”的事实意味着您的devise不可testing,所以您应该修复该问题,而不是使用“reflection”。
如果你需要在你的unit testing中访问私有成员,通常意味着这个类有一个不合适的接口,并且/或者尝试做太多。 所以要么修改它的接口,要么将代码提取到一个单独的类中,在那里可以公开那些有问题的方法/字段访问器。
请注意,使用reflection通常会导致代码除了更难理解和维护之外,也更脆弱。 在正常情况下,有一整套错误会被编译器检测到,但使用Reflection时,它们只会出现运行时exception。
更新:正如@tackline指出的那样,这只涉及在自己的testing代码中使用reflection,而不是testing框架的内部。 JUnit(也可能是所有其他类似的框架)使用reflection来识别和调用您的testing方法 – 这是reflection的合理和本地化的使用。 没有使用reflection,提供相同的function和便利将是困难的或不可能的。 OTOH完全封装在框架实现中,所以它不会使我们自己的testing代码变得复杂或不妥协。
仅仅为了testing而修改生产API的可见性是非常糟糕的。 由于正当的原因,该可见度很可能会被设置为当前值,不会被改变。
使用reflection来进行unit testing基本上没问题。 当然,你应该devise你的类可testing性 ,所以需要更less的反思。
Spring例如有ReflectionTestUtils
。 但是它的目的是设置依赖的模拟,spring应该注入它们。
这个话题比“不要不要”要深,要考虑什么要testing – 物体的内部状态是否需要testing, 是否应该对被testing的课程devise提出质疑; 等等
从TDD – testing驱动devise的angular度来看,这是一个糟糕的做法。 我知道你没有标记这个TDD,也没有专门询问它,但TDD是一个很好的做法,这是违背它的粒度。
在TDD中,我们使用我们的testing来定义类的接口 – 公共接口 – 因此我们正在编写只与公共接口直接交互的testing。 我们关心那个界面; 适当的访问级别是devise的重要组成部分,是良好代码的重要组成部分。 如果你发现自己需要testing一些私人的东西,通常是以我的经验,devise一种气味。
我认为这是一个不好的做法,但只是改变生产代码的可见性不是一个好的解决scheme,你必须看看原因。 要么你有一个无法testing的API(也就是说,API没有足够的公开来testing以获得处理),所以你想要testing私有状态,或者你的testing与你的实现相结合,这将使他们当你重构时只有边际使用。
如果不了解你的情况,我不能多说,但是用反思确实是一个不好的做法。 就个人而言,我宁愿让testing成为testing类的静态内部类,而不是通过reflection(如果说API的不可testing部分不在我的控制之下),但是一些地方会有更大的问题,testing代码在与使用reflection相同的生产代码包。
编辑:作为对你的编辑的回应,至less和使用反思一样,可能更糟。 通常处理的方式是使用相同的程序包,但将testing保存在单独的目录结构中。 如果unit testing与被testing的类不在同一个包中,我不知道是什么意思。
无论如何,你仍然可以解决这个问题,通过使用protected(不幸的是不是package-private真的非常适合这个)通过testing一个子类来这样做:
public class ClassUnderTest { protect void methodExposedForTesting() {} }
并在你的unit testing
class ClassUnderTestTestable extends ClassUnderTest { @Override public void methodExposedForTesting() { super.methodExposedForTesting() } }
如果你有一个受保护的构造函数:
ClassUnderTest test = new ClassUnderTest(){};
对于正常情况,我不一定build议上述内容,但是您已经被要求使用的限制,而不是“最佳实践”。
我第二个Bozho的想法:
仅仅为了testing而修改生产API的可见性是非常糟糕的。
但是如果你试图做最简单的事情,那么可能会更喜欢编写Reflection API样板,至less在你的代码仍然是新的/改变的时候。 我的意思是,手动改变从你的testing反映的调用每一次你改变方法名称或PARAMS的负担是恕我直言太多的负担和错误的重点。
为了进行testing,然后不经意间从其他生产代码中访问了私有方法,我想到了dp4j.jar :它注入了Reflection API代码( Lombok风格 ),以便不改变生产代码,而不要自己编写Reflection API; 在编译时,dp4j会将unit testing中的直接访问replace为等效的reflectionAPI。 这里是一个使用JUnit的dp4j的例子 。
我认为你的代码应该以两种方式进行testing。 你应该通过unit testingtesting你的公共方法,这将作为我们的黑盒testing。 由于你的代码被分解成可pipe理的function(好的devise),所以你需要用reflection来单独testing各个部分,以确保它们独立于stream程工作,我唯一能想到的做法是用reflection他们是私人的。
至less,这是我在unit testing过程中的想法。
要增加别人的话,请考虑以下几点:
//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); }
在这里,我们使用reflection来执行私有方法SomeApplicationClass.computeSquare(),传递一个Integer并返回一个String结果。 这会导致JUnittesting,如果发生以下任何情况,将会在执行期间编译正常但失败:
- 方法名称“computeSquare”被重命名
- 该方法采用不同的参数types(例如,将整数更改为长整数)
- 参数数量改变(例如传入另一个参数)
- 该方法的返回types改变(也许从string到整数)
相反,如果没有简单的方法来certificatecomputeSquare已经通过公共API完成了它应该做的事情,那么你的类可能会做很多事情。 把这个方法拉到一个新的类中,给你下面的testing:
public void testSquare() { String result = new NumberUtils().computeSquare(new Integer(3)); assertEquals("9", result); }
现在(特别是使用现代IDE中的重构工具时),更改方法的名称对testing没有影响(因为IDE也将重构JUnittesting),同时更改参数types,参数或方法的返回types将在您的Junittesting中标记一个编译错误,这意味着您将不会在运行时检查编译但失败的JUnittesting。
我的最后一点是,有时候,特别是在使用遗留代码的时候,你需要添加新的function,在单独的可testing的写得好的类中这样做可能并不容易。 在这种情况下,我的build议是将新代码更改隔离到受保护的可见性方法,您可以直接在JUnittesting代码中执行这些方法。 这使您可以开始build立testing代码的基础。 最终,您应该重构类,并提取您的附加function,但与此同时,新代码的受保护的可见性有时是可testing性方面的最佳方式,而不会进行重大的重构。