Java测试框架系列:PowerMock使用系列-3:抑制不想要的行为

1,861 阅读5分钟

抑制不想要的行为

总览

  1. 在测试用例的类上使用 @RunWith(PowerMockRunner.class)注解。
  2. 在测试用例的类上结合使用@PrepareForTest(ClassWithEvilParentConstructor.class)suppress(constructor(EvilParent.class))注解,以禁止调用EvilParent类的所有构造函数。
  3. 使用Whitebox.newInstance(ClassWithEvilConstructor.class) 方法去实例化一个类而无须调用其构造函数。
  4. 使用@SuppressStaticInitializationFor("org.mycompany.ClassWithEvilStaticInitializer")注解移除类org.mycompany.ClassWithEvilStaticInitializer的静态初始化器。
  5. 在类上使用 @PrepareForTest(ClassWithEvilMethod.class) 注解结合suppress(method(ClassWithEvilMethod.class, "methodName")) 去抑制类ClassWithEvilMethod中的 "methodName" 方法。
  6. 在类上使用 @PrepareForTest(ClassWithEvilField.class) 注解结合 suppress(field(ClassWithEvilField.class, "fieldName")) 去抑制类 ClassWithEvilField 中的"fieldName" 字段。

您可以在此处找到成员修改和成员匹配器方法:

  • org.powermock.api.support.membermodification.MemberModifier
  • org.powermock.api.support.membermodification.MemberMatcher

示例

有时,您甚至希望抑制某些构造函数,方法或静态初始化程序的行为,以便对您自己的代码进行单元测试。一个典型的例子是您的类需要从某种第三方框架中的另一个类扩展而来。当第3方类在其构造函数中执行某些操作而导致您无法对自己的代码进行单元测试时,就会出现问题。例如,由于某种原因,框架可能尝试加载dll或访问网络或文件系统。让我们看一些例子。

禁止超类构造函数

作为示例,让我们看一下名为ExampleWithEvilParent的类,它非常简单:

public class ExampleWithEvilParent extends EvilParent {

	private final String message;

	public ExampleWithEvilParent(String message) {
		this.message = message;
	}

	public String getMessage() {
		return message;
	}
}

这似乎是一个易于进行单元测试的类(实际上如此简单,您可能不应该对其进行测试,但是为了演示起见,让我们继续进行测试)。但是,等等,让我们看一下EvilParent类的样子:

public class EvilParent {

	public EvilParent() {
		System.loadLibrary("evil.dll");
	}
}

该父类尝试加载一个dll文件,该文件在您对ExampleWithEvilParent类运行单元测试时将不存在。使用PowerMock,您可以仅抑制EvilParent的构造函数,以便可以对ExampleWithEvilParent类进行单元测试。这是通过使用PowerMock API中的suppress方法完成的。在这种情况下,我们将执行以下操作:

suppress(constructor(EvilParent.class));

您还必须准备ExampleWithEvilParent以进行测试(因为从此类中调用了EvilParent的构造函数)。您可以通过将传递ExampleWithEvilParent.class@PrepareForTest注解来实现:

@PrepareForTest(ExampleWithEvilParent.class)

完整的测试如下所示:

@RunWith(PowerMockRunner.class)
@PrepareForTest(ExampleWithEvilParent.class)
public class ExampleWithEvilParentTest {

	@Test
	public void testSuppressConstructorOfEvilParent() throws Exception {
		suppress(constructor(EvilParent.class));
		final String message = "myMessage";
		ExampleWithEvilParent tested = new ExampleWithEvilParent(message);
		assertEquals(message, tested.getMessage());
	}
}

如果超类具有多个构造函数,则可以告诉PowerMock仅抑制特定的构造函数。假设您有一个名为ClassWithSeveralConstructors的类,该类的一个构造函数采用String而另一个构造函数采用int作为参数,而您只想移除采用String构造函数。您可以使用

suppress(constructor(ClassWithSeveralConstructors.class, String.class));

方法。

抑制自己的构造函数

上面的示例在抑制超类构造函数和被测类时起作用。抑制被测构造函数的另一种方法是使用Whitebox.newInstance方法。如果您自己的代码在其构造函数中执行了某些操作,导致难以进行单元测试。这将实例化该类,而根本不调用构造函数。例如,假设您要对以下类进行单元测试:

public class ExampleWithEvilConstructor {

	private final String message;

	public ExampleWithEvilConstructor(String message) {
		System.loadLibrary("evil.dll");
		this.message = message;
	}

	public String getMessage() {
		return message;
	}
}

要实例化此类,我们使用以下Whitebox.newInstance方法:

ExampleWithEvilConstructor tested = Whitebox.newInstance(ExampleWithEvilConstructor.class);

完整的测试如下所示:

public class ExampleWithEvilConstructorTest {	@Test	public void testSuppressOwnConstructor() throws Exception {		ExampleWithEvilConstructor tested = Whitebox.newInstance(ExampleWithEvilConstructor.class);		assertNull(tested.getMessage());	}}

请注意,您不需要使用@RunWith(..)注解或将类传递给@PrepareForTest注解。这样做并没有什么害处,但这不是必需的。要理解的另一件重要事情是,由于从不执行构造函数,因此该tested.getMessage()方法将返回null。您当然可以通过绕过封装来更改测试实例中message字段的状态。

抑制方法

在某些情况下,您只想抑制一个方法并使其返回一些默认值,在其他情况下,您可能需要抑制或模拟一个方法,因为该方法会阻止您对自己的类进行单元测试。看下面的虚构的示例:

public class ExampleWithEvilMethod {	private final String message;	public ExampleWithEvilMethod(String message) {		this.message = message;	}	public String getMessage() {		return message + getEvilMessage();	}	private String getEvilMessage() {		System.loadLibrary("evil.dll");		return "evil!";	}}

如果在测试getMessage()方法时执行了System.loadLibrary("evil.dll")语句,则测试将失败。避免这种情况的一种简单方法是简单地抑制getEvilMessage方法。您可以使用PowerMock API中的suppress(method(..))的方法来执行此操作,在这种情况下,您可以执行以下操作:

suppress(method(ExampleWithEvilMethod.class, "getEvilMessage"));

完整的测试如下所示:

@RunWith(PowerMockRunner.class)@PrepareForTest(ExampleWithEvilMethod.class)public class ExampleWithEvilMethodTest {	@Test	public void testSuppressMethod() throws Exception {		suppress(method(ExampleWithEvilMethod.class, "getEvilMessage"));		final String message = "myMessage";		ExampleWithEvilMethod tested = new ExampleWithEvilMethod(message);		assertEquals(message, tested.getMessage());	}}

抑制静态初始化器

有时,第三方类在其静态初始化程序(也称为静态构造函数)中执行某些操作,从而阻止您对自己的类进行单元测试。您自己的类也有可能在静态初始化器中执行某些操作,而您在单元测试类时不希望发生这种情况。因此,PowerMock可以简单地抑制该类的静态初始化。为此,您可以在测试的类级别或方法级别指定@SuppressStaticInitializationFor注解。例如,假设您要对以下类进行单元测试:

public class ExampleWithEvilStaticInitializer {	static {		System.loadLibrary("evil.dll");	}	private final String message;	public ExampleWithEvilStaticInitializer(String message) {		this.message = message;	}	public String getMessage() {		return message;	}}

这里的问题是,当加载ExampleWithEvilStaticInitializer类时,将执行静态代码块,并且System.loadLibrary("evil.dll")代码块将被执行,从而导致单元测试失败(无法加载evil.dll)。为了抑制此静态初始化器,我们这样做:

@SuppressStaticInitializationFor("org.mycompany.ExampleWithEvilStaticInitializer")

如您所见,我们没有将ExampleWithEvilStaticInitializer.class传递给@SuppressStaticInitializationFor而是给了它全称的类名。原因是,如果要传递ExampleWithEvilStaticInitializer.class给注解,则静态初始化程序将在测试开始之前运行,因此测试将失败。因此,在删除静态初始化器时,必须将全限定名传递给该类。整个测试需要:

@RunWith(PowerMockRunner.class)@SuppressStaticInitializationFor("org.mycompany.ExampleWithEvilStaticInitializer")public class ExampleWithEvilStaticInitializerTest {	@Test	public void testSuppressStaticInitializer() throws Exception {		final String message = "myMessage";		ExampleWithEvilStaticInitializer tested = new ExampleWithEvilStaticInitializer(message);		assertEquals(message, tested.getMessage());	}}

抑制字段

您也可以使用suppress(..)抑制字段。例如,假设您有以下类:

public class MyClass {	private MyObject myObject = new MyObject();	public MyObject getMyObject() {		return myObject;	}}

要抑制上面的myObject字段,您可以执行以下操作:

suppress(field(MyClass.class, "myObject"));

调用getMyObject()之后,将为准备进行测试的每个MyClass实例返回null

References