业务经常涉及到Excel文件的导出,后台给前端提供导出服务接口,不可能自己都没测试过,就提供给前端调用。最好的方案就是程序员编写单元测试,可以说,程序员有责任编写功能代码,同时也就有责任为自己的代码编写单元测试。
##单元测试
java 常用的单元测试框架是Junit,利用测试框架可以使我们在编写测试用例的过程更加便捷优雅。通过测试用例,我们只需要关注:对于特定的输入,被测对象的返回是否征程,下面是涉及到的相关类。
controller类提供导出接口
@GET
@Path("/{version}/exportRecordDetail")
@ApiOperation(value = "导出电子支付结算明细")
public String exportRecordDetail (@Context HttpServletResponse response, @BeanParam SingleWithdrawReq request){
//...
response.setContentType("application/x-download");
// 获取模板文件
String templateFile = "/export/AccountRecordDetail.xlsx";
try (InputStream is = this.getClass().getResourceAsStream(templateFile);
OPCPackage pkg = OPCPackage.open(is);
XSSFWorkbook workbook = new XSSFWorkbook(pkg)) {
outputFile = URLEncoder.encode(outputFile, "UTF-8");
//...
workbook.write(response.getOutputStream());
} catch (IOException | InvalidFormatException e) {
//...
}
return null;
}
核心代码: workbook.write(response.getOutputStream()); 这一句是指将文件写到输出流里,用到了HttpservletResponse 接口,因此在测试用例里,我们并不能直接实例化HttpservletResponse,因而也就不能作为参数传递到controller层。
那怎么办呢?首先想到了引入mock方法。Mock的引入,可以帮助我们构建比较难构造的Object,这些Object 通常有很多以来,在单元测试中构造出这些对象通常花费比较大的成本。
Mock 单元测试
@Test
public void exportService() {
SingleWithdrawReq request = new SingleWithdrawReq();
request.setParkId(12345);
ParkingBalanceAccountResources resource = new ParkingBalanceAccountResources();
// Mock HttpServletResponse 实例
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
// 定义一个输出的本地文件
File file = new File("output.xls");
try (ServletOutputStream sos = new MockServletOutputStreamUtil (file)) {
//用MockServletOutputStreamUtil 代替ServletOutputStream
Mockito.when(response.getOutputStream()).thenReturn(sos);
resource.exportRecordDetail(response, request);
}
}
单元测试思路: 将输出流写到一个临时文件里,但是在case里我们如何传入一个response并且得到输出流呢?
##工具类:构造ServletOutputStream
public class MockServletOutputStreamUtil extends ServletOutputStream {
private FileOutputStream fos; //定义文件输出流
public MockServletOutputStreamUtil(File file) {
try {
this.fos = new FileOutputStream(file); // 将输出流指向文件
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setWriteListener(WriteListener writeListener) {
throw new RuntimeException("do not support this method");
}
@Override
public void write(int b) throws IOException {
fos.write(b);
}
@Override
public void close() {
try {
fos.close();
} catch (IOException e) {
throw new RuntimeE
xception(e);
}
}