如何使用JUnit测试具有内部私有方法、字段或嵌套类的类?
仅仅为了能够运行测试而更改方法的访问修饰符似乎是不好的。
如何使用JUnit测试具有内部私有方法、字段或嵌套类的类?
仅仅为了能够运行测试而更改方法的访问修饰符似乎是不好的。
当前回答
PowerMock.Whitebox是我见过的最好的选项,但当我阅读它的源代码时,它会读取带有反射的私有字段,所以我想我有了答案:
使用PowerMock测试私有内部状态(字段),或仅进行反射,而无需引入其他独立性的开销对于私人方法:事实上,这个问题本身的赞成票,以及大量的评论和答案,表明这是一个非常并发和有争议的话题,无法给出适合每种情况的确切答案。我知道只有合同需要测试,但我们也需要考虑保险范围。事实上,我怀疑只有测试合约才能100%让一个类免于错误。私有方法是那些在定义数据的类中处理数据的方法,因此对其他类不感兴趣,因此我们不能简单地公开以使其可测试。我会尽量不去测试它们,但当你必须的时候,就去尝试,忘记这里的所有答案。你比互联网上的任何人都更了解自己的处境和限制。当您可以控制代码时,请使用它。经过考虑,但不要过度思考。
一段时间后,当我重新考虑时,我仍然相信这是真的,但我看到了更好的方法。
首先,Powermock.Whitebox仍然可用。
而且,Mockito Whitebox在v2之后被隐藏了(我可以在Whitebox中找到的最新版本是testImplementation“org.Mockito:Mockito core:1.10.19”),并且它一直是org.mockit.internal包的一部分,未来很可能会发生重大变化(请参阅本文)。所以现在我倾向于不使用它。
在Gradle/MMaven项目中,如果您定义了私有方法或字段,那么除了反射之外,没有任何其他方法可以访问它们,因此第一部分是正确的。但是,如果您将可见性更改为“包私有”,则测试包中遵循相同结构的测试将可以访问它们。这也是我们被鼓励在主包和测试包中创建相同层次结构的另一个重要原因。因此,当您可以控制生产代码和测试时,删除私有访问修饰符可能是您的最佳选择,因为相对而言,它不会造成巨大影响。这使得测试和私人方法间谍成为可能。
@Autowired
private SomeService service; // With a package private method "doSomething()"
@Test
void shouldReturnTrueDoSomething() {
assertThat(doSomething(input), is(true)); // Package private method testing
}
@Test
void shouldReturnTrueWhenServiceThrowsException() {
SomeService spy = Mockito.spy(service); // Spying real object
doThrow(new AppException()).when(spy).doSomething(input); // Spy package private method
...
}
谈到内部字段,在Spring中有ReflectionUtils.setField()。
最后,有时我们可以绕过问题本身:如果需要满足覆盖要求,也许可以将这些私有方法移到内部静态类中,并忽略Jacobo中的这个类。我只是找到了一些方法来忽略Jacobo Gradle任务中的内部类。另一个问题。
其他回答
在尝试了Cem Catikkas使用Java反射的解决方案后,我不得不说,他的解决方案比我在这里描述的更优雅。然而,如果您正在寻找使用反射的替代方案,并且能够访问您正在测试的源代码,那么这仍然是一个选项。
测试类的私有方法可能有好处,特别是在测试驱动开发中,您希望在编写任何代码之前设计小型测试。
创建一个可以访问私有成员和方法的测试,可以测试那些只访问公共方法而难以专门针对的代码区域。如果公共方法涉及多个步骤,它可以由多个私有方法组成,然后可以单独测试。
优势:
可以测试到更精细的粒度
缺点:
测试代码必须位于文件作为源代码更难维护与.class输出文件类似,它们必须保持在源代码中声明的相同包中
然而,如果连续测试需要这种方法,这可能是一个信号,表明应该提取私有方法,可以以传统的公共方式进行测试。
下面是一个复杂的例子,说明这是如何工作的:
// Import statements and package declarations
public class ClassToTest
{
private int decrement(int toDecrement) {
toDecrement--;
return toDecrement;
}
// Constructor and the rest of the class
public static class StaticInnerTest extends TestCase
{
public StaticInnerTest(){
super();
}
public void testDecrement(){
int number = 10;
ClassToTest toTest= new ClassToTest();
int decremented = toTest.decrement(number);
assertEquals(9, decremented);
}
public static void main(String[] args) {
junit.textui.TestRunner.run(StaticInnerTest.class);
}
}
}
内部类将编译为ClassToTest$StaticInnerTest。
另请参阅:Java提示106:静态内部类以获取乐趣和利润
在您的课堂上:
namespace my_namespace {
#ifdef UNIT_TEST
class test_class;
#endif
class my_class {
public:
#ifdef UNIT_TEST
friend class test_class;
#endif
private:
void fun() { cout << "I am private" << endl; }
}
}
在单元测试类中:
#ifndef UNIT_TEST
#define UNIT_TEST
#endif
#include "my_class.h"
class my_namespace::test_class {
public:
void fun() { my_obj.fun(); }
private:
my_class my_obj;
}
void my_unit_test() {
test_class test_obj;
test_obj.fun(); // here you accessed the private function ;)
}
JML具有spec_public注释注释语法,允许您在测试期间将方法指定为public:
private /*@ spec_public @*/ int methodName(){
...
}
2.4隐私修改器和可见性中讨论了此语法。还有一个将JML规范转换为JUnit测试的程序。我不确定它的工作情况如何,或者它的功能是什么,但似乎没有必要,因为JML本身就是一个可行的测试框架。
JUnit.org常见问题解答页面的答案:
但如果你必须。。。如果您使用的是JDK1.3或更高版本,则可以使用反射来颠覆访问控制机制。有关如何使用它的详细信息,请阅读本文。如果您使用的是JDK1.6或更高版本,并且使用@测试时,可以使用Dp4j在测试方法中注入反射。对于有关如何使用它的详细信息,请参阅此测试脚本。
P.S.我是Dp4j的主要贡献者。问我你是否需要帮助。:)
我只测试公共接口,但众所周知,我会保护特定的私有方法,因此我可以完全模拟它们,或者添加特定于单元测试目的的额外步骤。一般情况下,挂接我可以从单元测试中设置的标志,以使某些方法故意导致异常,从而能够测试故障路径;异常触发代码仅在受保护方法的重写实现中的测试路径中。
不过,我尽量不需要这样做,我总是记录下确切的原因,以避免混淆。