数据驱动在Uiautomator中的使用

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