如何使用JUnit测试具有内部私有方法、字段或嵌套类的类?
仅仅为了能够运行测试而更改方法的访问修饰符似乎是不好的。
如何使用JUnit测试具有内部私有方法、字段或嵌套类的类?
仅仅为了能够运行测试而更改方法的访问修饰符似乎是不好的。
当前回答
我建议您稍微重构一下代码。当你不得不开始考虑使用反射或其他类型的东西来测试你的代码时,你的代码出了问题。
你提到了不同类型的问题。让我们从私有字段开始。对于私有字段,我会添加一个新的构造函数并将字段注入其中。而不是:
public class ClassToTest {
private final String first = "first";
private final List<String> second = new ArrayList<>();
...
}
我会用这个:
public class ClassToTest {
private final String first;
private final List<String> second;
public ClassToTest() {
this("first", new ArrayList<>());
}
public ClassToTest(final String first, final List<String> second) {
this.first = first;
this.second = second;
}
...
}
即使使用一些遗留代码,这也不会成为问题。旧代码将使用一个空构造函数,如果你问我,重构后的代码看起来会更干净,而且你可以在测试中注入必要的值,而无需反射。
现在谈谈私有方法。根据我的个人经验,当您必须存根一个私有方法进行测试时,该方法在该类中没有任何作用。在这种情况下,一种常见的模式是将其包装在一个接口中,如Callable,然后在构造函数中传递该接口(使用多构造函数技巧):
public ClassToTest() {
this(...);
}
public ClassToTest(final Callable<T> privateMethodLogic) {
this.privateMethodLogic = privateMethodLogic;
}
我写的大部分内容看起来都是依赖注入模式。根据我的个人经验,它在测试时非常有用,我认为这种代码更干净,更容易维护。我对嵌套类也是这样说的。如果嵌套类包含大量逻辑,最好将其作为包私有类移动,并将其注入到需要它的类中。
在重构和维护遗留代码时,我还使用了其他几种设计模式,但这都取决于要测试的代码的情况。大多数情况下,使用反射并不是问题,但当您有一个经过严格测试的企业应用程序,并且在每次部署之前都要运行测试时,一切都会变得非常缓慢(这很烦人,我不喜欢这种东西)。
还有setter注入,但我不建议使用它。我最好使用构造函数,并在真正需要时初始化所有内容,留下注入必要依赖项的可能性。
其他回答
Groovy有一个bug/特性,通过它可以调用私有方法,就像它们是公共的一样。因此,如果您能够在项目中使用Groovy,那么可以使用它来代替反射。查看此页面以获取示例。
私有方法被公共方法使用。否则,它们就是死代码。这就是为什么要测试公共方法,断言公共方法的预期结果,从而断言它使用的私有方法。
在公共方法上运行单元测试之前,应该通过调试来测试私有方法。
也可以使用测试驱动开发来调试它们,调试单元测试,直到满足所有断言。
我个人认为最好使用TDD创建类;创建公共方法存根,然后使用预先定义的所有断言生成单元测试,这样在编写代码之前就可以确定方法的预期结果。这样,就不会走上使单元测试断言符合结果的错误道路。然后,当所有单元测试通过时,您的类是健壮的并满足要求。
我倾向于不测试私人方法。这里有疯狂。我个人认为,你应该只测试你公开的接口(包括受保护的和内部的方法)。
我建议您稍微重构一下代码。当你不得不开始考虑使用反射或其他类型的东西来测试你的代码时,你的代码出了问题。
你提到了不同类型的问题。让我们从私有字段开始。对于私有字段,我会添加一个新的构造函数并将字段注入其中。而不是:
public class ClassToTest {
private final String first = "first";
private final List<String> second = new ArrayList<>();
...
}
我会用这个:
public class ClassToTest {
private final String first;
private final List<String> second;
public ClassToTest() {
this("first", new ArrayList<>());
}
public ClassToTest(final String first, final List<String> second) {
this.first = first;
this.second = second;
}
...
}
即使使用一些遗留代码,这也不会成为问题。旧代码将使用一个空构造函数,如果你问我,重构后的代码看起来会更干净,而且你可以在测试中注入必要的值,而无需反射。
现在谈谈私有方法。根据我的个人经验,当您必须存根一个私有方法进行测试时,该方法在该类中没有任何作用。在这种情况下,一种常见的模式是将其包装在一个接口中,如Callable,然后在构造函数中传递该接口(使用多构造函数技巧):
public ClassToTest() {
this(...);
}
public ClassToTest(final Callable<T> privateMethodLogic) {
this.privateMethodLogic = privateMethodLogic;
}
我写的大部分内容看起来都是依赖注入模式。根据我的个人经验,它在测试时非常有用,我认为这种代码更干净,更容易维护。我对嵌套类也是这样说的。如果嵌套类包含大量逻辑,最好将其作为包私有类移动,并将其注入到需要它的类中。
在重构和维护遗留代码时,我还使用了其他几种设计模式,但这都取决于要测试的代码的情况。大多数情况下,使用反射并不是问题,但当您有一个经过严格测试的企业应用程序,并且在每次部署之前都要运行测试时,一切都会变得非常缓慢(这很烦人,我不喜欢这种东西)。
还有setter注入,但我不建议使用它。我最好使用构造函数,并在真正需要时初始化所有内容,留下注入必要依赖项的可能性。
对于Java,我会使用反射,因为我不喜欢仅仅为了测试而更改对声明方法的包的访问。然而,我通常只测试公共方法,这也应该确保私有方法正常工作。
不能使用反射从所有者类外部获取私有方法,私有修饰符也会影响反射
这不是真的。你当然可以,正如塞姆·卡提卡斯的回答中所提到的。