IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> PowerMock 教程(配合 Jacoco) -> 正文阅读

[开发测试]PowerMock 教程(配合 Jacoco)

是我在使用 Powermock 中逐渐归纳的一些使用方法、遇到的坑、以及不同场景的最佳实践。文章没有对 PowerMock 这个框架做非常详细的介绍,但已经足够使用了。缺点是没有那种很长很全面的案例,因为我不想让文章看起来太长太复杂。但全部读完再动手实践一下,应该能应对 99% 的场景了。

PowerMock 使用方法

一、PowerMock 介绍

Mockito 与 PowerMock 都是 Java 流行的 Mock 框架,使用Mock技术能让我们隔离外部依赖以便对我们自己的业务逻辑代码进行单元测试。

Mockito 的工作原理是通过创建依赖对象的 proxy,所有的调用先经过 proxy 对象,proxy 对象拦截了所有的请求再根据预设的返回值进行处理。

PowerMock 则在 Mockito 原有的基础上做了扩展,通过修改类字节码并使用自定义 ClassLoader 加载运行的方式来实现 mock 静态方法、final 方法、private 方法、系统类的功能。

从两者的项目结构中就可以看出,PowerMock 直接依赖于 Mockito,所以在项目中使用时只需要导入 PowerMock 包,不需要再单独导入 Mockito 包。并且 PowerMock 完全支持了 Mocktio 的 API,又在此基础上增加了自己的 API,可以看作是 Mocktio 的增强版。

二、环境搭建

(一)maven 引入

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>2.0.9</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>2.0.9</version>
    <scope>test</scope>
</dependency>

(二)常用注解

1、@Target(ElementType.TYPE)

常用在类上面的注解有以下几个:

  • @RunWith

    在测试类类名上添加 @RunWith(PowerMockRunner.class) 注解代表该测试类使用 PowerMock。必须添加

  • @PrepareForTest

    这个注解的作用就是告诉 PowerMock 哪些类是需要在字节码级别上进行操作的。也就是需要 mock 某些包含 final、static 等方法的类时使用,使用方法:@PrepareForTest({System.class, LogUtils.class})

  • @PowerMockIgnore

    PowerMock 是使用自定义的类加载器来加载被修改过的类,从而达到打桩的目的。@PowerMockIgnore 注解告诉 PowerMock 忽略哪些包下的类,从而消除类加载器引入的 ClassCastException。使用方法:@PowerMockIgnore({“javax.management.*”, “javax.net.ssl.*”, “javax.script.*”})

  • @SuppressStaticInitializationFor

    告诉 PowerMock 哪些包下的类需要被抑制静态初始化,包括 static 代码块或者 static 变量的初始化。防止因静态初始化导致的错误。使用方法:@SuppressStaticInitializationFor({“com.xxx.SmsServiceImpl”})

2、@Target({ElementType.FIELD})

常用在字段/属性上面的注解有以下几个:

  • @InjectMocks

    创建一个待测类的实例,其余用 @Mock 注解创建的 mock 对象都将被自动注入到用该实例中。

    Spring 使用 @Autowird 完成自动注入。但在单元测试中,没有启动 spring 框架,此时就需要通过 @ InjectMocks 完成依赖注入。@InjectMocks 会将带有 @Mock 注解的对象注入到待测试的目标类中。

  • @Mock

    创建一个 mock 对象。

使用 @InjectMocks 和 @Mock 配合能 mock 出被 Spring 容器托管的 bean,并自动注入到待测试类中。

@RunWith(PowerMockRunner.class)
@PrepareForTest({})
@PowerMockIgnore({"javax.management.*", "javax.net.ssl.*", "javax.script.*"})
public class MyServiceTest {

    @InjectMocks
    private MyService testInstance; //待测类

    @Mock
    private MyDao myDao; //mock 对象:myDao 会被自动注入到 testInstance 中

}

3、@Target(ElementType.METHOD)

常用在方法上面的注解有以下几个:

  • @Before

    初始化方法,对于每一个测试方法都要执行一次,一般用于测试前的属性赋值、mock 待测试方法等。注意和 @BeforeClass 的区别,后者是必须为 static void,只执行一次,针对所有方法。

  • @Test

    @Test 放在方法上表示这是一个测试方法。

(三)导包

PowerMock/Mocktio 在 mock 方法时是采用 when…thenDo… 的逻辑,如

PowerMocktio.when(myDao.queryById(ArgumentMatchers.any(Long.class))).thenReturn(new Entity());

上面代码表示:当执行 myDao 对象的 queryById() 方法时,只要改方法接受的参数是 Long 类型,无论参数的值是什么,都返回一个 new Entity() 对象。

  • 类似的执行逻辑还有:when().thenThrow() 抛出指定异常; when().thenAnswer() 执行自定义逻辑。

    如果执行的方法无返回值,不需要抛异常,仅仅只是想要 mock 掉执行过程,则可以 doNothing().when()

  • 类似的参数通配符有:anyInt()、anyString()、anyObject()、isA()、isNull()、notNull()、eq()…

推荐在导包时加入:

import static org.mockito.ArgumentMatchers.*;
import static org.powermock.api.mockito.PowerMockito.*;

就可以免去写 PowerMocktio 和 ArgumentMatchers,直接调用 when()、any()…,以及 mock()、mockStatic()…

三、Mock

(一)普通属性

  • 方法一:@Mock

    mock 待测类的普通属性,最常见的就是通过 Spring @Autowired 自动注入的 bean。这类属性并非 final,也并非 static,只需要在测试类中使用 @Mock 注解,Pwermock 框架就能自动生成 mock 对象,并自动注入到 @InjectMock 修饰的待测类中。

  • 方法二:mock()

    也可以使用比较通用的 mock() 方法,调用 mock(XXX.class),能生成一个 mock 的 XXX 对象,然后通过反射获取待测类的字段,再将 mock 对象赋给字段。一般用于不是由 Spring @Autowired 注入的普通属性。

@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*", "javax.net.ssl.*", "javax.script.*"})
public class MyServiceTest {

    @InjectMocks
    private MyService testInstance;

    @Mock
    private MyDao myDao; //方法一

    // @Before 方法会在所有待测方法前执行一次
    // 对于字段的赋值、或者某些返回值是固定的方法的 mock,建议在这里做
    @Before 
    public void init() throws Exception {
        RedisUtils redisUtilsMock = mock(RedisUtils.class); //方法二
        Field redisUtilsField = testInstance.getClass().getDeclaredField("redisUtils"); //使用反射获取待测类的字段
        redisUtilsField.setAccessible(true);
        redisUtilsField.set(testInstance, redisUtilsMock);//赋值

    }
}

(二)普通方法

PowerMock/Mocktio 在 mock 方法时是采用 when…thenDo… 的逻辑。在 mock 了对象之后,PowerMock 会自动 mock 该对象内的普通方法,不过是执行结果都是固定的,如果要自定义 mock 方法。可以如下:

when(myDao.queryById(any(Long.class))).thenReturn(new Entity());

上面代码表示:当执行 myDao 对象的 queryById() 方法时,只要该方法接受的参数是 Long 类型,无论参数的值是什么,都返回一个 new Entity() 对象。

  • 类似的执行逻辑还有:when().thenThrow() 抛出指定异常; when().thenAnswer() 执行自定义逻辑。

    如果执行的方法无返回值,不需要抛异常,仅仅只是想要 mock 掉执行过程,则可以 doNothing().when()

  • 类似的参数通配符有:anyInt()、anyString()、anyObject()、isA()、isNull()、notNull()、eq()…

(三)private 属性

参考普通属性

(四)private 方法

需要在 @PrepareForTest 注解中添加要 mock 的类

@PrepareForTest({MyDao.class})
  • 方法一:

    对于 private 方法,因为无法在外界直接访问,就不能像普通方法那样 mock,应该使用下面的方法,不使用对象直接调用,而是通过方法名字去调用:

    when(myDao, "queryById", anyLong()).thenReturn(new Entity());
    

    这是一种通用的方法。可以 mock private 方法,也可以 mock 普通方法。

  • 方法二:

    MemberModifier.stub()

    方法一是 mock 某个对象的方法,而方法二则直接在类的层次上进行 mock:

    // 使用这种方法的时候,待 mock 方法的参数不是使用通配符 anyLong(),而是使用泛型 Long.class
    MemberModifier.stub(MemberMatcher.method(MyDao.class, "queryById", Long.class)).toReturn(new Entity());
    

    这是一种更通用的方法,可用于待测类本身、父类、其他类的方法等等。由于是在类的层次上进行 mock,所以常用于 mock 没有实例的方法,比如通过继承获得的父类的方法,或者直接通过类名调用的 static 方法。当然,也可以用于 mock 实例的方法。

    注意:如果代码覆盖率检测工具使用的是 jacoco,则无法 mock 待测类自身,因为类要放到 @PrepareForTest 注解中,而 jacoco 会忽略掉该注解中的类,覆盖率会为 0

(五)final 属性

需要在 @PrepareForTest 注解中添加要 mock 的类

参考普通属性

(六)final 方法

需要在 @PrepareForTest 注解中添加要 mock 的类

参考 private 方法和普通方法

(七)static 属性

需要在 @PrepareForTest 注解中添加要 mock 的类

在写单元测试时,一般时是通过待测类的实例来进行测试的,但是 static 属性并不属于实例。一般如果是需要对 static 属性中的某一两个方法进行 mock,则可以直接使用前面介绍的 private 方法中的 MemberModifier.stub() 来实现。

但如果有很多方法或者所有方法都需要 mock,并且没有什么定制化需求,直接让 PowerMock 自动实现,则可以通过下面的方法:

Whitebox.setInternalState()

LogUtils logUtilsMock = mock(LogUtils.class);
Whitebox.setInternalState(testInstance.getClass(), "logUtils", logUtilsMock);

然后对于 static 属性中的普通或者 private 方法的 mock,参考普通方法或者 private 方法

(八)static 方法

需要在 @PrepareForTest 注解中添加要 mock 的类

对于 static 方法的 mock,PowerMock 提供了一个 mockStatic() 方法,该方法对需要 mock static 方法的类中所有 static 方法进行自动 mock。

// 将 SendEmailProxy 类中的所有 static 方法 mock 掉
mockStatic(SendEmailProxy.class);

一般情况这个注解就够用,如果需要自定义 static 方法的返回值,参考 private 方法的方法二

(九)new

需要在 @PrepareForTest 注解中添加要 mock 的类

一般很少 mock new 对象,但如果遇到待测方法内出现 new 依赖的对象,则需要对 new 对象也进行 mock。

Entity entity = new Entity();
whenNew(Entity.class).withAnyArguments().thenReturn(entity);

注意:由于要将需要 mock new 的类加入 @PrepareForTest 中,所以如果代码覆盖率检测工具使用的是 jacoco,则无法 mock 待测类自身

(十)待测类的 private 方法

前面介绍了其他类的 private 方法的 mock。但有时候待测类里也会有一些 private 方法,而这些 private 方法 一般是给 public 方法调用的。比如有一个 private X 方法,而其他的 public A、B、C 三个方法都会调用到 X 方法。

所以我们需要把这个 private 方法也 mock 掉,但是由于要将需要 mock 的类加入 @PrepareForTest 直接中,而代码覆盖率检测工具使用的是 jacoco 时是无法 mock 待测类自身的,所以对于待测类本身的 private 方法,无法做到一步就直接 mock 掉,只能对 private 方法里面的内容进行 mock,以达到 mock 整个 private 方法的效果。

推荐的方式是在测试类中自定义一个方法,这个方法中对待测类的 private 方法中需要 mock 的地方进行 mock,并且该方法接受的参数就是待测类返回的参数,这样在每个需要 mock private 方法的地方,调用这个方法,就能实现“一步”完成 mock,并且还能自定义返回值。

该方法比较通用,对于待测类的所有需要 mock 的方法,都可以这样做。

四、测试

mock 完成后,就可以调用待测试的方法进行测试。

(一)测试方法

一般来说,待测类和测试类是放在同一个包下的,所以 public、protected、缺省的方法都是可以通过待测类的实例直接调用来执行的。

static 方法则直接通过类名来调用。

通用的方法(包括 private)是通过反射获取待测方法执行。

(二)多分支测试

PowerMock 的 when…do… 支持指定多次调用的返回值。如

when(myDao.queryById(any(Long.class))).thenReturn(new Entity(), null, new SQLException());

上面代码表示当调用 myDao.queryById() 时,第一次调用返回一个 Entity 对象,第二次返回 null,第三次抛出异常。

对于多分支的测试而言,就可以不需要写多个方法,而是在一个方法里完成多条分支的测试。

  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2021-11-30 15:55:05  更:2021-11-30 15:55:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/18 5:29:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码