Part 1: 前言
本文的基础是Junit4的参数化,如果不了解请阅读或百度Junit4参数化
Part 2: 理论依据
数据驱动的设计思路是,把需要的数据放到excel表格中,然后由程序去读里面的数据,传入程序里进行测试。因此涉及到了两个方面:
- 数据读取
- 数据分配
在数据分配上由于Junit已经实现了,因此只需要把数据读到程序里,形成一个collocation即可。这里需要用到POI,使用POI来进行读取Excel的操作。
Part 3:实现步骤
在AS中添加POI的依赖:
依赖添加步骤:
在要添加的jar文件上点击右键,然后选择在弹出菜单中选择Add As Library如下图:

或者直接在build.gradle(App下那个)中的dependencies里添加jar的路劲如下:
1
| compile files('src/libs/poi-3.17-beta1.jar')
|
使用POI读取Excel
读取Excel步骤分析:
- 拿到excel表格
- 拿到表格里的sheet
- 拿到行和列
- 拿到单元格数量
- 判断单元格里的内容并读取。
Part 3 代码实现
读取Excel部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| //从文件流中读取sheet里的信息,并放到一个Object数组里 private Collection<Object[]> readExcelData(final InputStream excelFile) throws IOException { //新建一个Excel工作簿文件 HSSFWorkbook workbook = new HSSFWorkbook(excelFile); //用来存放行、列、单元格里的数据 data = new ArrayList<Object[]>(); //获取第一个sheet Sheet sheet = workbook.getSheetAt(0); //统计sheet里不为空的行 int numberOfColumns = countNonEmptyColumns(sheet); //新建两个数组用来存放列和列里面的数据 List<Object[]> rows = new ArrayList<Object[]>(); List<Object> rowData = new ArrayList<Object>(); int rowNum = sheet.getPhysicalNumberOfRows(); //遍历解析sheet里的数据 // for (Row row : sheet) { for (int i = 1; i < rowNum; i++) { //如果row里的为空,则退出循环 Row row = sheet.getRow(i); if (isEmpty(row)) { break; } else { rowData.clear(); //循环遍历所有列 for (int column = 1; column < numberOfColumns; column++) { //读取所有列里的内容 Cell cell = row.getCell(column); rowData.add(cellFormatCheck(workbook, cell)); } rows.add(rowData.toArray()); } } return rows; }
|
单元格判断
在读取单元格里内容时,需要先判断单元格里值的类型。然后根据不同类型在分别读取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| //判断Row的内容是否为空 private boolean isEmpty(final Row row) { Cell firstCell = row.getCell(0); boolean rowIsEmpty = (firstCell == null) || (firstCell.getCellTypeEnum() == CellType.BLANK); return rowIsEmpty; } /*判断单元格里的公式类型 如=SUM(A1:E1*{1,2,3,4,5} */ private Object evaluateCellFormula(final HSSFWorkbook workbook, final Cell cell) { FormulaEvaluator evaluator = workbook.getCreationHelper() .createFormulaEvaluator(); CellValue cellValue = evaluator.evaluate(cell); Object result = null; if (cellValue.getCellTypeEnum() == CellType.BOOLEAN) { result = cellValue.getBooleanValue(); } else if (cellValue.getCellTypeEnum() == CellType.NUMERIC) { result = cellValue.getNumberValue(); } else if (cellValue.getCellTypeEnum() == CellType.STRING) { result = cellValue.getStringValue(); } return result; } /* * 判断不为空的行数,通过第一行的不为空的单元格 */ private int countNonEmptyColumns(final Sheet sheet) { Row firstRow = sheet.getRow(0); return firstEmptyCellPosition(firstRow); } /* 获取第一行不为空的单元格数量 */ private int firstEmptyCellPosition(final Row cells) { int columnCount = 0; for (Cell cell : cells) { if (cell.getCellTypeEnum() == CellType.BLANK) { break; } columnCount++; } return columnCount; }
|
读取单元格里的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| private Object cellFormatCheck(final HSSFWorkbook workbook, final Cell cell) { Object cellValue = null; //判断对应cell(单元格)的值,并读取出来。 if (cell.getCellTypeEnum() == CellType.STRING) { cellValue = cell.getRichStringCellValue().getString(); } else if (cell.getCellTypeEnum() == CellType.NUMERIC) { cellValue = getNumericCellValue(cell); } else if (cell.getCellTypeEnum() == CellType.BOOLEAN) { cellValue = cell.getBooleanCellValue(); } else if (cell.getCellTypeEnum() == CellType.FORMULA) { cellValue = evaluateCellFormula(workbook, cell); } return cellValue; } //从单元格中读取数字的值 private Object getNumericCellValue(final Cell cell) { Object cellValue; if (DateUtil.isCellDateFormatted(cell)) { cellValue = new Date(cell.getDateCellValue().getTime()); } else { cellValue = cell.getNumericCellValue(); } return cellValue; }
|
获取读取到的数据并返回
1 2 3 4 5 6 7 8 9 10
| // 从传入的文件流里读取excel内容,并放到data集合数组里。 public ExcelReader(final InputStream excelInputStream) throws IOException { this.data = readExcelData(excelInputStream); } // put the data in collection and return the data. public Collection<Object[]> getData() { return data; }
|
在测试中的使用
定义数据源
1 2 3 4 5 6 7
| @Parameterized.Parameters public static Collection testDataSource() throws IOException { InputStream excelURL = new FileInputStream(Environment.getExternalStorageDirectory() + "/1.xls"); return new ExcelReader(excelURL).getData(); }
|
使用构建参数用于数据传递
1 2 3 4 5 6 7 8 9 10 11 12
| private String appName; private String appPackage; /* 在构建方法里指定参数的对应关系 */ public DataDrivenTestCase(String target, String result) { this.appName = target; this.appPackage = result; }
|
编写测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Test public void OpenAppFormList() { try { DemoTools.isClickByDesc("Apps"); UiScrollable uiScrollable = new UiScrollable(new UiSelector().resourceId("com.android.launcher3:id/apps_customize_pane_content")); if (DemoTools.getUiDevice().wait(Until.hasObject(By.res("com.android.launcher3:id/active")), 5000)) { uiScrollable.setAsHorizontalList(); if (uiScrollable.scrollTextIntoView(appName)) { DemoTools.isClickByText(appName); Thread.sleep(1000); Assert.assertTrue("Test Fail,Package does not match!", DemoTools.getCurrentPackageName().equals(appPackage)); } else { fail("The Target Object " + appName + " Not Found!"); } } } catch (UiObjectNotFoundException e) { e.printStackTrace(); Log.i("BlogDemo", e.toString()); } catch (InterruptedException e) { e.printStackTrace(); } }
|
Part 4 总结
- 构建参数里的参数个数要和表格里给出的列数一致,不然会报参数错误,如图:

- 参数会到该类里所有测试方法都起作用。因此即使在有的测试用例里,没用到相关数据,case依然会执行多次(按数据条数计)
- 本次的难点在于,从excel里读取数据,然后返回成Junit4要求的格式,折腾了好久,终于搞定。不容易啊
- 记得添加读取存储的权限哦!
示例和资料
示例
本次示例
参考资料
Busy Developers’ Guide to HSSF and XSSF Features
Data-driven tests With JUnit 4 and Excel
Parameterized-tests