转自:https://www.ibm.com/developerworks/cn/java/j-lo-powermock/index.html
本文主要内容:
1、@PrepareForTest注释
2、访问私有状态内容
3、抑制(禁止加载),即不运行,不需要的内容
4、测试听众
5、模拟策略
6、模拟系统类
PowerMock是一个Java模拟框架,可用于解决通常认为很难甚至无法测试的测试问题。使用PowerMock,可以模拟静态方法,删除静态初始化程序,允许模拟而不依赖于注入,等等。PowerMock通过在执行测试时在运行时修改字节码来完成这些技巧。PowerMock还包含一些实用程序,可让您更轻松地访问对象的内部状态。
PowerMock由两个扩展API组成。
一个用于EasyMock,一个用于Mockito。要使用PowerMock,您需要依赖这些API中的一个以及测试框架。
目前PowerMock支持JUnit和TestNG。有三种不同的JUnit测试执行程序可供使用,一种适用于JUnit 4.4+,一种适用于JUnit 4.0-4.3,一种适用于JUnit 3(已弃用)。
TestNG有一个测试执行器,它需要版本5.11+,具体取决于您使用的PowerMock版本。
@PrepareForTest这个注释告诉PowerMock准备测试某些类。需要使用此批注定义的类通常是需要进行字节码操作的类。这包括final类,带有final,private,static或本地方法的类,这些方法应该被mock,并且类应该在实例化时返回一个模拟对象。
这个注释可以放在测试类或者单独的测试方法中。
如果放在一个类上,这个测试类中的所有测试方法都将由PowerMock处理(以便测试)。
如果要为单个方法重写此行为,只需在特定测试方法上放置@PrepareForTest注释。例如,如果您想在测试方法A中修改类X,但在测试方法B中希望X完好无损,那么这很有用。在这种情况下,您在方法B上放置@PrepareForTest,并从value()列表中排除类X.
有时你需要准备内部类来进行测试,这可以通过提供应该模拟到fullyQualifiedNames()列表的内部类的完全限定名来完成。
@Target( { ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface PrepareForTest {
Class<?>[] value() default IndicateReloadClass.class;
String[] fullyQualifiedNames() default "";
}
访问内部状态:
使用Whitebox.setInternalState(..)设置一个实例或类的非公共成员。
使用Whitebox.getInternalState(..)得到的实例或类的非公共成员。
使用Whitebox.invokeMethod(..)调用实例或类的非公共方法。
用于使用Whitebox.invokeConstructor(..)私有构造函数创建类的实例。
注意:
所有这些都可以在不使用PowerMock的情况下实现,这只是普通的Java反射。然而,反射需要大量的锅炉代码,并且容易出错,因此PowerMock会为您提供这些实用方法。PowerMock让您可以选择是否重构代码并添加getter / setter方法来检查/更改内部状态,或者是否使用其实用方法在不更改生产代码的情况下完成相同的操作。
抑制不需要的行为:
有时候你想甚至需要抑制某些构造函数,方法或静态初始化器的行为,以便单元测试你自己的代码。一个典型的例子是当你的类需要在某种第三方框架中从另一个类扩展时。当这个第三方类在构造函数中做些什么来阻止你单元测试你自己的代码时,就会出现问题。例如,框架可能会尝试加载一个DLL或出于某种原因访问网络或文件系统。
抑制构造函数:
假设我们要测试ExampleWithEvilParent类的getMessage()方法,看起来好像很简单。但是这个父类试图加载一个dll文件,当你为这个ExampleWithEvilParent类运行一个单元测试时它将不会出现。使用PowerMock,您可以禁止EvilParent的构造函数,以便您可以单元测试ExampleWithEvilParent类。
public class ExampleWithEvilParent extends EvilParent {
private final String message;
public ExampleWithEvilParent(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
public class EvilParent {
public EvilParent() {
System.loadLibrary("evil.dll");
}
}
测试代码如下:
@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());
}
}
上面的例子在抑制超类构造函数和被测类时起作用。另一种抑制被测试类的构造函数,我们通过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;
}
}
通过下面的方式抑制:
ExampleWithEvilConstructor tested = Whitebox.newInstance(ExampleWithEvilConstructor.class);
1
请注意,您不需要使用@RunWith(..)注释或将类传递给@PrepareForTest注释。这样做并没有伤害,但没有必要。
抑制私有方法:
在某些情况下,你只是想压制一个方法并使其返回一些默认值,在其他情况下,你可能需要压制或模拟一个方法,因为它会阻止你对自己的类进行单元测试。看看下面的组装示例:
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!";
}
}
如果System.loadLibrary(“evil.dll”)在测试getMessage()方法时执行语句,则测试将失败。避免这种情况的一个简单方法是简单地抑制该getEvilMessage方法。你可以使用
suppress(method(ExampleWithEvilMethod.class, "getEvilMessage"));
1
完整的测试如下:
@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());
}
}
禁止静态初始化器:
public class ExampleWithEvilStaticInitializer {
static {
System.loadLibrary("evil.dll");
}
private final String message;
public ExampleWithEvilStaticInitializer(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
这个大家应该比较熟悉,直接贴出完整测试:
@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());
}
}
抑制字段:
public class MyClass {
private MyObject myObject = new MyObject();
public MyObject getMyObject() {
return myObject;
}
}
suppress(field(MyClass.class, "myObject"));
1
注意:
在powermock+mockit中,抑制语法如下:
PowerMockito.suppress(PowerMockito.method(类.class,"方法名"));
1
测试听众
PowerMock 1.1及以上版本具有测试监听器的概念。测试监听器可用于从测试框架获取事件,例如测试方法开始和结束以及测试执行的结果。这些测试监听器的目的是提供独立于测试框架的方式,通过实现org.powermock.core.spi.PowerMockTestListener并将其传递给PowerMockListener注释来获取和响应这些通知。PowerMock有一些内置的测试监听器供您使用。
1、AnnotationEnabler
考虑下面的一段代码:
@RunWith(PowerMockRunner.class)
@PowerMockListener(AnnotationEnabler.class)
public class PersonServiceTest {
@Mock
private PersonDao personDaoMock;
private PersonService classUnderTest;
@Before
public void setUp() {
classUnderTest = new PersonService(personDaoMock);
}
...
}
使用@Mock注释消除了手动设置和拆卸模拟的需要,这可以最大限度地减少重复测试代码并使测试更具可读性。AnnotationEnabler适用于EasyMock和Mockito API。在EasyMock版本中,如果要创建部分模拟,还可以提供您希望模拟的方法的名称,例如:
@RunWith(PowerMockRunner.class)
@PowerMockListener(AnnotationEnabler.class)
public class PersonServiceTest {
@Mock("getPerson")
private PersonDao personDaoMock;
private PersonService classUnderTest;
@Before
public void setUp() {
classUnderTest = new PersonService(personDaoMock);
}
...
}
这段代码将指示PowerMock创建一个PersonDao只模拟”getPerson”方法的部分模拟。由于EasyMock支持好的和严格的模拟,你可以使用@MockNice和@MockStrict注释来获得这个好处。
在Mockito中,你只是spy(..)用来部分地模拟一个类或实例。
2、FieldDefaulter
此测试监听器实现可用于在每次测试之后为junit测试中的每个成员字段设置默认值。对于许多开发人员来说,使用JUnit时,创建tearDown方法和使所有引用无效的标准过程大致相同(有关此问题的更多信息,请参阅此处)。但是,可以使用FieldDefaulter自动完成此操作。举个例子,假设你有5个合作者想要在你的测试中进行模拟,并且你想确保每次测试之后它们都被设置为null,以允许它们被垃圾收集。所以不要这样做:
@RunWith(PowerMockRunner.class)
public class MyTest {
private Collaborator1 collaborator1Mock;
private Collaborator2 collaborator2Mock;
private Collaborator3 collaborator3Mock;
private Collaborator4 collaborator4Mock;
private Collaborator5 collaborator5Mock;
...
@After
public void tearDown() {
collaborator1Mock = null;
collaborator2Mock = null;
collaborator3Mock = null;
collaborator4Mock = null;
collaborator5Mock = null;
}
...
}
您可以使用FieldDefaulter测试监听器彻底摆脱拆除方法:
@RunWith(PowerMockRunner.class)
@PowerMockListener(FieldDefaulter.class)
public class MyTest {
private Collaborator1 collaborator1Mock;
private Collaborator2 collaborator2Mock;
private Collaborator3 collaborator3Mock;
private Collaborator4 collaborator4Mock;
private Collaborator5 collaborator5Mock;
...
}
模拟策略:
一个模拟策略可以用来更容易地将某些代码与PowerMock单独测试到一个特定的框架中。模拟策略实现可以例如抑制一些方法,抑制静态初始化器或拦截方法调用,并改变它们对于特定框架或一组类或接口的返回值(例如返回模拟对象)。例如,可以实施模拟策略以避免为测试编写重复的设置代码。假设你使用的是框架X,为了让你测试它,需要某些方法总是返回一个模拟实现。也许一些静态初始化器也必须被抑制。而不是在测试之间复制这段代码,写一个可重用的模拟策略是一个好主意。
PowerMock 1.1提供了三种模拟slf4j,java common-logging和log4j的模拟策略。以slf4j为例,假设您有一个如下所示的类:
public class Slf4jUser {
private static final Logger log = LoggerFactory.getLogger(Slf4jUser.class);
public final String getMessage() {
log.debug("getMessage!");
return "log4j user";
}
}
这里我们遇到了一个问题,因为记录器在Slf4jUser类的静态初始化器中被实例化。有时这会导致问题,具体取决于日志配置,因此您想在单元测试中执行的操作是对日志实例进行存根。这是完全可行的,而不使用模拟政策。一种方法是先从我们的测试中禁用Slf4jUser类的静态初始化程序。然后,我们可以创建一个存根或Logger类的一个很好的模拟,并将其注入Slf4jUser实例。但是这还不够,想象一下,我们已经配置了slf4j来使用log4j作为后端日志记录,然后在运行测试时我们会在控制台中显示以下错误:
log4j:ERROR A "org.apache.log4j.RollingFileAppender" object is not assignable to a org.apache.log4j.Appender" variable.
log4j:ERROR The class "org.apache.log4j.Appender" was loaded by
log4j:ERROR [org.powermock.core.classloader.MockClassLoader@aa9835] whereas object of type
log4j:ERROR "org.apache.log4j.RollingFileAppender" was loaded by [sun.misc.Launcher$AppClassLoader@11b86e7].
log4j:ERROR Could not instantiate appender named "R".
为了避免这个错误信息,我们需要准备org.apache.log4j.Appender测试。完整的测试设置将如下所示:
@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("org.myapp.Slf4jUser")
@PrepareForTest( Appender.class)
public class MyTest {
@Before
public void setUp() {
Logger loggerMock = createNiceMock(Logger.class);
Whitebox.setInternalState(Slf4jUser.class, loggerMock);
...
}
...
}
这种设置行为将不得不被复制到处理slf4j的所有测试类。相反,您可以使用Slf4j模拟策略来照顾您为此设置。你的测试看起来像这样:
@RunWith(PowerMockRunner.class)
@MockPolicy(Slf4jMockPolicy.class)
public class Slf4jUserTest {
...
}
请注意,我们没有做任何设置来模拟slf4j,这Slf4jMockPolicy需要照顾。
模拟策略也可以像这样链接或嵌套:
@RunWith(PowerMockRunner.class)
@MockPolicy({MockPolicyX.class, MockPolicyY.class})
public class MyTest {
...
}
请注意,链中的后续模拟策略可以覆盖上述策略的行为。在这个例子中,这意味着MockPolicyY可能会覆盖由定义的行为MockPolicyX。如果编写自定义模拟策略,记住这一点很重要。
还可以创建自定义的模拟策略,这里不再叙述。
模拟系统类:
PowerMock 1.2.5及以上版本支持Java系统类中的模拟方法,例如位于java.lang和java.net中的模拟方法。这可以在不修改JVM或IDE设置的情况下运行!尽管如此,mock这些类的方式有点不同。通常情况下,你需要准备包含静态方法的类(我们称之为X),但是因为PowerMock不可能为测试准备一个系统类,所以必须采取另一种方法。因此,不是准备X,而是准备在X中调用静态方法的类!我们来看一个简单的例子:
public class SystemClassUser {
public String performEncode() throws UnsupportedEncodingException {
return URLEncoder.encode("string", "enc");
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest( { SystemClassUser.class })
public class SystemClassUserTest {
@Test
public void assertThatMockingOfNonFinalSystemClassesWorks() throws Exception {
mockStatic(URLEncoder.class);
expect(URLEncoder.encode("string", "enc")).andReturn("something");
replayAll();
assertEquals("something", new SystemClassUser().performEncode());
verifyAll();
}
}
---------------------
作者:苏璟有点甜
来源:CSDN
原文:https://blog.csdn.net/weixin_39471249/article/details/80398212
版权声明:本文为博主原创文章,转载请附上博文链接!
转自:https://www.ibm.com/developerworks/cn/java/j-lo-powermock/index.html
PowerMock 简介
使用 PowerMock 以及 Mockito 实现单元测试
张 羽 和 吴 长侠
2012 年 4 月 16 日发布
EasyMock 以及 Mockito 都因为可以极大地简化单元测试的书写过程而被许多人应用在自己的工作中,但是这 2 种 Mock 工具都不可以实现对静态函数、构造函数、私有函数、Final 函数以及系统函数的模拟,但是这些方法往往是我们在大型系统中需要的功能。PowerMock 是在 EasyMock 以及 Mockito 基础上的扩展,通过定制类加载器等技术,PowerMock 实现了之前提到的所有模拟功能,使其成为大型系统上单元测试中的必备工具。
单元测试模拟框架的功能及其实现简介
单元测试在软件开发过程中的重要性不言而喻,特别是在测试驱动开发的开发模式越来越流行的前提下,单元测试更成为了软件开发过程中不可或缺的部分。于是相应的,各种单元测试技术也应运而生。本文要介绍的 PowerMock 以及 Mockito 都是简化单元测试书写过程的工具。
Mockito 是一个针对 Java 的单元测试模拟框架,它与 EasyMock 和 jMock 很相似,都是为了简化单元测试过程中测试上下文 ( 或者称之为测试驱动函数以及桩函数 ) 的搭建而开发的工具。在有这些模拟框架之前,为了编写某一个函数的单元测试,程序员必须进行十分繁琐的初始化工作,以保证被测试函数中使用到的环境变量以及其他模块的接口能返回预期的值,有些时候为了单元测试的可行性,甚至需要牺牲被测代码本身的结构。单元测试模拟框架则极大的简化了单元测试的编写过程:在被测试代码需要调用某些接口的时候,直接模拟一个假的接口,并任意指定该接口的行为。这样就可以大大的提高单元测试的效率以及单元测试代码的可读性。
相对于 EasyMock 和 jMock,Mockito 的优点是通过在执行后校验哪些函数已经被调用,消除了对期望行为(expectations)的需要。其它的 mocking 库需要在执行前记录期望行为(expectations),而这导致了丑陋的初始化代码。
但是,Mockito 也并不是完美的,它不提供对静态方法、构造方法、私有方法以及 Final 方法的模拟支持。而程序员时常都会发现自己有对以上这些方法的模拟需求,特别是当一个已有的软件系统摆在面前时。幸好 , 还有 PowerMock。
PowerMock 也是一个单元测试模拟框架,它是在其它单元测试模拟框架的基础上做出的扩展。通过提供定制的类加载器以及一些字节码篡改技巧的应用,PowerMock 现了对静态方法、构造方法、私有方法以及 Final 方法的模拟支持,对静态初始化过程的移除等强大的功能。因为 PowerMock 在扩展功能时完全采用和被扩展的框架相同的 API, 熟悉 PowerMock 所支持的模拟框架的开发者会发现 PowerMock 非常容易上手。PowerMock 的目的就是在当前已经被大家所熟悉的接口上通过添加极少的方法和注释来实现额外的功能,目前,PowerMock 仅支持 EasyMock 和 Mockito。
本文的目的就是和大家一起学习在 Mockito 框架上扩展的 PowerMock 的强大功能。
环境配置方法
对于需要的开发包,PowerMock 网站提供了”一站式”下载 : 从 此页面中选择以类似 PowerMock 1.4.10 with Mockito and JUnit including dependencies 为注释的链接,该包中包含了最新的 JUnit 库,Mockito 库,PowerMock 库以及相关的依赖。
如果是使用 Eclipse 开发,只需要在 Eclipse 工程中包含这些库文件即可。
如果是使用 Maven 开发,则需要根据版本添加以下清单内容到 POM 文件中:
JUnit 版本 4.4 以上请参考清单 1,
清单 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
JUnit 版本 4.0-4.3 请参考清单 2,
清单 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
JUnit 版本 3 请参考清单 3,
清单 3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
PowerMock 在单元测试中的应用
模拟 Static 方法
在任何需要用到 PowerMock 的类开始之前,首先我们要做如下声明:
@RunWith(PowerMockRunner.class)
然后,还需要用注释的形式将需要测试的静态方法提供给 PowerMock:
@PrepareForTest( { YourClassWithEgStaticMethod.class })
然后就可以开始写测试代码:
首先,需要有一个含有 static 方法的代码 , 如清单 4:
清单 4
1 2 3 4 5 6 7 8 9 10 |
|
然后,在被测代码中,引用了以上方法 , 如清单 5 所示:
清单 5
1 2 3 4 5 6 7 8 9 |
|
为了达到单元测试的目的,需要让静态方法 generateNewId()
返回各种值来达到对被测试方法 methodToTest()
的覆盖测试,实现方式如清单 6 所示:
清单 6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
如清单 6 中所展示,在测试代码中,可以使用 When().thenReturn(
) 语句来指定被引用的静态方法返回任意需要的值,达到覆盖测试的效果。
模拟构造函数
有时候,能模拟构造函数,从而使被测代码中 new
操作返回的对象可以被随意定制,会很大程度的提高单元测试的效率,考虑如清单 7 的代码:
清单 7
1 2 3 4 5 6 7 8 9 10 11 12 |
|
为了充分测试 create()
函数,我们需要被 new
出来的 File 对象返回文件存在和不存在两种结果。在 PowerMock 出现之前,实现这个单元测试的方式通常都会需要在实际的文件系统中去创建对应的路径以及文件。然而,在 PowerMock 的帮助下,本函数的测试可以和实际的文件系统彻底独立开来:使用 PowerMock 来模拟 File 类的构造函数,使其返回指定的模拟 File 对象而不是实际的 File 对象,然后只需要通过修改指定的模拟 File 对象的实现,即可实现对被测试代码的覆盖测试,参考如清单 8 的代码:
清单 8
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
使用 whenNew().withArguments().thenReturn()
语句即可实现对具体类的构造函数的模拟操作。然后对于之前创建的模拟对象 directoryMock
使用 When().thenReturn()
语句,即可实现需要的所有功能,从而实现对被测对象的覆盖测试。在本测试中,因为实际的模拟操作是在类 DirectoryStructureTest
中实现,所以需要指定的 @PrepareForTest
对象是 DirectoryStructureTest.class
。
模拟私有以及 Final 方法
为了实现对类的私有方法或者是 Final 方法的模拟操作,需要 PowerMock 提供的另外一项技术:局部模拟。
在之前的介绍的模拟操作中,我们总是去模拟一整个类或者对象,然后使用 When().thenReturn()
语句去指定其中值得关心的部分函数的返回值,从而达到搭建各种测试环境的目标。对于没有使用 When().thenReturn()
方法指定的函数,系统会返回各种类型的默认值(具体值可参考官方文档)。
局部模拟则提供了另外一种方式,在使用局部模拟时,被创建出来的模拟对象依然是原系统对象,虽然可以使用方法 When().thenReturn()
来指定某些具体方法的返回值,但是没有被用此函数修改过的函数依然按照系统原始类的方式来执行。
这种局部模拟的方式的强大之处在于,除开一般方法可以使用之外,Final 方法和私有方法一样可以使用。
参考如清单 9 所示的被测代码:
清单 9
1 2 3 4 5 6 7 8 9 |
|
为了保持单元测试的纯洁性,在测试方法 methodToTest()
时,我们不希望受到私有函数 methodToMock()
实现的干扰,为了达到这个目的,我们使用刚提到的局部模拟方法来实现 , 实现方式如清单 10:
清单 10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
可以发现,为了实现局部模拟操作,用来创建模拟对象的函数从 mock()
变成了 spy()
,操作对象也从类本身变成了一个具体的对象。同时,When()
函数也使用了不同的版本:在模拟私有方法或者是 Final 方法时,When()
函数需要依次指定模拟对象、被指定的函数名字以及针对该函数的输入参数列表。
结束语
以上列举了扩展于 Mockito 版本的 PowerMock 的一部分强大的功能,特别是针对已有的软件系统,利用以上功能可以轻易的完成清晰独立的单元测试代码,帮助我们提高代码质量。
注:
本文中的部分测试代码引用自 Johan Haleby 的 Untestable code with Mockito and PowerMock。
相关主题
- Mockito 官方网站:Mockito 的官方站点 , 在这里你可以找到完整的 Mockito 介绍、接口文档,以及一些代码示例。
- PowerMock 官方网站:PowerMock 的官方站点 , 在这里你可以找到完整的 PowerMock 介绍、接口文档,以及一些代码示例。
- developerWorks Java 技术专区:这里有数百篇关于 Java 编程各个方面的文章。
/**<!-- 单元测试框架 junit4 + powermock start-->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.7.1</version>
</dependency>
<!-- 单元测试框架 junit4 + powermock end-->
*/