随着 IT 技术的普及,越来越多的商业软件应用到企业当中帮助管理企业、优化效率、提升生产力。现代的金融软件系统根据应用领域可以分为三大类:经营管理类、金融业务类和统计分析类。金融管理类软件用于批量实时处理银行业务;统计分析类软件通过采集实际生产经营中的数据,按逻辑规则分类展示,为决策提供直观的图表和参考依据;经营管理类软件为企业的正常运转提供流程上和财务上的支持。其中财务管理是每个企业必不可少的核心,此类软件应用包含大量的计算逻辑、输入数据、中间结果和报表展示。以财务管理中的必不可少的三表(资产负债表,现金流量表,利润表)为例,每张报表多达上千不同的数据值,对测试人员来说,如何在有限的时间里利用自动化提高测试效率,实现有效的测试覆盖是一个很有挑战性的任务。
IBM Rational Functional Tester (RFT) 是用于测试 Java、Web、.NET 等各种应用软件的一个自动化测试工具,它具备强大的对象识别机制,应用程序中的 GUI 元素被 RFT 映射成为 Test Object 对象,并由 RFT 提供的对象模型框架进行组织管理。通过 RFT 的对象识别机制(静态识别和动态识别),用户可以访问到应用软件中的对象以及对象的方法和属性,并以此为依据产生测试脚本。
RFT8.1 版本以后提供了简便,灵活的数据池构建,增强了对数据驱动测试的支持。通过 RFT 的数据池可以实现单个脚本使用不同输入数据重复执行测试用例,达到更高效的测试覆盖。使用数据池构建自动化测试框架将测试数据和测试脚本分离开,测试数据以表格的形式存储在数据池中,自动化测试人员可以在不更改测试脚本的情况下通过对数据池中数据的更改变更测试用例,增加测试范围,大大提高地测试自动化的覆盖率,同时也增强了测试脚本的复用性和可维护性。用户不仅可以通过编辑器直接操作 RFT 中的数据池,也可以从 Excel 或者是 Rational Test Manager 中导入数据,再将数据池和相应的测试脚本关联起来即可实现数据驱动测试。
图 1. Rational Functional Tester 数据池图示
Apache Poor Obfuscation Implementation (POI) 是用 Java 编写的免费开源的跨平台的 Java API,它提供了基于 Office Open XML(OOXML)和 Microsoft 的 OLE 2 Compound Document 标准构建的文件的处理方式。通过 Apache POI
可以使用 Java 读取、创建和修改 Open Office/Microsoft Office 文件。 Apache POI 主要包括一下主要组件:
HSSF -提供读 Excel XLS 格式档案的功能
XSSF - 提供读写 Excel OOXML XLSX 格式档案的功能;
HWPF - 提供读写 Word DOC 格式档案的功能。
HSLF - 提供读写 PowerPoint 格式档案的功能。
HDGF - 提供读 Visio 格式档案的功能。
HPBF - 提供读 Publisher 格式档案的功能。
HSMF - 提供读 Outlook 格式档案的功能。
本文主要使用 HSSF 用于金融财务类软件的自动化框架搭建,HSSF 全称是 Horrible Spread sheet Format,通过它不仅可以简单的读写 Excel 中单元格的数字,字符串,公式值还可以定义行列大小,格式化单元格,清空或填充特定类型的数据,详细用法参见下面章节。
回页首
金融财务类软件一般具有大量的计算逻辑和计算报表,不同的输入数据会流向不同的逻辑分支最终表现为界面上的数据展示和报表。软件测试自动化是模拟人的手工操作,在界面上输入数据,选取不同的逻辑分支并验证结果的过程。对于金融财务类软件的验证,在自动化脚本里实现所有计算逻辑耗时耗力,相当于重写一遍软件,此做法并不现实。在没有使用 IT 加速企业管理以前,财务部门往往使用 Excel 来管理企业的财务运营,这些 Excel 模板也通常是财务类软件工具设计实现的需求来源,通过结合并有效利用这些 Excel 文档来构建测试自动化框架,能有效的节省测试资源,实现高效的测试覆盖。
金融财务类软件的数据一部分直接在界面以 UI 控件的形式展现,另一部分以报表形式展现。报表往往可以直接导出为 Excel,对于此类数据的验证,可以通过 Excel 遍历对比的方法输出一个比较报表,高亮出不一样的计算结果并提供相关链接。对于不可导出类的数据,可以通过将数据池中的数据灌入需求 Excel 文档当中,利用需求 Excel 文档中的公式计算出结果,提取 Excel 的结果和界面 UI 控件的展示,对比并验证计算正确性。对于两种不一样的数据展现,本文提供了不一样的处理流程和方法,详情见下:
图 2. 界面数据验证流程图
图 3. 报表数据验证流程图
需求文档分析、测试数据的组合设计以及数据池模板定义对不同的项目不同的需求来说,千差万别,需要测试人员依据行业背景和测试经验进行相关的设计,属于手工分析实现的部分,在本文不做详述。RFT 中数据池的创建,数据池数据的导入、编辑、删除以及和测试脚本的关联可以参见参考资料中 Rational 产品的技术资源和最佳实践。本文着重描述框架的构建以及对于 Excel 计算模板的处理。
回页首
本文主要采用了 Apache POI HSSF 组件实现了对 Excel 计算模板的处理。HSSF 包主要有以下组件和对象:
表 1. Apache POI HSSF 的组件列表
HSSFWorkbook | excel 的文档对象 |
HSSFSheet | excel 的表单 |
HSSFRow | excel 的行 |
HSSFCell | excel 的格子单元 ,有多种属性,标示不同的单元格数据格式 |
FormulaEvaluator | excel 的公式处理工具 |
POIFSFileSystem | POI 文件输入 |
自动化测试人员获取到需求 Excel 模板后,需要向模板中注入相应的输入结果,利用 Excel 模板内含的计算逻辑得到期望的计算结果,然后提取出相应的预期结果和界面做对比。本文利用了 Apache POI HSSF 实现了 Excel 数据的填充和提取。
清单 1.通过操作 Apache POI 向 Excel 中写入数据
public static void writeExcel(String fileName, String sheetName,String title, String key, String value) { FileOutputStream fOut = null; String temp; try { fileName = prjectPath + fileName; POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream( fileName)); HSSFWorkbook wb = new HSSFWorkbook(fs); HSSFSheet sheet = wb.getSheet(sheetName); HSSFRow row = null; HSSFRow row1 = null; HSSFCell cell = null; for (int i = 0; i < 1000; i++) { row = sheet.getRow((short) i); if (row != null) { cell = row.getCell((short) 0); if (cell != null) { temp = cell.getStringCellValue(); if (temp.trim().equals(title)) { short l = row.getLastCellNum(); short r = (short) (i + 1); short c = 0; for (int n = 1; n < l; n++) { cell = row.getCell((short) n); if (cell != null) { temp = cell.getStringCellValue(); if (temp.trim().equals(key.trim())) { c = (short) n; break; } } } if (c != 0) { row1 = sheet.getRow((short) r); if (row1 == null) row1 = sheet.createRow((short) r); cell = row1.getCell((short) c); if (cell == null) { cell = row1.createCell((short) c); } cell.setCellType(HSSFCell.CELL_TYPE_STRING); cell.setCellValue(value); break; } } } } } FileOutputStream fileOut = new FileOutputStream(fileName);//将数据写出到 Excel 文档中 wb.write(fileOut); fileOut.close(); } catch (Exception e) { System.out.println("Excel File Write Error: " + e); } finally { if (fOut != null) { try { fOut.close(); } catch (IOException e1) { } } } }
Excel 中写入输入数据以后,需要重置内含的逻辑公式,启动计算过程才能得到输出结果。Apache 提供了 FormulaEvaluator 来对 Excel 中的公式进行处理。
清单 2.通过操作 Apache POI 重置 Excel 公式得到预期结果
public static void resetSheetFormula(String fileName, String sheetName){ fileName = prjectPath + fileName; File file = new File(fileName); FileInputStream in = null; String[] s = null; String temp = ""; int l = 0; FormulaEvaluator eval=null; try { in = new FileInputStream(file); HSSFWorkbook wb = new HSSFWorkbook(in); HSSFSheet sheet = wb.getSheet(sheetName); HSSFCell cell = null; if(wb instanceof HSSFWorkbook) eval=new HSSFFormulaEvaluator((HSSFWorkbook) wb); for(int i = 0;i<250;i++){ if(sheet.getRow(i)!= null){ for(int j = 0;j<20;j++){ cell = sheet.getRow(i).getCell(j); if(cell!=null&&cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA){ cell.setCellFormula(cell.getCellFormula()); } } } } FileOutputStream fileOut = new FileOutputStream(fileName); wb.write(fileOut); fileOut.close(); } catch (Exception e) { System.out.println("" + file.getAbsolutePath() + e); } finally { if (in != null) { try { in.close(); } catch (IOException e1) { } } } }
回页首
导出报表的数据驱动流程和界面数据验证类似,区别是 Excel 提取结果后,对比的数据是来源于界面还是导出报表。对于预期结果以导出报表展现的,需要用 Excel 分别去实际结果报表和导出报表中提取数据,循环遍历做比较。
对比报表首先要从实际计算结果和预期计算结果的 Excel 中提取数据,存为程序能处理的格式,本文将 Excel 映射为一个多层次的 list,以读取预期计算结果为例说明,读取特定路径 Excel(sBaseLineDir)下特定 sheet(sSheetName)的数据,返回一个嵌套 List。
清单 3.通过操作 Apache POI 提取 Excel 数据
public List<List<String>> readExcelForBaseLine(String sBaseLineDir, String sSheetName) { List<List<String>> list = new ArrayList<List<String>>(); FileInputStream fs = null; HSSFWorkbook wb = null; HSSFSheet sheet = null; HSSFRow row = null; int row_baseline = 0; int col_baseline = 0; try { fs = new FileInputStream(sBaseLineDir); //读入 excel 文件 wb = new HSSFWorkbook(fs); //将 excel 文件映射为 HSSFWorkbook } catch (IOException e) { e.printStackTrace(); } sheet = wb.getSheet(sSheetName); row_baseline = sheet.getLastRowNum();//得到 sheet 的行数 col_baseline = sheet.getRow(2).getPhysicalNumberOfCells();//得到 sheet 的列数 for (int i = 4; i <= row_baseline; i++) { try { row = sheet.getRow(i); List<String> value = new ArrayList<String>(); for (int j = 0; j < col_baseline; j++) { if(transferFormatForNumeric(row.getCell((short) j)) != "") { value.add(transferFormatForNumeric(row.getCell((short) j))); //若单元格不为空,将该单元格数据存入 List 中 } } list.add(value); } catch(Exception e) { e.getStackTrace(); } } return list; }
提取完对应 sheet 数据以后,需要遍历对比 List,并打印出相应的 log,方法见下。
清单 4.通过操作 Apache POI 操作实现报表的遍历对比
点击查看代码清单
关闭 [x]
public String compareResult(String sBaseLineDir, String sResultDir, String sSheetName) { String log = ""; int SI; Report report = new Report(); List<?> baseLineList = report.readExcelForBaseLine(sBaseLineDir, sSheetName); List<?> resultList = report.readExcelForResult(sResultDir, sSheetName); if(baseLineList.size() != resultList.size()) { System.out.println(baseLineList.size()+" VS "+resultList.size()); if(baseLineList.size()>resultList.size()){SI = resultList.size();}else{SI = baseLineList.size();} for (int i = 0; i < SI; i++) { List<?> baseLineValue = (List<?>) baseLineList.get(i); List<?> resultValue = (List<?>) resultList.get(i); for (int j = 0; j < baseLineValue.size(); j++) { if (!resultValue.get(j).toString().equals(baseLineValue.get(j).toString())) { System.out.println("Point(" + (i + 5) + "," + (j + 1) + ")" + / baseLineValue.get(0).toString() + " (baseline:" + baseLineValue.get(j) + " export:" + resultValue.get(j).toString() + ")/r/n"); log = log + "Point(" + (i + 5) + "," + (j + 1) + ")" + baseLineValue.get(0).toString() + " (baseline:" + baseLineValue.get(j) + " export:" + resultValue.get(j).toString() + ")/r/n"; return "*******************" + sSheetName + " Row Number Inconsistent*******************/r/n" + log + "/r/n"; } } } if (log.equals("")) { System.out.println("*******************" + sSheetName + " Pass*******************/r/n/r/n"); return "*******************" + sSheetName + " Pass*******************/r/n/r/n"; } else { System.out.println("*******************" + sSheetName + " Fail*******************/r/n/r/n"); return "*******************" + sSheetName + " Fail*******************/r/n" + log + "/r/n"; } } else { for (int i = 0; i < baseLineList.size(); i++) { List<?> baseLineValue = (List<?>) baseLineList.get(i); List<?> resultValue = (List<?>) resultList.get(i); for (int j = 0; j < baseLineValue.size(); j++) { if (!resultValue.get(j).toString().equals(baseLineValue.get(j).toString())) { System.out.println("Point(" + (i + 5) + "," + (j + 1) + ")" + baseLineValue.get(0).toString() + " (baseline:" + baseLineValue.get(j) + " export:" + resultValue.get(j).toString() + ")/r/n"); log = log + "Point(" + (i + 5) + "," + (j + 1) + ")" + baseLineValue.get(0).toString() + " (baseline:" + baseLineValue.get(j) + " export:" + resultValue.get(j).toString() + ")/r/n"; } } } if (log.equals("")) { System.out.println("*******************" + sSheetName + " Pass*******************/r/n/r/n"); return "*******************" + sSheetName + " Pass*******************/r/n/r/n"; } else { System.out.println("*******************" + sSheetName + " Fail*******************/r/n/r/n"); return "*******************" + sSheetName + " Fail*******************/r/n" + log + "/r/n"; } } }
回页首
本文提供了金融财务类软件的自动化测试框架,结合 IBM Rational Functional Tester 的数据池以及 Apache POI HSSF 对 Excel 的支持实现了一个高效,灵活,可配置的自动化测试框架,并提供了相应的方法和实例演示。自动化测试开发人员使用这些技术作为基石,可以高效的实现更为复杂的金融财务系统自动化。