单元测试那些坑
1. 前言
为了提高白盒测试覆盖率,项目中需要添加单元测试代码,写单元测试中很多都是用的Mock+Junit,但是我这个项目中使用的是Mock+testng,不过这两种方式我都会介绍。
2. Mock+TestNG单元测试
2.1 前提准备
这里提供一份依赖jar包的pom文件:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.0.0</version>
<scope>test</scope>
</dependency>
<!--静态类方法模拟-->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.7.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-testng</artifactId>
<version>1.7.4</version>
<scope>test</scope>
</dependency>
2.2 接口方法测试
在接口中我们肯定会有一些自动注入(@Autowired
)的Dao层对象或者其他对象,在Mock
中有两种方式表示这些自动注入的对象
2.2.1 使用注解自动初始化
自动化注解需要使用@InjectMocks
和@Mock
搭配使用,这样就可以在测试类运行的时候Mock这些自动注入对象,之后在@BeforeTest
中使用MockitoAnnotations.initMocks(this);
就可以了。
实例代码:
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;
import com.xing.springDataJpa.first.FirstEntity;
import com.xing.springDataJpa.first.FirstRepository;
import com.xing.springDataJpa.service.impl.IndexServiceImpl;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.http.ResponseEntity;
import org.testng.Assert;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
/**
* 功能描述
*
* @since 2020-09-28
*/
public class IndexServiceImplTest {
@Mock
private FirstRepository firstRepository;
// @InjectMocks: 创建一个实例,简单的说是这个Mock可以调用真实代码的方法,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中。
@InjectMocks
private IndexServiceImpl service;
@BeforeTest
public void beforeTest() {
System.out.println("========= beforeTest() ==========");
// 其中this就是单元测试所在的类,在initMocks函数中Mockito会根据类中不同的注解(如@Mock, @Spy等)创建不同的mock对象,即初始化工作
MockitoAnnotations.initMocks(this);
}
@Test
public void getPerson() {
String userId = "3423423113";
String userName = "xiaoming";
FirstEntity firstEntity = new FirstEntity();
firstEntity.setUserId(userId);
firstEntity.setUserName(userName);
when(firstRepository.findFirstByUserIdAndAndUserName(anyString(), anyString()))
.thenReturn(firstEntity);
ResponseEntity<Object> person = service.getPerson(userId, userName);
Assert.assertNotNull(person);
}
}
2.2.2 使用反射机制初始化注入对象
这种是很简便的使用,如果想多写几行代码可以使用反射机制,set这些对象。
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;
import com.xing.springDataJpa.first.FirstEntity;
import com.xing.springDataJpa.first.FirstRepository;
import com.xing.springDataJpa.service.impl.IndexServiceImpl;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.http.ResponseEntity;
import org.testng.Assert;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import java.lang.reflect.Field;
/**
* 功能描述
*
* @since 2020-09-28
*/
public class IndexServiceImplTest {
IndexServiceImpl service = new IndexServiceImpl();
@Mock
private FirstRepository firstRepository;
@BeforeTest
public void beforeTest() throws NoSuchFieldException, IllegalAccessException {
System.out.println("========= beforeTest() ==========");
// 其中this就是单元测试所在的类,在initMocks函数中Mockito会根据类中不同的注解(如@Mock, @Spy等)创建不同的mock对象,即初始化工作
MockitoAnnotations.initMocks(this);
Class serviceClass = service.getClass();
// 反射获取属性
Field firstRepositoryField = serviceClass.getDeclaredField("firstRepository");
firstRepositoryField.setAccessible(true);
firstRepositoryField.set(service, firstRepository);
}
@Test
public void getPerson() {
String userId = "3423423113";
String userName = "xiaoming";
FirstEntity firstEntity = new FirstEntity();
firstEntity.setUserId(userId);
firstEntity.setUserName(userName);
when(firstRepository.findFirstByUserIdAndAndUserName(anyString(), anyString()))
.thenReturn(firstEntity);
ResponseEntity<Object> person = service.getPerson(userId, userName);
Assert.assertNotNull(person);
}
}
2.3 静态类方法测试
在复杂的接口业务中,经常会用到一些工具类,静态方法等等,这类单元测试需要通过PowerMockito
来实现静态方法的模拟。
静态方法:
public class StaticUtils {
public static String printList(List<String> list) {
System.out.println("============ StaticUtils.printHello()============");
list.forEach(System.out::println);
return "printList";
}
}
接口类添加静态方法:
@Override
public ResponseEntity<Object> getPerson(String userId, String userName) {
String s = StaticUtils.printList(Arrays.asList("a", "b", "c"));
FirstEntity firstEntity = firstRepository.findFirstByUserIdAndAndUserName(userId,userName);
return ResponseEntity.ok().body(firstEntity);
}
PowerMockito.mockStatic(StaticUtils.class);
要放在 MockitoAnnotations.initMocks(this);
前面才可以,示例代码:
import static org.mockito.Matchers.anyListOf;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;
import com.xing.springDataJpa.first.FirstEntity;
import com.xing.springDataJpa.first.FirstRepository;
import com.xing.springDataJpa.service.impl.IndexServiceImpl;
import com.xing.springDataJpa.utils.StaticUtils;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockObjectFactory;
import org.springframework.http.ResponseEntity;
import org.testng.Assert;
import org.testng.IObjectFactory;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.ObjectFactory;
import org.testng.annotations.Test;
/**
* 功能描述
*
* @since 2020-09-28
*/
@PrepareForTest(StaticUtils.class)
public class IndexServiceImplTest {
@Mock
private FirstRepository firstRepository;
// @InjectMocks: 创建一个实例,简单的说是这个Mock可以调用真实代码的方法,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中。
@InjectMocks
private IndexServiceImpl service;
@ObjectFactory
public IObjectFactory getObjectFactory() {
return new PowerMockObjectFactory();
}
@BeforeTest
public void beforeTest() throws NoSuchFieldException, IllegalAccessException {
System.out.println("========= beforeTest() ==========");
// 其中this就是单元测试所在的类,在initMocks函数中Mockito会根据类中不同的注解(如@Mock, @Spy等)创建不同的mock对象,即初始化工作
PowerMockito.mockStatic(StaticUtils.class);
MockitoAnnotations.initMocks(this);
}
@Test
public void getPerson() {
String userId = "3423423113";
String userName = "xiaoming";
FirstEntity firstEntity = new FirstEntity();
firstEntity.setUserId(userId);
firstEntity.setUserName(userName);
when(firstRepository.findFirstByUserIdAndAndUserName(anyString(), anyString()))
.thenReturn(firstEntity);
// 模拟静态方法
String rel = "rel";
PowerMockito.when(StaticUtils.printList(anyListOf(String.class)))
.thenReturn(rel);
ResponseEntity<Object> person = service.getPerson(userId, userName);
Assert.assertNotNull(person);
}
}
3. Mock+Junit单元测试
完整的代码:
import static org.mockito.Matchers.anyListOf;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;
import com.xing.springDataJpa.first.FirstEntity;
import com.xing.springDataJpa.first.FirstRepository;
import com.xing.springDataJpa.service.impl.IndexServiceImpl;
import com.xing.springDataJpa.utils.StaticUtils;
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.mockito.MockitoAnnotations;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.http.ResponseEntity;
/**
* 功能描述
*
* @since 2020-09-28
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest(StaticUtils.class)
public class IndexServiceImplTest {
@Mock
private FirstRepository firstRepository;
// @InjectMocks: 创建一个实例,简单的说是这个Mock可以调用真实代码的方法,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中。
@InjectMocks
private IndexServiceImpl service;
@Before
public void beforeTest() throws NoSuchFieldException, IllegalAccessException {
System.out.println("========= beforeTest() ==========");
// 其中this就是单元测试所在的类,在initMocks函数中Mockito会根据类中不同的注解(如@Mock, @Spy等)创建不同的mock对象,即初始化工作
PowerMockito.mockStatic(StaticUtils.class);
MockitoAnnotations.initMocks(this);
}
@Test
public void getPerson() {
String userId = "3423423113";
String userName = "xiaoming";
FirstEntity firstEntity = new FirstEntity();
firstEntity.setUserId(userId);
firstEntity.setUserName(userName);
when(firstRepository.findFirstByUserIdAndAndUserName(anyString(), anyString()))
.thenReturn(firstEntity);
// 模拟静态方法
String rel = "rel";
PowerMockito.when(StaticUtils.printList(anyListOf(String.class)))
.thenReturn(rel);
ResponseEntity<Object> person = service.getPerson(userId, userName);
Assert.assertNotNull(person);
}
}
相比较TestNG 只有部分代码不一样其他都差不多。
4. 注解说明
这里是TestNG的一些注解说明:
@BeforeClass---@AfterClass
类实例化前, 被执行, 主要用于设置环境变量等, 与SpringTestContext结合用的时候要注意, 这种情况下@autowire的bean还未实例化
@BeforeTest----@AfterTest
整个测试类开始前, 被执行, 主要用户塞值, 或者进行mock(Object)的初始化, 此方法只会运行一次
@BeforeMethod-----@AfterMethod
每个测试方法执行前, 进行调用, 和@BeforeTest的主要区别在于, 你如果需要每次清空你测试的一些上下文, 那么需要配合@AfterMethod一起使用