文章目录
- 1、前言
- 2、Maven依赖
- 2.1 JDK21+SpringBoot版本基于3.1.0
- 2.2 JDK17+SpringBoot版本基于2.2.5.RELEASE
- 3、业务代码
- 4、单元测试
1、前言
之前写过一篇使用testMe自动生成单元测试用例,使用的是junit4来编写的单元测试用例,目前很多新项目都已经使用JDK11+以及SpringBoot3+。本次基于junit5+Mockito来编写单元测试。
2、Maven依赖
2.1 JDK21+SpringBoot版本基于3.1.0
SpringBoot依赖
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.0</version><relativePath/> <!-- lookup parent from repository --></parent>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.objenesis</groupId><artifactId>objenesis</artifactId></exclusion></exclusions></dependency>
mockito依赖
<!--junit5单元测试--><dependency><groupId>org.mockito</groupId><artifactId>mockito-junit-jupiter</artifactId><version>5.3.1</version></dependency><dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>5.3.1</version></dependency>
lombok依赖
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>edge-SNAPSHOT</version></dependency>
2.2 JDK17+SpringBoot版本基于2.2.5.RELEASE
SpringBoot依赖
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.5.RELEASE</version></parent>
Junit依赖
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.8.2</version></dependency>
mockito依赖
<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>5.2.0</version><exclusions><exclusion><groupId>net.bytebuddy</groupId><artifactId>byte-buddy</artifactId></exclusion><exclusion><groupId>net.bytebuddy</groupId><artifactId>byte-buddy-agent</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.mockito</groupId><artifactId>mockito-junit-jupiter</artifactId><version>5.2.0</version></dependency><dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy</artifactId><version>1.14.1</version></dependency><dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy-agent</artifactId><version>1.14.1</version></dependency>
lombok依赖
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version></dependency>
3、业务代码
package com.summer.toolkit.mock;import com.alibaba.fastjson.JSON;
import com.summer.toolkit.dto.UserDto;
import com.summer.toolkit.exception.BizException;
import com.summer.toolkit.executor.DefaultThreadFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.*;@Slf4j
@Service
public class UserServiceImpl implements UserService {@Resourceprivate UserManager userManager;@Overridepublic Long createUser(UserDto userDto) {log.info("创建用户入参:{}", JSON.toJSONString(userDto));String name = userDto.getUsername();if (StringUtils.isBlank(name)) {log.error("用户名称不能为空");throw new BizException("用户名称不能为空");}Long id = userManager.createUser(userDto);log.info("创建用户出参:{}", id);return id;}@Overridepublic Boolean updateUser(UserDto userDto) {log.info("更新用户入参:{}", JSON.toJSONString(userDto));Long id = userDto.getId();String name = userDto.getUsername();if (Objects.isNull(id)) {log.error("用户主键不能为空");throw new BizException("用户主键不能为空");}if (StringUtils.isBlank(name)) {log.error("用户名称不能为空");throw new BizException("用户名称不能为空");}UserDto user = userManager.getUser(userDto);if (Objects.isNull(user)) {log.error("用户不存在");throw new BizException("用户不存在");}Boolean result = userManager.updateUser(userDto);log.info("更新用户出参:{}", result);return result;}@Overridepublic UserDto getUser(UserDto userDto) {log.info("获取用户入参:{}", JSON.toJSONString(userDto));Long id = userDto.getId();if (Objects.isNull(id)) {log.error("用户主键不能为空");throw new BizException("用户主键不能为空");}UserDto user = userManager.getUser(userDto);log.info("获取用户出参:{}", user);return user;}@Overridepublic Boolean batchCreateUser(List<UserDto> list) {log.info("批量创建用户入参:{}", JSON.toJSONString(list));if (CollectionUtils.isEmpty(list)) {log.error("入参集合不能为空");throw new BizException("入参集合不能为空");}int size = 10;long keepAliveTime = 60;long start = System.currentTimeMillis();BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10000);ThreadFactory threadFactory = new DefaultThreadFactory("executor");ExecutorService executorService= new ThreadPoolExecutor(size, size, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);List<CompletableFuture<Boolean>> futureList = new ArrayList<>();for (UserDto userDto : list) {CompletableFuture<Boolean> future = CompletableFuture.supplyAsync(() -> {log.info("当前线程名称:{}", Thread.currentThread());try {Long id = userManager.createUser(userDto);TimeUnit.SECONDS.sleep(3L);log.info("线程:{} id={} done", Thread.currentThread(), id);return Boolean.TRUE;} catch (InterruptedException e) {log.error("创建用户异常:{}", e.getMessage(), e);return Boolean.FALSE;}}, executorService);futureList.add(future);}Boolean result = Boolean.TRUE;try {CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).get(10, TimeUnit.SECONDS);for (CompletableFuture<Boolean> future : futureList) {Boolean back = future.get();if (Boolean.FALSE.equals(back)) {result = Boolean.FALSE;}}} catch (Exception e) {log.error("创建用户异常:{}", e.getMessage(), e);result = Boolean.FALSE;}long end = System.currentTimeMillis();log.info("批量创建用户耗时:{}", (end - start));log.info("批量创建用户出参:{}", result);return result;}}
4、单元测试
package com.summer.toolkit.service;import com.summer.toolkit.dto.UserDto;
import com.summer.toolkit.exception.BizException;
import com.summer.toolkit.mock.UserManager;
import com.summer.toolkit.mock.UserServiceImpl;
import com.summer.toolkit.util.FileUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.Logger;import java.util.ArrayList;
import java.util.Date;
import java.util.List;import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;@ExtendWith(MockitoExtension.class)
@EnabledIfEnvironmentVariable(named = "DEBUG", matches = "true")
public class UserServiceTest {@Mockprivate Logger log;@Mockprivate UserManager userManager;@InjectMocksprivate UserServiceImpl userService;@Testpublic void testCreateUser() {// 模拟依赖方法Mockito.when(userManager.createUser(any())).thenReturn(Long.valueOf(1));// 调用被测方法UserDto userDto = this.buildUserDto();Long result = userService.createUser(userDto);// 验证方法结果Long expect = 1L;Assertions.assertEquals(expect, result);// 验证方法是否被调用Mockito.verify(userManager).createUser(userDto);// 验证依赖对象只有这一个Mockito.verifyNoMoreInteractions(userManager);}@Testpublic void testUpdateUser() {// 模拟依赖方法Mockito.when(userManager.updateUser(any())).thenReturn(Boolean.TRUE);Mockito.when(userManager.getUser(any())).thenReturn(new UserDto());// 调用被测方法UserDto userDto = this.buildUserDto();userDto.setId(1L);Boolean result = userService.updateUser(userDto);// 验证方法结果Assertions.assertEquals(Boolean.TRUE, result);// 验证方法是否被调用Mockito.verify(userManager).getUser(any());Mockito.verify(userManager).updateUser(any());}@Testpublic void testGetUser() {// 模拟依赖方法Mockito.when(userManager.getUser(any())).thenReturn(new UserDto());// 调用被测方法UserDto userDto = this.buildUserDto();userDto.setId(1L);UserDto result = userService.getUser(userDto);// 验证方法结果Assertions.assertNotNull(result);// 验证方法是否被调用Mockito.verify(userManager).getUser(userDto);}@Testpublic void testBatchCreateUser() {// 模拟依赖方法,指定单个异常类型Mockito.when(userManager.createUser(any())).thenThrow(BizException.class);// 调用被测方法List<UserDto> param = new ArrayList<>();UserDto userDto = this.buildUserDto();param.add(userDto);Boolean result = userService.batchCreateUser(param);// 验证方法结果Assertions.assertEquals(Boolean.FALSE, result);// 验证方法是否被调用,默认一次Mockito.verify(userManager).createUser(userDto);// 验证方法是否被调用了1次Mockito.verify(userManager, Mockito.times(1)).createUser(any());}@Testpublic void testBatchCreateUserTimes() {// 模拟依赖方法,指定单个异常类型Mockito.when(userManager.createUser(any())).thenReturn(1L);// 调用被测方法List<UserDto> param = new ArrayList<>();UserDto userDto = this.buildUserDto();param.add(userDto);param.add(userDto);param.add(userDto);Boolean result = userService.batchCreateUser(param);// 验证方法结果Assertions.assertEquals(Boolean.TRUE, result);// 验证方法是否被调用了3次Mockito.verify(userManager, Mockito.times(3)).createUser(any());}@Testpublic void testFileUtils() {// 构建对象List<String> list = new ArrayList<>();list.add("1");list.add("2");// 模拟对应的类// JDK11及以上版本中,try块中的变量可以在外部声明MockedStatic<FileUtils> mocked = Mockito.mockStatic(FileUtils.class);try (mocked) {// 模拟依赖静态方法mocked.when(() -> FileUtils.readFileAllLines(anyString())).thenReturn(list);// 调用被测方法List<String> lines = FileUtils.readFileAllLines(anyString());// 验证方法结果Assertions.assertEquals(list.size(), lines.size());} catch (Exception e) {log.error("模拟静态方法异常:{}", e.getMessage(), e);}}/*** 构建用户数据传输对象** @return UserDto 返回构建好的用户数据传输对象*/private UserDto buildUserDto() {UserDto userDto = new UserDto();userDto.setUsername("小明");userDto.setBirthday(new Date());userDto.setAddress("北京市大兴区亦庄经济开发区");userDto.setComment("加麻加辣");userDto.setGender(1);return userDto;}}