Java 在单元测试中使用反射是不好的做法吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2811141/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is it bad practice to use Reflection in Unit testing?
提问by RoflcoptrException
During the last years I always thought that in Java, Reflection is widely used during Unit testing. Since some of the variables/methods which have to be checked are private, it is somehow necessary to read the values of them. I always thought that the Reflection API is also used for this purpose.
在过去的几年里,我一直认为在 Java 中,反射在单元测试中被广泛使用。由于某些必须检查的变量/方法是私有的,因此有必要读取它们的值。一直以为Reflection API也有这个用途。
Last week i had to test some packages and therefore write some JUnit tests. As always i used Reflection to access private fields and methods. But my supervisor who checked the code wasn't really happy with that and told me that the Reflection API wasn't meant to use for such "hacking". Instead he suggested to modifiy the visibility in the production code.
上周我不得不测试一些包,因此编写了一些 JUnit 测试。和往常一样,我使用反射来访问私有字段和方法。但是我检查代码的主管对此并不满意,并告诉我反射 API 并不打算用于这种“黑客攻击”。相反,他建议修改生产代码中的可见性。
Is it really bad practice to use Reflection? I can't really believe that-
使用反射真的不好吗?我真不敢相信——
Edit: I should have mentioned that i was required that all tests are in a separate package called test (so using protected visibilty e.g. wasn't a possible solution too)
编辑:我应该提到我需要所有测试都在一个名为 test 的单独包中(因此使用保护的可见性也不是一个可能的解决方案)
采纳答案by Péter T?r?k
IMHO Reflection should really only be a last resort, reserved for the special case of unit testing legacy code or an API you can't change. If you are testing your own code, the fact that you need to use Reflection means your design is not testable, so you should fix that instead of resorting to Reflection.
恕我直言,反射应该只是最后的手段,保留用于单元测试遗留代码或无法更改的 API 的特殊情况。如果您正在测试自己的代码,则需要使用反射的事实意味着您的设计不可测试,因此您应该修复它而不是诉诸反射。
If you need to access private members in your unit tests, it usually means the class in question has an unsuitable interface, and/or tries to do too much. So either its interface should be revised, or some code should be extracted into a separate class, where those problematic methods / field accessors can be made public.
如果你需要在你的单元测试中访问私有成员,这通常意味着有问题的类有一个不合适的接口,和/或试图做太多。所以要么修改它的接口,要么把一些代码提取到一个单独的类中,在那里那些有问题的方法/字段访问器可以公开。
Note that using Reflection in general results in code which, apart from being harder to understand and maintain, is also more fragile. There are a whole set of errors which in the normal case would be detected by the compiler, but with Reflection they crop up as runtime exceptions only.
请注意,通常使用反射会导致代码除了更难理解和维护之外,也更脆弱。在正常情况下,编译器会检测到一整套错误,但是使用反射,它们只会作为运行时异常出现。
Update:as @tackline noted, this concerns only using Reflection within one's own test code, not the internals of the testing framework. JUnit (and probably all other similar frameworks) uses reflection to identify and call your test methods - this is a justified and localized use of reflection. It would be difficult or impossible to provide the same features and convenience without using Reflection. OTOH it is completely encapsulated within the framework implementation, so it does not complicate or compromise our own testing code.
更新:正如@tackline 所指出的,这仅涉及在自己的测试代码中使用反射,而不涉及测试框架的内部结构。JUnit(可能还有所有其他类似的框架)使用反射来识别和调用您的测试方法——这是对反射的合理和本地化使用。如果不使用反射,就很难或不可能提供相同的功能和便利。OTOH 它完全封装在框架实现中,因此它不会使我们自己的测试代码复杂化或妥协。
回答by Bozho
It is reallybad to modify the visibility of a production API just for the sake of testing. That visibility is likely to be set to its current value for valid reasons and is not to be changed.
这是真的不好修改生产API的知名度只是用于测试的缘故。由于正当理由,该可见性很可能被设置为其当前值并且不会被更改。
Using reflection for unit testing is mostly fine. Of course, you should design your classes for testability, so that less reflection is required.
使用反射进行单元测试大多很好。当然,您应该为可测试性设计您的类,以便减少需要的反射。
Spring for example has ReflectionTestUtils
. But its purpose is set mocks of dependencies, where spring was supposed to inject them.
例如,Spring 有ReflectionTestUtils
. 但它的目的是设置依赖项的模拟,spring 应该在其中注入它们。
The topic is deeper than "do & don't", and regards whatshould be tested - whether the internal state of the objects needs to be tested or not; whether we should afford questioning the design of the class under test; etc.
这个话题比“做&不做”更深,关于应该测试什么——对象的内部状态是否需要测试;我们是否应该质疑被测类的设计;等等。
回答by Yishai
I would regard it as a bad practice, but just changing visibility in the production code isn't a good solution, you have to look at the cause. Either you have an untestable API (that is the API doesn't expose enough for a test to get a handle on) so you are looking to test private state instead, or your tests are too coupled with your implementation, which will make them of only marginal use when you refactor.
我认为这是一种不好的做法,但仅更改生产代码中的可见性并不是一个好的解决方案,您必须查看原因。要么你有一个不可测试的 API(也就是说 API 没有暴露足够的测试来处理)所以你正在寻找测试私有状态,或者你的测试与你的实现过于耦合,这将使它们成为仅在重构时边际使用。
Without knowing more about your case, I can't really say more, but it is indeed regarded as a poor practice to use reflection. Personally I would rather make the test a static inner class of the class under test than resort to reflection (if say the untestable part of the API was not under my control), but some places will have a larger issue with the test code being in the same package as the production code than with using reflection.
在不了解你的情况下,我真的不能说更多,但使用反射确实被认为是一种不好的做法。就个人而言,我宁愿让测试成为被测类的静态内部类,而不是诉诸反射(如果说 API 的不可测试部分不在我的控制之下),但有些地方会在测试代码中遇到更大的问题与生产代码相同的包而不是使用反射。
EDIT: In response to your edit, thatis at least as poor a practice as using Reflection, probably worse. The way it is typically handled is to use the same package, but keep the tests in a separate directory structure. If unit tests don't belong in the same package as the class under test, I don't know what does.
编辑:根据您的编辑,这至少与使用反射一样糟糕,可能更糟。通常处理它的方式是使用相同的包,但将测试保存在单独的目录结构中。如果单元测试与被测类不属于同一个包,我不知道是什么。
Anyway, you can still get around this problem by using protected (unfortunately not package-private which is really ideal for this) by testing a subclass like so:
无论如何,您仍然可以通过使用protected(不幸的是不是package-private,这是非常理想的)来解决这个问题,方法是测试像这样的子类:
public class ClassUnderTest {
protect void methodExposedForTesting() {}
}
And inside your unit test
在你的单元测试中
class ClassUnderTestTestable extends ClassUnderTest {
@Override public void methodExposedForTesting() { super.methodExposedForTesting() }
}
And if you have a protected constructor:
如果你有一个受保护的构造函数:
ClassUnderTest test = new ClassUnderTest(){};
I don't necessarily recommend the above for normal situations, but the restrictions you are being asked to work under and not "best practice" already.
对于正常情况,我不一定推荐上述内容,但是您被要求工作的限制而不是“最佳实践”。
回答by Richard R
I think your code should be tested in two ways. You should test your public methods via Unit Test and that will act as our black box testing. Since your code is broken apart into manageable functions(good design), you'll want to unit test the individual pieces with reflection to make sure they work independent of the process, the only way I can think of doing this would be with reflection since they are private.
我认为您的代码应该以两种方式进行测试。您应该通过单元测试测试您的公共方法,这将作为我们的黑盒测试。由于您的代码被分解为可管理的功能(良好的设计),您需要使用反射对各个部分进行单元测试以确保它们独立于流程工作,我能想到的唯一方法是使用反射,因为他们是私人的。
At least, this is my thinking in the unit test process.
至少,这是我在单元测试过程中的想法。
回答by Carl Manaster
From the perspective of TDD - Test Driven Design- this is a bad practice. I know you didn't tag this TDD, nor specifically ask about it, but TDD is a good practice and this goes against its grain.
从 TDD(测试驱动设计)的角度来看,这是一种不好的做法。我知道你没有标记这个 TDD,也没有特别询问它,但 TDD 是一个很好的做法,这违背了它的原则。
In TDD, we use our tests to define the interface of the class - the publicinterface - and therefore we're writing tests that interact directly only with the public interface. We are concerned with that interface; appropriate access levels are an important part of design, an important part of good code. If you find yourself needing to test something private, it's usually, in my experience, a design smell.
在 TDD 中,我们使用我们的测试来定义类的接口 -公共接口 - 因此我们正在编写仅与公共接口直接交互的测试。我们关心那个接口;适当的访问级别是设计的重要组成部分,是良好代码的重要组成部分。如果你发现自己需要测试一些私密的东西,根据我的经验,它通常是一种设计气味。
回答by Chris Knight
To add to what others have said, consider the following:
要补充其他人所说的话,请考虑以下内容:
//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);
}
Here, we are using reflection to execute the private method SomeApplicationClass.computeSquare(), passing in an Integer and returning a String result. This results in a JUnit test which will compile fine but fail during execution if any of the following occur:
在这里,我们使用反射来执行私有方法 SomeApplicationClass.computeSquare(),传入一个 Integer 并返回一个 String 结果。这将导致 JUnit 测试,如果发生以下任何情况,该测试将正常编译但在执行期间失败:
- Method name "computeSquare" is renamed
- The method takes in a different parameter type (e.g. changing Integer to Long)
- The number of parameters change (e.g. passing in another parameter)
- The return type of the method changes (perhaps from String to Integer)
- 方法名称“computeSquare”被重命名
- 该方法接受不同的参数类型(例如将整数更改为长整数)
- 参数数量改变(例如传入另一个参数)
- 方法的返回类型改变(可能从String到Integer)
Instead, if there is no easy way to prove that the computeSquare has done what it is supposed to do through the public API, then your class is likely trying to do to much. Pull this method into a new class which gives you the following test:
相反,如果没有简单的方法来证明 computeSquare 已经通过公共 API 完成了它应该做的事情,那么您的类可能会尝试做很多事情。将此方法拉入一个新类,该类为您提供以下测试:
public void testSquare()
{
String result = new NumberUtils().computeSquare(new Integer(3));
assertEquals("9", result);
}
Now (especially when you use the refactoring tools available in modern IDE's), changing the name of the method has no effect on your test (as your IDE will also have refactored the JUnit test as well), while changing the parameter type, number of parameters or return type of the method will flag a compile error in your Junit test meaning you won't be checking in a JUnit test which compiles but fails at runtime.
现在(尤其是当您使用现代 IDE 中可用的重构工具时),更改方法的名称对您的测试没有影响(因为您的 IDE 也会重构 JUnit 测试),同时更改参数类型、数量方法的参数或返回类型将在您的 Junit 测试中标记编译错误,这意味着您不会检查编译但在运行时失败的 JUnit 测试。
My final point is that sometimes, especially when working in legacy code, and you need to add new functionality, it may not be easy to do so in a separate testable well written class. In this instance my recommendation would be to isolate new code changes to protected visibility methods which you can execute directly in your JUnit test code. This allows you to begin building up a base of test code. Eventually you should refactor the class and extract your added functionality, but in the meantime, protected visibility on your new code can sometimes be your best way forward in terms of testability without major refactoring.
我的最后一点是,有时,尤其是在处理遗留代码时,并且您需要添加新功能时,在单独的可测试编写良好的类中这样做可能并不容易。在这种情况下,我的建议是将新代码更改隔离到受保护的可见性方法,您可以直接在 JUnit 测试代码中执行这些方法。这允许您开始构建测试代码的基础。最终,您应该重构该类并提取添加的功能,但与此同时,在不进行重大重构的情况下,新代码的受保护可见性有时可能是您在可测试性方面的最佳前进方式。
回答by simpatico
I second the thought of Bozho:
我附和Bozho的想法:
It is really bad to modify the visibility of a production API just for the sake of testing.
仅仅为了测试而修改生产 API 的可见性真的很糟糕。
But if you try to do the simplest thing that could possibly workthen it might be preferred to writing Reflection API boilerplate, at least while your code is still new/changing. I mean, the burden of manually changing the reflected calls from your test every time you change the method name, or params, is IMHO too much burden and the wrong focus.
但是,如果您尝试做可能可行的最简单的事情,那么可能更喜欢编写反射 API 样板,至少在您的代码仍然是新的/不断变化的时候。我的意思是,每次更改方法名称或参数时手动更改测试反射调用的负担是恕我直言太多的负担和错误的焦点。
Having fallen in the trap of relaxing accessibility just for testing and then inadvertently accessing the was-private method from other production code i thought of dp4j.jar: it injects the Reflection API code (Lombok-style) so that you don't change the production code AND don't write the Reflection API yourself; dp4j replaces your direct access in the unit test with the equivalent reflection API at compile-time. Here is an example of dp4j with JUnit.
陷入了放松可访问性的陷阱,只是为了测试,然后无意中从其他生产代码中访问了 was-private 方法,我想到了dp4j.jar:它注入了反射 API 代码(Lombok 风格),这样你就不会改变生产代码并且不要自己编写反射 API;dp4j 在编译时用等效的反射 API 替换了您在单元测试中的直接访问。这是一个带有 JUnit 的 dp4j 示例。