如何写单元测试

作为一位程序猿,想必大家都写过单元测试,想想自己和单元测试之间发生了多少的爱恨情仇。 对于一个常规的web应用,大家写完代码都是怎么测的呢?是不是都是启动工程,通过页面或者postman点一下,不报错正常返回就提测了? 测试同学开始发火了,写的什么鬼玩意,这么多bug,从此以后你的身上多了大bug、不靠谱的标签,人与人之间的信任就没了,最重要的代码质量太差 会阻碍升职加薪。 不说废话了,接下来我就讲讲平时怎么写单元测试。

案例介绍

生成导出任务

流程图

生成导出任务.png

核心逻辑

public Boolean exportExcel(ExportExcelCreateModel  model) {
        
        //参数验证
        CommissionExportTaskGwHelper.checkExportExcel(model);
        //是否有正在执行的任务
        String cacheKey = KeyFormatUtil.format(cacheKeyConfig.EXPORT_TASK_RUNING, model.getBrandId(), model.getCreatorId());
        if (cacheClient.exists(cacheKey)) {
            throw new AppRuntimeException(CommonErrorCode.EXPORT_TASK_RUNING);
        }
        //数据模型转换
        ExportTaskModel exportTaskModel = this.createExportTaskModel(model);
        //写入数据库
        exportTaskRepository.insert(exportTaskModel);
        //设置缓存
        cacheClient.set(cacheKey, true, 60 * 10);
        //发送MQ
        this.sedExportMsg(model,exportTaskModel.getExportId(),exportTaskModel.getPlanId());
        return Boolean.TRUE;
    }
复制代码

编写测试用例

采用Mockito和PowerMock单元测试模拟框架,编写的单元测试用例

@RunWith(PowerMockRunner.class)
public class ExportTaskServiceTest {
    @Mock
    private ExportTaskRepository exportTaskRepository;
    @Mock
    private SequenceManager sequenceManager;
    @Mock
    private CacheClient cacheClient;
    @Mock
    private MessageService messageService;
    @Mock
    private CacheKeyConfig cacheKeyConfig;
    @InjectMocks
    private ExportTaskServiceImpl exportTaskService;


    /**
     * 在测试之前
     */
    @Before
    public void beforeTest() {
        // 注入依赖对象
         Whitebox.setInternalState(cacheKeyConfig, "EXPORT_TASK_RUNING", "export_task_runing_key:12_21");
    }

    /**
     * 测试导出
     */
    @Test
    public void exportExcel() {
        //模拟依赖方法:cacheClient.get
        Mockito.doReturn(null).when(cacheClient).get(Mockito.anyString());
        //模拟依赖方法:sequenceManager.nextValue
        Mockito.doReturn("1").when(sequenceManager).nextValue(SequenceIdEnum.EXPORT_TASK);
        Mockito.doReturn(Boolean.TRUE).when(exportTaskRepository).insert(Mockito.mock(ExportTaskModel.class));

        //模拟环境标
        MockEnvironment mockEnvironment = new MockEnvironment();
        mockEnvironment.setProperty("spring.application.env","DAILY");
        Mockito.spy(new AppEnvProperties(mockEnvironment));
        // 调用被测方法
        String exportJson = ResourceUtil.readUtf8Str("export/export.json");
        ExportExcelCreateModel exportExcelCreateModel = JSON.parseObject(exportJson, ExportExcelCreateModel.class);
        Assert.assertEquals("创建导出任务异常", Boolean.TRUE, exportTaskService.exportExcel(exportExcelCreateModel));
        //验证依赖方法
        Mockito.verify(sequenceManager).nextValue(SequenceIdEnum.EXPORT_TASK);
    }
}
复制代码

好了,案例就整理结束了,比较简单,接下来为大家整理下编写测试用例的流程,以及在各个流程中使用的语法及注意点

测试用例编写流程

定义对象->模拟方法->调用方法->验证方法

定义对象

定义被测对象、模拟依赖对象(类成员)、注入依赖对象(类成员)3大部分

定义被测对象

定义被测试对象,测试服务类实例化,此处不能是接口,必须是实现类

/**
 * 导出service
 */
@InjectMocks
private ExportTaskServiceImpl exportTaskService;
复制代码

模拟依赖对象

定义了成员对象——服务(Service)、数据访问对象(DAO)、参数(Value)等。在Spring框架中,这些类成员对象通过@Autowired、@Value等方式注入,它们可能涉及复杂的环境配置、依赖第三方接口服务,在单元测试中,为了解除对这些类成员对象的依赖,我们需要对这些类成员对象进行模拟

@Mock
private ExportTaskRepository exportTaskRepository;
@Mock
private SequenceManager sequenceManager;
@Mock
private CacheClient cacheClient;
@Mock
private MessageService messageService;
@Mock
private CacheKeyConfig cacheKeyConfig;
复制代码

注入依赖对象

当模拟完这些类成员对象后,我们需要把这些类成员对象注入到被测试类的实例中。以便在调用被测试方法时,可能使用这些类成员对象,而不至于抛出空指针异常。

/**
 * 在测试之前
 */
@Before
public void beforeTest() {
    // 注入依赖对象
     Whitebox.setInternalState(cacheKeyConfig, "EXPORT_TASK_RUNING", "export_task_runing_key:12_21");
}
复制代码

模拟方法

主要包括模拟依赖对象(参数或返回值)、模拟依赖方法

模拟依赖对象(参数或返回值)

通常,在调用一个方法时,需要先指定方法的参数,然后获取到方法的返回值。所以,在模拟方法之前,需要先模拟该方法的参数和返回值。

模拟依赖方法

在调用一个方法时,需要先指定方法的参数,然后获取到方法的返回值。所以,在模拟方法之前,需要先模拟该方法的参数和返回值

//模拟依赖方法:cacheClient.get
Mockito.doReturn(null).when(cacheClient).get(Mockito.anyString());
//模拟依赖方法:sequenceManager.nextValue
Mockito.doReturn("1").when(sequenceManager).nextValue(SequenceIdEnum.EXPORT_TASK);
//模拟依赖方法:exportTaskRepository.insert
Mockito.doReturn(Boolean.TRUE).when(exportTaskRepository).insert(Mockito.mock(ExportTaskModel.class));
//模拟环境标
MockEnvironment mockEnvironment = new MockEnvironment();
mockEnvironment.setProperty("spring.application.env","DAILY");
Mockito.spy(new AppEnvProperties(mockEnvironment));
复制代码

调用方法

主要包括模拟对象(参数)、调用被测方法、验证参数对象(返回值)

模拟对象

在调用被测方法之前,需要模拟被测方法的参数。如果这些参数还有方法调用,还需要模拟这些参数的方法,对于字段比较多复杂参数可以选择本地json文件,直接读取反序列化为对象

// 调用被测方法,ResourceUtil为hutool工具包
String exportJson = ResourceUtil.readUtf8Str("export/export.json");
ExportExcelCreateModel exportExcelCreateModel = JSON.parseObject(exportJson, ExportExcelCreateModel.class);
复制代码

调用被测方法

在准备好参数对象后,就可以调用被测试方法了

exportTaskService.exportExcel(exportExcelCreateModel)
复制代码

验证数据对象(返回值)

如果被测试方法有返回值,需要验证这个返回值是否符合预期;如果被测试方法要抛出异常,需要验证这个异常是否满足要求

Assert.assertEquals("创建导出任务异常", Boolean.TRUE, exportTaskService.exportExcel(exportExcelCreateModel));
复制代码

Guess you like

Origin juejin.im/post/7034781567708823559