如何对具有内部私有方法,字段或嵌套类的类进行单元测试(使用xUnit)?还是通过内部链接(static
在C / C ++中)或在私有(匿名)名称空间中使其私有化的函数?
仅仅为了能够运行测试而更改方法或函数的访问修饰符似乎很糟糕。
如何对具有内部私有方法,字段或嵌套类的类进行单元测试(使用xUnit)?还是通过内部链接(static
在C / C ++中)或在私有(匿名)名称空间中使其私有化的函数?
仅仅为了能够运行测试而更改方法或函数的访问修饰符似乎很糟糕。
Answers:
更新:
大约十年后,测试私有方法或任何无法访问的成员的最佳方法可能是
@Jailbreak
来自Manifold框架。@Jailbreak Foo foo = new Foo(); // Direct, *type-safe* access to *all* foo's members foo.privateMethod(x, y, z); foo.privateField = value;
这样,您的代码将保持类型安全和可读性。没有设计折衷,也没有为了测试而过度曝光的方法和字段。
如果您使用的是旧版Java应用程序,并且不允许更改方法的可见性,那么测试私有方法的最佳方法是使用Reflection。
在内部,我们使用助手来获取/设置private
和private static
变量以及调用private
和private static
方法。以下模式将使您几乎可以执行与私有方法和字段相关的所有操作。当然,您private static final
不能通过反射更改变量。
Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);
对于字段:
Field field = TargetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
注意:
1.TargetClass.getDeclaredMethod(methodName, argClasses)
让您研究private
方法。同样的事情也适用getDeclaredField
。
2.setAccessible(true)
必须与私人玩耍。
测试私有方法的最佳方法是通过另一个公共方法。如果不能这样做,则满足以下条件之一:
当我的类中有私有方法非常复杂,以至于我需要直接测试私有方法时,这就是代码的味道:我的类太复杂了。
解决这些问题的常用方法是挑逗一个包含有趣内容的新类。通常,此方法及其与之交互的字段,也许可以将一两个方法提取到新类中。
新类将这些方法公开为“ public”,因此可用于单元测试。现在,新类和旧类都比原始类更简单,这对我来说很棒(我需要保持简单,否则我会迷路!)。
请注意,我并不是在建议人们不要动脑筋来创建课程!这里的重点是利用单元测试的力量来帮助您找到好的新类。
过去,我曾使用反射来为Java执行此操作,我认为这是一个很大的错误。
严格地说,应该不会被编写单元测试直接测试私有方法。您应该测试的是该类与其他对象之间的公共合同;您永远不要直接测试对象的内部。如果另一个开发人员想要对该类进行小的内部更改,而不影响该类的公共合同,则他/她必须修改您基于反射的测试以确保其有效。如果您在整个项目中重复执行此操作,则单元测试将不再是对代码运行状况的有用衡量,并开始成为开发的障碍和开发团队的烦恼。
我建议做的是使用诸如Cobertura之类的代码覆盖工具,以确保您编写的单元测试以私有方法提供对代码的适当覆盖。这样,您可以间接测试私有方法的功能,并保持更高的敏捷性。
从本文开始:使用JUnit和SuiteRunner测试私有方法(Bill Venners),您基本上有4个选择:
- 不要测试私有方法。
- 授予方法包访问权限。
- 使用嵌套的测试类。
- 使用反射。
通常,单元测试旨在行使班级或单元的公共界面。因此,私有方法是您不希望显式测试的实现细节。
我想在哪里测试私有方法的两个示例:
SecurityManager
没有配置为防止这种情况的大多数情况下,反射可以在大多数情况下甚至查看私有方法)。我了解仅测试“合同”的想法。但我看不出有人可以倡导实际上不测试代码-您的工作量可能会有所不同。
因此,我的权衡包括通过反射使JUnit复杂化,而不是损害我的安全性和SDK。
private
并不是唯一的选择。没有访问修饰符,package private
并且意味着只要单元测试位于同一包中,就可以对其进行单元测试。
私有方法由公共方法调用,因此,公共方法的输入也应测试由那些公共方法调用的私有方法。当公用方法失败时,则可能是专用方法失败。
我使用的另一种方法是将私有方法更改为打包私有或受保护的程序,然后用Google Guava库的@VisibleForTesting批注对其进行补充。
这将告诉使用此方法的任何人要小心,即使在包中也不要直接访问它。同样,测试类不必在物理上位于同一包中,而是在test文件夹下的同一包中。
例如,如果要测试的方法在其中,src/main/java/mypackage/MyClass.java
则您的测试调用应放在中src/test/java/mypackage/MyClassTest.java
。这样,您就可以访问测试类中的测试方法。
与大型和古怪的类测试的遗留代码,它往往是能够测试一个私人(或公共)方法我正在写非常有帮助的现在。
我将junitx.util.PrivateAccessor -package用于Java。访问私有方法和私有字段的大量有用的一线工具。
import junitx.util.PrivateAccessor;
PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);
Class
在这些方法中将a 用作第一个参数时,仅访问static
成员。
在Spring Framework中,您可以使用以下方法测试私有方法:
ReflectionTestUtils.invokeMethod()
例如:
ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");
在使用 Cem Catikkas的Java 反射解决方案尝试过之后,我不得不说他的解决方案比我在此描述的更为优雅。但是,如果您正在寻找使用反射的替代方法,并且可以访问要测试的源,则仍然可以选择。
测试类的私有方法可能有好处,尤其是在测试驱动的开发中,您希望在编写任何代码之前先设计小型测试。
创建具有对私有成员和方法的访问权的测试可以测试仅通过访问公共方法而难以专门针对的代码区域。如果公共方法涉及多个步骤,则它可以包含多个私有方法,然后可以分别进行测试。
优点:
缺点:
但是,如果连续测试需要此方法,则可能表明应该提取私有方法,可以采用传统的公共方法进行测试。
这是一个如何工作的复杂示例:
// 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
。
正如其他人所说的...不要直接测试私有方法。这里有一些想法:
在单元测试上运行代码覆盖率。如果您发现方法未经过完全测试,请添加到测试中以扩大覆盖范围。争取100%的代码覆盖率,但要意识到您可能不会明白。
私有方法由公共方法使用。否则,它们就是无效代码。这就是为什么要测试公共方法,声明公共方法的预期结果以及由此使用的私有方法的原因。
在私有方法上运行单元测试之前,应通过调试来测试私有方法。
也可以使用测试驱动的开发来调试它们,调试单元测试,直到满足所有声明为止。
我个人认为最好使用TDD创建类。创建公共方法存根,然后使用预先定义的所有断言生成单元测试,因此在对方法进行编码之前,应确定该方法的预期结果。这样,您就不会沿着使单元测试断言适合结果的错误道路走下去。这样,您的课程将变得很健壮,并且在所有单元测试都通过后就可以满足要求。
如果使用Spring,则ReflectionTestUtils提供了一些方便的工具,可以在不费吹灰之力的情况下为您提供帮助。例如,在不强制添加不受欢迎的公共二传手的情况下在私有成员上设置模拟:
ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);
如果您要测试您不愿或无法更改的现有代码,则反射是一个不错的选择。
如果该类的设计仍然很灵活,并且您想单独测试一个复杂的私有方法,则建议您将其拉到一个单独的类中并分别测试该类。不必更改原始类的公共接口。它可以在内部创建helper类的实例并调用helper方法。
如果要测试来自辅助方法的困难错误条件,则可以更进一步。从帮助程序类中提取一个接口,向原始类中添加一个公共获取器和设置器,以注入辅助程序类(通过其接口使用),然后将模拟的辅助程序类注入到原始类中,以测试原始类的效果响应助手的异常。如果您想测试原始类而不测试助手类,则此方法也很有用。
测试私有方法会破坏类的封装,因为每次更改内部实现时,都会破坏客户端代码(在这种情况下为测试)。
因此,请勿测试私有方法。
来自JUnit.org常见问题页面的答案:
但是如果你必须...
如果使用的是JDK 1.3或更高版本,则可以在PrivilegedAccessor的帮助下使用反射来破坏访问控制机制。有关如何使用它的详细信息,请阅读本文。
如果使用的是JDK 1.6或更高版本,并使用@Test注释测试,则可以使用Dp4j在测试方法中注入反射。有关如何使用它的详细信息,请参见此测试脚本。
如果要在无法更改代码的情况下测试旧版应用程序的私有方法,则Java的一个选项是jMockit,即使您的对象是类的私有对象,它也可以创建对象的对象。
Deencapsulation.invoke(instance, "privateMethod", param1, param2);
如果您使用的是JUnit,请查看junit-addons。它具有忽略Java安全模型并访问私有方法和属性的能力。
我建议您重构一下代码。当您不得不开始考虑使用反射或其他类型的东西时,仅仅为了测试您的代码,您的代码就出了问题。
您提到了不同类型的问题。让我们从私有字段开始。如果是私有字段,我会添加一个新的构造函数并将字段注入其中。代替这个:
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;
}
我写的大部分内容看起来都是依赖注入模式。以我的个人经验,它在测试时确实很有用,而且我认为这种代码更简洁并且更易于维护。对于嵌套类,我也会说同样的话。如果嵌套类包含繁琐的逻辑,则最好将其作为包私有类移动并将其注入需要它的类中。
在重构和维护遗留代码时,我还使用了其他几种设计模式,但这都取决于要测试的代码的情况。大多数情况下,使用反射不是问题,但是当您拥有经过严格测试的企业应用程序并且在每次部署之前都运行测试时,一切都会变得非常缓慢(这很烦人,我不喜欢这种东西)。
也有二传手注射,但我不建议使用它。我最好坚持使用构造函数,并在确实需要时初始化所有内容,从而留出注入必要依赖的可能性。
ClassToTest(Callable)
注射。这变得ClassToTest
更加复杂。把事情简单化。而且,这还需要其他人告诉应该成为老板的ClassToTest
事情ClassToTest
。有一个注入逻辑的地方,但事实并非如此。您使课程变得更加难以维护,而不是更加简单。
X
不会将X
复杂性增加到可能引起更多问题的地步,因此也需要进行额外的测试,则是更可取的。 (这不是无限循环;每次迭代都可能小于之前的迭代,但仍然很烦人)
ClassToTest
难于维护吗?实际上,它使您的应用程序更易于维护,您建议为所需的每个不同值first
和“第二”变量创建新类吗?
class A{ A(){} f(){} }
比class A { Logic f; A(logic_f) } class B { g() { new A( logic_f) } }
。更简单。如果那不是真的,并且确实很容易维护提供类的逻辑作为构造函数参数,那么我们将所有逻辑作为构造函数参数传递。我只是看不到您如何声称class B{ g() { new A( you_dont_know_your_own_logic_but_I_do ) } }
曾经使A易于维护。在某些情况下,这样的注入是有道理的,但我不认为您的示例“更易于维护”
私有方法只能在同一类中访问。因此,无法从任何测试类中测试目标类的“私有”方法。解决方法是您可以手动执行单元测试,也可以将方法从“专用”更改为“受保护”。
然后,只能在定义了类的同一包中访问受保护的方法。因此,测试目标类的受保护方法意味着我们需要在与目标类相同的程序包中定义测试类。
如果以上都不符合您的要求,请使用反射方式访问私有方法。
正如以上许多建议所建议的,一种好方法是通过您的公共接口对其进行测试。
如果这样做,则最好使用代码覆盖率工具(例如Emma)来查看您的私有方法是否实际上已从测试中执行。
这是我测试私有字段的通用函数:
protected <F> F getPrivateField(String fieldName, Object obj)
throws NoSuchFieldException, IllegalAccessException {
Field field =
obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return (F)field.get(obj);
}
请参见下面的示例;
应该添加以下导入语句:
import org.powermock.reflect.Whitebox;
现在,您可以直接传递具有私有方法,要调用的方法名称以及其他参数的对象,如下所示。
Whitebox.invokeMethod(obj, "privateMethod", "param1");
今天,我推出了一个Java库来帮助测试私有方法和字段。它在设计时就考虑到了Android,但实际上可以用于任何Java项目。
如果您使用私有方法或字段或构造函数获取一些代码,则可以使用BoundBox。它确实满足您的需求。以下是一个测试示例,该示例访问一个Android活动的两个私有字段进行测试:
@UiThreadTest
public void testCompute() {
// Given
boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());
// When
boundBoxOfMainActivity.boundBox_getButtonMain().performClick();
// Then
assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
}
BoundBox可以轻松测试私有/受保护的字段,方法和构造函数。您甚至可以访问被继承隐藏的内容。实际上,BoundBox破坏了封装。它将使您能够通过反射访问所有内容,但在编译时会检查所有内容。
它是测试某些旧代码的理想选择。小心使用。;)
首先,我将提出这个问题:为什么您的私人成员需要隔离测试?它们是否如此复杂,提供的行为如此复杂,以至于需要进行公开测试以外的测试?它是单元测试,而不是“代码行”测试。不要汗流stuff背。
如果它们足够大,足够大,以至于这些私人成员各自都是复杂的“单位”,请考虑将此类私人成员从此类中重构出来。
如果重构不合适或不可行,在进行单元测试时,是否可以使用策略模式替换对这些私有成员函数/成员类的访问?在单元测试中,该策略将提供更多的验证,但是在发行版本中,这将是简单的传递。
我最近遇到了这个问题,并编写了一个名为Picklock的小工具,它避免了显式使用Java反射API的问题,这是两个示例:
调用方法,例如private void method(String s)
-通过Java反射
Method method = targetClass.getDeclaredMethod("method", String.class);
method.setAccessible(true);
return method.invoke(targetObject, "mystring");
调用方法,例如private void method(String s)
-通过Picklock
interface Accessible {
void method(String s);
}
...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.method("mystring");
设置字段,例如private BigInteger amount;
-通过Java反射
Field field = targetClass.getDeclaredField("amount");
field.setAccessible(true);
field.set(object, BigInteger.valueOf(42));
设置字段,例如private BigInteger amount;
-通过Picklock
interface Accessible {
void setAmount(BigInteger amount);
}
...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.setAmount(BigInteger.valueOf(42));
PowerMockito是为此而制造的。使用Maven依赖
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-mockito-release-full</artifactId>
<version>1.6.4</version>
</dependency>
那你可以做
import org.powermock.reflect.Whitebox;
...
MyClass sut = new MyClass();
SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);