背景
基于 springboot 微服务架构给单元测试带来的问题:
- springboot 单元测试启动家长过程非常缓慢,后期服务启动达到分钟级,非常影响效率
- 服务之间相互依赖非常严重,单元测试的运行非常依赖其它服务稳定性
- 第三方服务和中间件,测试过程产生大量垃圾数据,污染环境,非常笨重,甚至产生资损。
解决办法
采用 EasyMock, PowerMock,Mockito 等mock 框架, 屏蔽外部依赖,还原单元测试本身。
Mockito使用
由于spring-boot-starter-test 默认集成了 Mockito的依赖,本文优先介绍 Mockito框架的使用。
依赖
一般不需要手动指定
<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>3.3.3</version></dependency>
导入
导入常用静态方法
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
模拟对象
Mock 对象的创建
语法: mock(class or interface)
用例:
OrderService orderService = mock(OrderService.class);
设置预期返回值
语法: when(mock.someMethod()).thenReturn(value)
用例:
when(productService.getById(1L)).thenReturn(new Product(1L,"mate50", new BigDecimal("9000")));
验证被测试类方法
语法: verify(mock).someMethod(…),verify(mock,times(integer)).someMethod(…)
用例:
verify(orderMapper).insert(any(Order.class));
verify(stockService, times(2)).deduct(anyLong(), eq(1));
配合注解使用
语法: @Mock, mock 一个对象
语法: @InjectMocks , 依赖注入 mock对象
用例:
/*** orderMapper, stockService等为 OrderServiceImpl的依赖对象.*/@InjectMocksprivate OrderService orderService = new OrderServiceImpl(); @Mockprivate OrderMapper orderMapper;@Mockprivate StockService stockService;@Mockprivate AmountService amountService;@Mockprivate ProductService productService;
完整案列
@Testpublic void testSubmitOrder() {Long userId = 1L;List<Product> productList = Lists.newArrayList(new Product(1L, 1),new Product(2L, 1));// 模拟productService.getById方法,返回一个Product对象when(productService.getById(1L)).thenReturn(new Product(1L,"mate50", new BigDecimal("9000")));when(productService.getById(2L)).thenReturn(new Product(2L,"充电宝", new BigDecimal("120")));boolean result = orderService.submitOrder(userId, productList);// submitOrder 验证返回结果为trueassertTrue(result);// 验证stockService.deduct方法被调用了2次verify(stockService, times(2)).deduct(anyLong(), eq(1));// 验证orderMapper.insert方法被调用了1次verify(orderMapper, times(1)).insert(any(Order.class));}
使用codeGPT 生成单元测试
prompt: Write JUnit Tests for the following code by using java, use mockito, use Chinese comment:
完整代码
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.*;@RunWith(MockitoJUnitRunner.class)
public class OrderServiceTest {@Mockprivate ProductService productService;@Mockprivate StockService stockService;@Mockprivate OrderMapper orderMapper;@InjectMocksprivate OrderService orderService;@Testpublic void testSubmitOrder() {// givenList<Product> products = Arrays.asList(new Product(1L, "product1", BigDecimal.valueOf(10), 2),new Product(2L, "product2", BigDecimal.valueOf(20), 1));when(productService.getById(1L)).thenReturn(new Product(1L, "product1", BigDecimal.valueOf(10), 10));when(productService.getById(2L)).thenReturn(new Product(2L, "product2", BigDecimal.valueOf(20), 10));// whenboolean result = orderService.submitOrder(123L, products);// thenassertEquals(true, result);verify(productService, times(2)).getById(anyLong());verify(stockService, times(3)).deduct(anyLong(), anyInt());verify(orderMapper).insert(any(Order.class));}
}
结论:通过观察,gpt 生成的单元测试跟手动写的单元测试非常相近,几乎直接可以使用。
一点心得
不要去纠结GPT 哪方面做的不好,要多思考,利用GPT能为我们做什么。