首页 > 编程语言 >Java中的单元测试

Java中的单元测试

时间:2023-03-07 21:02:05浏览次数:37  
标签:PowerMockito Java 单元测试 getMsg test sendService newValue mock

1、概念介绍

单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。单元是人为规定的最小的被测功能模块。

本文主要讲Java中的单元测试中的代码编写,一般最小的单元就是一个方法,设计测试场景(一些边界条件),看运行结果是否满足预期,修改了代码也能帮助验证是否影响了原有的逻辑。

2、常用的Java测试框架

Junit:是一个为Java编程语言设计的单元测试框架。

Mockito:允许程序员使用自动化的单元测试创建和测试模拟对象。

PowerMock:PowerMock利用自定义的类加载器和字节码操纵器,来确保静态方法的模拟、静态初始化的删除、函数构造、最终的类和方法以及私有方法。

实际上还有许多优秀的测试框架,这几种笔者比较常用,所以本文记录下使用方法。

3、Maven引入

相关的Maven依赖如下,彼此间有版本对应关系,没对应可能会出现冲突

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.9.0</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>

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

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

4、自动生成单元测试代码

  • Idea引入插件

  • 选择要生成单元测试的类,按Alt+Insert出现如下界面,选择TestMe自动生成文件

  • 选择需要的生成模板,可以根据自己实际引入的依赖选择,此处选择Junit4+Mockito

  • 生成的代码如下,可以生成一些基本的方法和注解,然后根据实际情况修改,可以节省一部分工作量。

5、常用注解和配置

@Mock:创建一个模拟的对象,类似于@Autowired,但不是真实的对象,是Mock对象,这个注解使用在类属性上

@InjectMocks:创建一个实例,其余用@Mock注解创建的mock将被注入到用该实例中,这个注解使用在类属性上

@RunWith:表示一个运行器,@RunWith(PowerMockRunner.class)表示指定用PowerMockRunner运行,这个注解使用在类上

@PowerMockIgnore:这个注解表示将某些类延迟到系统类加载器加载,解决一些类加载异常。(具体类加载异常实际中还未遇见,后续补充),这个注解在类和方法上使用

@PrepareForTest:这个注解告诉PowerMock为测试准备某些类,通常是那些需要字节码操作的类。这包括final类、带有final、private、static或native方法的类,也包括在实例化时应该返回模拟对象的类(需要new一个模拟对象时),这个注解在类和方法上使用

@Test:@Test修饰的public void方法可以作为测试用例运行。Junit会构造一个新的类实例,然后调用所有加了@Test的方法,方法执行过程中的任何异常,都会被判定为测试用例执行失败。

@Before:@Before注解的方法中的代码会在@Test注解的方法中首先被执行。可以做一些公共的前置操作:加入一些申请资源的代码:申请数据库资源,申请io资源,申请网络资源,new一些公共的对象等等。

@After:@After注解的方法中的代码会在@Test注解的方法中首先被执行。可以做一些公共的后置操作,如关闭资源的操作。

注:可以查看注解上的注释,了解其大致用法。

6、常规用法

下面给一个使用上述所有注解的简单例子:

import org.example.dto.CallbackDTO;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import java.io.File;
import java.util.Map;

@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"org.xml.*", "javax.xml.*"})
@PrepareForTest({File.class, MessageCardService.class})
public class MessageCardServiceTest {

    @InjectMocks
    MessageCardService messageCardService;

    @Mock
    SendService sendService;

    @Before
    public void setUp() {
        System.out.println("前置操作");
    }

    @After
    public void after() {
        System.out.println("后置操作");
    }

    @Test
    public void sendMessage() {
        PowerMockito.when(sendService.getMsg()).thenReturn("test");
        Map map = messageCardService.updateMessageCard(new CallbackDTO());
        Assert.assertEquals(map.get("key"), null);
    }
}

运行结果如下:

被单元测试的方法源码如下:

@Service
public class MessageCardService {

    @Autowired
    private SendService sendService;

    public String updateMessageCard(String param) throws FileNotFoundException {
        String msg = sendService.getMsg(param);
        System.out.println("msg:" + msg);
        File file = new File("path");
        Date now = new Date();
        System.out.println("date:" + now);
        System.out.println("length:" + file.length());
        sendService.printMsg(param);
        final String s = SendService.sendMsg(param);
        System.out.println("static result:" + s);
        return msg;
    }

    private String msgHello(String param) {
        return param;
    }
    
}
@Service
public class SendService {

    public String getMsg(String msg) {
        return msg;
    }

    public void printMsg(String param) {
        System.out.println(param);
    }

    public static String sendMsg(String param) {
        return param;
    }
}

6.1、when().thenReturn()

当调用mock对象的方法时,根据入参条件,返回指定的值

/**
 * Mock普通方法的返回值
 */
@Test
public void testWhen() throws FileNotFoundException {
    //mock方法sendService.getMsg()的返回值,当入参为test时,返回值为newValue
    PowerMockito.when(sendService.getMsg("test")).thenReturn("newValue");
    //运行需要测试的实际方法
    String result = messageCardService.updateMessageCard("test");
    //比较预期结果
    Assert.assertEquals(result, "newValue");
}

6.2、whenNew().thenReturn()

当需要new一个对象时,可以根据条件返回一个mock对象,下面的示例,当new File()时,如果入参是path,则返回mock的文件对象。

注意:涉及到mock new一个对象时,例如whenNew()和verifyNew(),需要将@InjectMocks修饰的类放入@PrepareForTest注解中,如下图:

示例代码如下:

/**
 * Mock普通方法的返回值
 */
@Test
public void testWhenNew() throws Exception {
    //mock方法sendService.getMsg()的返回值,当入参为test时,返回值为newValue
    PowerMockito.when(sendService.getMsg("test")).thenReturn("newValue");
    //Mock new的对象
    File mockFile = PowerMockito.mock(File.class);
    PowerMockito.when(mockFile.exists()).thenReturn(true);
    PowerMockito.when(mockFile.isFile()).thenReturn(true);
    PowerMockito.when(mockFile.length()).thenReturn(100L);
    PowerMockito.whenNew(File.class).withArguments("path").thenReturn(mockFile);
    //运行需要测试的实际方法
    String result = messageCardService.updateMessageCard("test");
    //比较预期结果
    Assert.assertEquals(result, "newValue");
}

6.3、Mockito.verify()

  • 验证中间过程的方法入参是否符合预期,例如中间某个方法的入参经过了计算得到,则可以验证此入参是否符合预期
/**
 * 验证中间过程的方法入参是否符合预期
 */
@Test
public void testVerifyParam() throws FileNotFoundException {
    //mock方法sendService.getMsg()的返回值,当入参为test时,返回值为newValue
    PowerMockito.when(sendService.getMsg("test")).thenReturn("newValue");

    //运行需要测试的实际方法
    String result = messageCardService.updateMessageCard("test");

    //验证sendService.getMsg(string s)入参是否是test
    Mockito.verify(sendService).getMsg("test");

    //比较预期结果
    Assert.assertEquals(result, "newValue");
}
  • 验证中间过程的方法被调用的次数是否符合预期
/**
 * 验证中间过程的方法被调用的次数
 */
@Test
public void testVerifyTimes() throws FileNotFoundException {
    //mock方法sendService.getMsg()的返回值,当入参为test时,返回值为newValue
    PowerMockito.when(sendService.getMsg("test")).thenReturn("newValue");

    //运行需要测试的实际方法
    String result = messageCardService.updateMessageCard("test");

    //验证sendService.getMsg(string s)被调用次数是否为一次
    Mockito.verify(sendService, Mockito.times(1)).getMsg("test");

    //比较预期结果
    Assert.assertEquals(result, "newValue");
}

6.4、PowerMockito.verifyNew()

验证中间过程的Mock对象被new了几次,例如:一个for循环里反复new一个对象,该对象在外面被mock,则可以判断对象new的次数是否等于循环次数。

注意:只能验证预期的对象创建,就是whenNew的对象次数,不是真实的对象创建,即如果该对象没有被mock,而是真实的创建对象,则次数不会被统计。verifyNew()时需要将@InjectMocks修饰的类放入@PrepareForTest注解中

/**
 * 验证中间过程的对象被new的次数
 */
@Test
public void testVerifyNew() throws Exception {
    //mock方法sendService.getMsg()的返回值,当入参为test时,返回值为newValue
    PowerMockito.when(sendService.getMsg("test")).thenReturn("newValue");

    //mock一个日期
    Date mockDate = PowerMockito.mock(Date.class);
    PowerMockito.whenNew(Date.class).withNoArguments().thenReturn(mockDate);

    //运行需要测试的实际方法
    String result = messageCardService.updateMessageCard("test");

    //验证是否mock new了一个日期实例(注意这里只能验证预期的对象创建,就是whenNew的对象次数,不是验证真实的对象new了几次)
    PowerMockito.verifyNew(Date.class, Mockito.times(1)).withNoArguments();

    //比较预期结果
    Assert.assertEquals(result, "newValue");
}

6.5、PowerMockito.doNothing().when()

mock无返回值的方法

/**
 * mock无返回值的方法(返回值为void)
 */
@Test
public void testReturnVoid() throws FileNotFoundException {
    //mock方法sendService.getMsg()的返回值,当入参为test时,返回值为newValue
    PowerMockito.when(sendService.getMsg("test")).thenReturn("newValue");

    //mock一个无返回值的方法
    PowerMockito.doNothing().when(sendService).printMsg("test");

    //运行需要测试的实际方法
    String result = messageCardService.updateMessageCard("test");

    //比较预期结果
    Assert.assertEquals(result, "newValue");
}

6.6、PowerMockito.method()和Whitebox.invokeMethod()

mock私有方法,有两种方式,代码如下:

方式一:

/**
 * mock私有方法
 */
@Test
public void testPrivate() throws InvocationTargetException, IllegalAccessException {
    //@InjectMocks注入MessageCardService,指定私有方法
    Method msgHello = PowerMockito.method(MessageCardService.class, "msgHello", String.class);

    //调用私有方法
    Object result = msgHello.invoke(messageCardService, "hello");

    //比较预期结果
    Assert.assertEquals(result, "hello");
}

方式二:

/**
 * mock私有方法2
 */
@Test
public void testPrivate2() throws Exception {
    //Whitebox 调用私有方法
    Object result = Whitebox.invokeMethod(messageCardService, "msgHello", "hello");

    //比较预期结果
    Assert.assertEquals(result, "hello");
}

6.7、PowerMockito.mockStatic()

mock静态方法,需要注意将mock的类放入@PrepareForTest注解,如下图:

代码如下:

/**
 * mock静态方法
 */
@Test
public void testStatic() throws FileNotFoundException {
    //mock方法sendService.getMsg()的返回值,当入参为test时,返回值为newValue
    PowerMockito.when(sendService.getMsg("test")).thenReturn("newValue");

    //mock静态方法
    PowerMockito.mockStatic(SendService.class);
    PowerMockito.when(SendService.sendMsg("test")).thenReturn("newValue");

    //运行需要测试的实际方法
    String result = messageCardService.updateMessageCard("test");

    //比较预期结果
    Assert.assertEquals(result, "newValue");
}

标签:PowerMockito,Java,单元测试,getMsg,test,sendService,newValue,mock
From: https://www.cnblogs.com/zhaodalei/p/17148339.html

相关文章

  • Java基础
    Java基础注释平时我们编写代码,在代码量比较少的时候,我们还可以看懂自己写的,但是当项目结构一旦复杂起来,我们就需要用到注释了。注释并不会被执行,是给我们写代码的人......
  • Java官方笔记1编写运行Java程序
    你可能已经迫不及待想安装Java,写个Java程序跑起来了。但是在这之前,有些概念需要提前了解,因为Java跟C、C++和Python都有点不一样。编译和执行我们在文本文件中编写英文代......
  • 03Java8日期处理
    使用now方法根据当前日期或时间创建实例对象如使用now方法创建LocalDate(年、月、日)和LocalTime(时、分、秒)等实例对象LocalTime.now()——>LocalTimenow=LocalTime.n......
  • java压缩流
    利用压缩流解压文件夹@TestpublicvoidzipInputStreamDemo()throwsException{//利用压缩流解压文件夹,注意java只识别zipFilesrc=newFile("D:/a.......
  • 【选择排序算法详解】Java/Go/Python/JS/C 不同语言实现
    【选择排序算法详解】Java/Go/Python/JS/C不同语言实现 说明选择排序(SelectionSort)是一种简单直观的排序算法。跟冒泡、插入排序一样,它将数列分为已排序和待排序两个......
  • TypeScript 与 JavaScript:你应该知道的区别
    作者:京东零售杨冰译自Radix网站的文章,原文链接:https://radixweb.com/blog/typescript-vs-javascript,原文作者:NiharRaval正在寻找经验丰富的JavaScript开发团队来将您的......
  • 在 Java 中解码 Base64 数据
    我有一个Base64编码的图像。在Java中解码它的最佳方法是什么?希望只使用SunJava6中包含的库。解答http://www.stackoverflow.ink/posts/zai-java-zhong-......
  • java -D的一些学习和使用
    背景java开发的程序有很多进行配置的方式可以通过yaml文件或者是xml文件也可以通过环境变量的方式.1.容器的话可以使用-e或者是env进行注入2.K8S的话可以通过co......
  • 推荐 7 个有用的 JavaScript 库,也许你会用的上
    推荐7个有用的JavaScript库,也许你会用的上原创2023-02-1016:52·前端达人使用这7个库,加速你的项目开发 当我们可以通过使用库轻松实现相同的结果时,为什么还要......
  • JavaSE——接口
    概述我们已经学完了抽象类,抽象类中可以用抽象方法,也可以有普通方法,构造方法,成员变量等。那么什么是接口呢?接口是更加彻底的抽象,JDK7之前,包括JDK7,接口中全部是抽象方法。接......