Springboot 如何使用Powermock做单元测试

这篇具有很好参考价值的文章主要介绍了Springboot 如何使用Powermock做单元测试。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Powermock介绍

一、为什么要使用Mock工具

在做单元测试的时候,我们会发现我们要测试的方法会引用很多外部依赖的对象,比如:(发送邮件,网络通讯,远程服务, 文件系统等等)。 而我们没法控制这些外部依赖的对象,为了解决这个问题,我们就需要用到Mock工具来模拟这些外部依赖的对象,来完成单元测试。

二、PowerMock简介

PowerMock 也是一个单元测试模拟框架,它是在其它单元测试模拟框架的基础上做出的扩展。通过提供定制的类加载器以及一些字节码篡改技巧的应用,PowerMock 现了对静态方法、构造方法、私有方法以及 Final 方法的模拟支持,对静态初始化过程的移除等强大的功能。因为 PowerMock 在扩展功能时完全采用和被扩展的框架相同的 API, 熟悉 PowerMock 所支持的模拟框架的开发者会发现 PowerMock 非常容易上手。PowerMock 的目的就是在当前已经被大家所熟悉的接口上通过添加极少的方法和注释来实现额外的功能。
简而言之,Powermock是mockito的升级版,但powermock会依赖于mockito。

使用示例

Maven 包引入

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <powermock.version>2.0.0</powermock.version>
</properties>

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>${powermock.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>${powermock.version}</version>
    <scope>test</scope>
</dependency>

简单使用

HelloMockitoTest

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)

//@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@PowerMockIgnore("javax.management.*")
@PrepareForTest({HelloController.class, TestUtils.class})
@ContextConfiguration(classes = TestBizServer.class)
@ActiveProfiles("local")
public class HelloMockitoTest {

    @InjectMocks
    private HelloController helloController;

    @Mock
    private HelloService helloService;

    @Before
    public void init () {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void test () throws Exception {
        Mockito.when(helloService.sayHi(Mockito.any(String.class))).thenReturn("你好啊!");
        String result2 = helloController.testHello();
        System.out.println(result2);
        assert result2.equals("你好啊!");
    }

    @Mock
    private TestUtils testUtils;

    @Test
    public void testSome () throws Exception {
        TestUtils mock = Mockito.mock(TestUtils.class);
        PowerMockito.whenNew(TestUtils.class).withNoArguments().thenReturn(mock);
        Mockito.when(mock.testSome()).thenReturn("123456");
        String s = helloController.doHttpGet("http://127.0.0.1", Maps.newHashMap());
        System.out.println(s);
    }
}

HelloController 这里代码都是伪代码,不要把controller当成一个rest,当成一个Bean即可。

@RestController
@RequestMapping("/hello")
public class HelloController {

    @Autowired(required = false)
    HelloService helloService;

    @GetMapping("/test")
    public String testHello () {
        String hello = null;
        try {
            Thread.sleep(1000);
            hello = helloService.sayHi("hello world");
            System.out.println("hello param is : " + hello);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return hello != null ? hello : "error";
    }

    @GetMapping("/testSome")
    public String doHttpGet(String uri, Map<String, String> getParams) {
        TestUtils testUtils = new TestUtils();
        String s = testUtils.testSome();
        System.out.println("testUtils.testSome return is:" + s);
        CloseableHttpResponse response = null;
        return s;
    }
}

HelloService

@Service
public class HelloService implements InitializingBean {

    @Autowired
    private HelloDao helloDao;

    public String sayHi () {
        System.out.println("HI ...");
        return "hi";
    }

    public String sayHi (String name) {
        System.out.println("HI ..." + name);
        return "hi";
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("我是动态注册的你,不是容器启动的时候注册的你");
    }

    public boolean deleteOne () {
        System.out.println("------------------------------");
        return helloDao.deleteOne();
    }
}

test方法运行如果没有写mock正常会返回HI,这里会返回你好啊!。mockito这中mock方式只适用于Spring中注入代理的Bean才能进行正常的mock。如果说这方法体中实例化了对象或者调用静态方法,这个时候就不起作用了。

testSome中使用了powermock去处理在方法中实例化的新对象,把new对象替换为mock对象(可以理解为一层代理 ),然后让调用mock对象的方法返回我们想要的值。

其实这里可以理解,如果我们想让某个对象的方法返回一个值或者抛出一个异常,我们就需要把这个对象替换成mock对象,可以把mock对象理解为代理对象(打桩),这个是核心思路。

注解讲解

@RunWith

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

@PrepareForTest

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

@PowerMockIgnore

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

@SuppressStaticInitializationFor

告诉 PowerMock 哪些包下的类需要被抑制静态初始化,包括 static 代码块或者 static 变量的初始化。防止因静态初始化导致的错误。使用方法:

@SuppressStaticInitializationFor({com.xxx.SmsServiceImpl})

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

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

PowerMock简单实现原理

1.当某个测试方法被注解@PrepareForTest标注以后,在运行测试用例时,会创建一个新的org.powermock.core.classloader.MockClassLoader实例,然后加载该测试用例使用到的类(系统类除外)。

2.PowerMock会根据你的mock要求,去修改写在注解@PrepareForTest里的class文件(当前测试类会自动加入注解中),以满足特殊的mock需求。例如:去除’final方法的final标识,在静态方法的最前面加入自己的虚拟实现等。

3.如果需要mock的是系统类的final方法和静态方法,PowerMock不会直接修改系统类的class文件,而是修改调用系统类的class文件,以满足mock需求。

Api讲解

whenNew 当创建新对象的时候

PowerMockito.whenNew(Entity.class).withAnyArguments().thenReturn(entity);

thenThrow 抛出异常
when 当执行xx方法的时候,一般后面跟链式调用处理分支
doNothing 什么都不执行的时候,一般后面跟链式调用处理分支
doReturn 返回,一般后面接when(object).xxxMethod(), 意指让mock对象调用某些方法的时候不做任何处理。

PowerMockito.doNothing().when(userMappers).putUser(Mockito.any(UserDTO.class));  //主要代码

thenReturn 然后返回
mock 创建mock对象
mockStatic mock静态class方法属性,要在类前@PrepareForTest中加入使用SendEmailProxy的类。

PowerMockito.mockStatic(SendEmailProxy.class);
PowerMockito.when(SendEmailProxy.doSomething()).thenReturn(xxx);

any 模拟参数

PowerMockito.any(xxx.class)

注意

  • 如果项目中增加了springboot-actuator包则需要在类前加下述注解配置
@PowerMockIgnore({"javax.management.*"})
  • 如果需要测试HttpComponent包则需要在类前加下述注解配置
@PowerMockIgnore({"javax.management.*", "javax.net.ssl.*"})

Powermock与Jacoco兼容问题

如果在@PrepareForTest中修饰了一个类,这个类jacoco就没法进行覆盖率统计了,powermock是与jacoco的on-the-fly模式冲突的,但是有办法可以避免这样的问题。

原因

JaCoCo和PowerMock都是通过在加载类的时候修改字节码文件来实现统计覆盖率和mock静态类的功能。JaCoCo在加载class的时候会把统计代码插入到class中,而PowerMock当使用了@PrepareForTest注解,在加载相关类的时候会从class文件重新读取字节码信息,导致JaCoCo的修改都没有了,所以就没办法统计到了

on-the-fly模式的解决方案

1.不是用@PrepareForTest修饰要测覆盖率的类,往需要覆盖的类底层去mock,比如 A 依赖 B ,B依赖C,C依赖D。加入我们需要得到A和B的覆盖率,那么我们在@PrepareForTest中就只添加C和D,只去mock C和D即可。
2.上述例子,假如我们A没有依赖,但A中有成员变量需要mock,这个时候我们可以利用反射将成员变量设置为mock对象。然后再进行mock操作,这样就避免了再@PrepareForTest中加入A.class导致覆盖率为零。

缺点:
1.这样写对开发人员的要求比较高,要熟悉各种类库(JDK、各种中间件),往代码深处去mock代理取代在需要单元测试覆盖率的类中进行代理测试。
2.有些底层类不支持mock,比如Charset 的静态方法中要求encode参数必须不能为空,powermock就没法代理到Charset.encode的调用。

offline解决方案

使用jacoco的offline模式:

<!--- 定义 jacoco 版本 -->
<properties>
  <jacoco.version>0.8.5</jacoco.version>
</properties>
<!--- 定义 jacoco 执行 offline 模式 goals -->
<build>
  <plugins>
    <!-- 注意不是在pluginManagement, pluginManagement中只是声明 -->
    <plugin>
      <groupId>org.jacoco</groupId>
      <artifactId>jacoco-maven-plugin</artifactId>
      <version>${jacoco.version}</version>
      <executions>
        <execution>
          <id>default-instrument</id>
          <goals>
            <goal>instrument</goal>
          </goals>
        </execution>
        <execution>
          <id>default-restore-instrumented-classes</id>
          <goals>
            <goal>restore-instrumented-classes</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <!--离线模式必需指定, 否则到模块根目录而不是target目录了-->
        <configuration>
            <systemPropertyVariables>
                <jacoco-agent.destfile>target/jacoco.exec</jacoco-agent.destfile>
            </systemPropertyVariables>
        </configuration>
    </plugin>
  </plugins>
</build>
<!--- 定义 jacoco 依赖  注意不是在dependencyManagement, dependencyManagement中只是声明-->
<dependencies>
  <dependency>
    <groupId>org.jacoco</groupId>
    <artifactId>org.jacoco.agent</artifactId>
    <version>${jacoco.version}</version>
    <classifier>runtime</classifier>
  </dependency>
</dependencies>

Offline模式单元测试不能跨模块, 不能源码在A模块单测写在B模块
比方说:项目分成了四个模块,service,utils,dao,controller,你在写service单测的时候哪怕调用了utils的代码,但是在实际统计覆盖率的时候是没办法统计到的,需要单独针对每一个模块写单测文章来源地址https://www.toymoban.com/news/detail-628395.html

到了这里,关于Springboot 如何使用Powermock做单元测试的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • java & jacoco & powerMock 单元测试覆盖率为0怎么解决

    我们项目中使用powerMock作为单元测试的mock工具,统计项目测试覆盖率使用jacoco编译的结果上传到sonar,但是jacoco 和 powerMock在运行时runtime加载代码的时候自定义了类加载器,所以就会有冲突,导致测试覆盖率为0。 使用命令 mvn clean verify sonar:sonar上传jacoco编译结果(这里sonar命令

    2023年04月08日
    浏览(32)
  • 基于Junit4+Mockito+PowerMock实现Controller+Service的单元测试

    一 导入的依赖 二 依赖版本 三 controller测试示例代码       controller         controllerTest         测试结果:覆盖率100%         带异常的Controller         带异常提示的ControllerTest         测试结果,覆盖率100%   三 service测试示例代码         service         serviceTest    

    2024年02月14日
    浏览(36)
  • PowerMock使用-依赖准备

    PowerMock 是一个单元测试框架,可以模拟静态方法,私有方法和 final 方法等来简化单元测试的编写。本篇文章将对使用 PowerMock 需要的依赖进行介绍。 一. 注解添加与使用场景 在使用 PowerMock 时需要针对不同场景添加对应注解,主要是 @RunWith 和 @PrepareForTest 注解。注解添加和场

    2024年02月08日
    浏览(31)
  • PowerMock的简单使用

    官方文档:https://github.com/powermock/powermock/wiki 在做ut时,发现要测试的方法会引用很多外部依赖的对象,就可能需要用Mock工具来模拟这些外部依赖的对象,来完成ut。 在EasyMock、Mockito等的基础上,增加了对static、final、private等方法的mock。 1、引入依赖 版本可根据具体情况而定

    2024年02月14日
    浏览(28)
  • PowerMock使用-Spy公共方法

    本篇文章将说明如何使用 PowerMock 对公共方法进行 Spy 。关于使用 PowerMock 需要引入哪些依赖,请参考PowerMock使用-依赖准备。 先给出一个示例,再对 Spy 进行解释。被测试类如下所示。 测试类如下所示。 Spy 公共方法时需要使用 PowerMockito.spy(方法所在类的实例) 获取 Spy 出来的

    2024年02月05日
    浏览(38)
  • PowerMock使用-Mock私有方法

    本篇文章将说明如何使用 PowerMock 对私有方法进行 Mock 。关于使用 PowerMock 需要引入哪些依赖,请参考PowerMock使用-依赖准备。 被测试类如下所示。 被测试类中有一个公共方法 isTrue() ,在 isTrue() 方法中会调用 MockPrivateMethod 的私有方法 returnTrue() 。测试类如下所示。 Mock 私有方

    2024年02月11日
    浏览(26)
  • PowerMock使用-Mock静态私有方法

    本篇文章将说明如何使用 PowerMock 对静态私有方法进行 Mock 。关于使用 PowerMock 需要引入哪些依赖,请参考PowerMock使用-依赖准备。 被测试类如下所示。 被测试类中有一个静态公共方法 isTrue() ,在 isTrue() 方法中会调用 MockStaticPrivateMethod 的静态私有方法 returnTrue() 。测试程序如

    2024年02月14日
    浏览(23)
  • 如何做SpringBoot单元测试?

    单元测试(unit testing),是指对项目中的最⼩可测试单元进⾏检查和验证的过程就叫单元测试,对于Java来说或者是在SpringBoot项目中,最小的可测试单元就是一个方法。做单元测试就是为了证明某段代码的执⾏结果是否符合我们的预期。 1、可以⾮常简单、直观、快速的测试某

    2024年01月16日
    浏览(24)
  • SpringBoot如何写好单元测试

    Spring中的单元测试非常方便,可以很方便地对Spring Bean进行测试,包括Controller、Service和Repository等Spring Bean进行测试,确保它们的功能正常,并且不会因为应用的其他变化而出现问题。 1. 导入所需的依赖 :在测试类中,需要导入Spring Test相关的依赖,例如spring-test和JUnit。 2.

    2024年03月23日
    浏览(26)
  • SpringBoot 中如何利用 Junit 实现单元测试?

    2024软件测试面试刷题,这个小程序(永久刷题),靠它快速找到工作了!(刷题APP的天花板)_软件测试刷题小程序-CSDN博客 文章浏览阅读2.5k次,点赞85次,收藏11次。你知不知道有这么一个软件测试面试的刷题小程序。里面包含了面试常问的软件测试基础题,web自动化测试、

    2024年03月11日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包