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