《Java单元测试实战》——案例集锦:Java单元测试典型案例集锦(7) https://developer.aliyun.com/article/1232052?groupCode=java
八、 如何测试已变更的方法参数值
在单元测试中,我们通常通过ArgumentCaptor进行方法参数捕获并验证。但是,在有些情况下,我们捕获的可能是已经变更的方法参数,所以无法对这些方法参数值进行验证。
1. 案例代码
这里,以分批读取并保存ODPS数据为例说明。其中,dataList在每次存储后,都进行了一次清除操作。
/** * 读取数据 * * @param <T> 模板类型 * @param recordReader 记录读取器 * @param batchSize 批量大小 * @param dataParser 数据解析器 * @param dataStorage 数据存储器 * @throws IOException IO异常 */ public static <T> void readData(RecordReader recordReader, int batchSize, Function<Record, T> dataParser, Consumer<List<T>> dataStorage) throws IOException { // 依次读取数据 Record record; List<T> dataList = new ArrayList<>(batchSize); while (Objects.nonNull(record = recordReader.read())) { // 解析添加数据 T data = dataParser.apply(record); if (Objects.nonNull(data)) { dataList.add(data); } // 批量存储数据 if (dataList.size() == batchSize) { dataStorage.accept(dataList); dataList.clear(); } } // 存储剩余数据 if (CollectionUtils.isNotEmpty(dataList)) { dataStorage.accept(dataList); dataList.clear(); } }
2. 问题测试
通常情况下,我们利用ArgumentCaptor编写的测试用例如下:
/** * 测试: 读取数据-正常 * * @throws IOException IO异常 */ @Test public void testReadDataWithNormal() throws IOException { // 模拟依赖方法 // 模拟依赖方法: recordReader.read Record record1 = Mockito.mock(Record.class); Record record2 = Mockito.mock(Record.class); TunnelRecordReader recordReader = Mockito.mock(TunnelRecordReader.class); Mockito.doReturn(record1, record2, null).when(recordReader).read(); // 模拟依赖方法: dataParser.apply Function<Record, Object> dataParser = CastUtils.cast(Mockito.mock(Function.class)); Object object1 = new Object(); Object object2 = new Object(); Mockito.doReturn(object1).when(dataParser).apply(record1); Mockito.doReturn(object2).when(dataParser).apply(record2); // 调用测试方法 int batchSize = 2; Consumer<List<Object>> dataStorage = CastUtils.cast(Mockito.mock(Consumer.class)); OdpsHelper.readData(recordReader, batchSize, dataParser, dataStorage); // 验证依赖方法 // 验证依赖方法: recordReader.read Mockito.verify(recordReader, Mockito.times(3)).read(); // 验证依赖方法: dataParser.apply Mockito.verify(dataParser).apply(record1); Mockito.verify(dataParser).apply(record2); // 验证依赖方法: dataStorage.test ArgumentCaptor<List<Object>> dataListCaptor = CastUtils.cast(ArgumentCaptor.forClass(List.class)); Mockito.verify(dataStorage).accept(dataListCaptor.capture()); Assert.assertEquals("数据列表不一致", Arrays.asList(object1, object2), dataListCaptor.getValue()); }
执行该单元测试后,会出现以下错误:
因为,我们捕获的方法参数dataList只是一个对象引用,其数据内容早已被clear方法清除干净了。
3. 正确测试
对于这种情况,我们可以利用Mockito.doAnswer来保存这些临时值,最后再进行统一的数据验证。
/** * 测试: 读取数据-正常 * * @throws IOException IO异常 */ @Test public void testReadDataWithNormal() throws IOException { // 模拟依赖方法 // 模拟依赖方法: recordReader.read Record record1 = Mockito.mock(Record.class); Record record2 = Mockito.mock(Record.class); TunnelRecordReader recordReader = Mockito.mock(TunnelRecordReader.class); Mockito.doReturn(record1, record2, null).when(recordReader).read(); // 模拟依赖方法: dataParser.apply Function<Record, Object> dataParser = CastUtils.cast(Mockito.mock(Function.class)); Object object1 = new Object(); Object object2 = new Object(); Mockito.doReturn(object1).when(dataParser).apply(record1); Mockito.doReturn(object2).when(dataParser).apply(record2); // 模拟依赖方法: dataStorage.test List<Object> dataList = new ArrayList<>(); Consumer<List<Object>> dataStorage = CastUtils.cast(Mockito.mock(Consumer.class)); Mockito.doAnswer(invocation -> dataList.addAll(invocation.getArgument(0))) .when(dataStorage).accept(Mockito.anyList()); // 调用测试方法 int batchSize = 2; OdpsHelper.readData(recordReader, batchSize, dataParser, dataStorage); Assert.assertEquals("数据列表不一致", Arrays.asList(object1, object2), dataList); // 验证依赖方法 // 验证依赖方法: recordReader.read Mockito.verify(recordReader, Mockito.times(3)).read(); // 验证依赖方法: dataParser.apply Mockito.verify(dataParser).apply(record1); Mockito.verify(dataParser).apply(record2); // 验证依赖方法: dataStorage.test Mockito.verify(dataStorage).accept(Mockito.anyList()); }
《Java单元测试实战》——案例集锦:Java单元测试典型案例集锦(9) https://developer.aliyun.com/article/1232050?groupCode=java