1 概述
对于一个函数或者一个流程,给定一个输入,应当返回一个结果
输入指参数或条件的组合,也就构成不同的测试场景
结果即程序对输入的处理,将其与预期结果比对,便可知当前测试场景功能的正确性
当这些数据以外部文件的形式存储时,就可以很方便地修改追加,而不用重新调整代码
2 @DataProvider
在Testng中,@DataProvider负责向测试方法提供数据,返回Object[][]
对象
于是可以考虑,在@DataProvider方法中,实现读取外部文件,并将文件的内容以Object[][]
形式返回
方案:
- 每个方法对应各自的dataProvider,读取各自的数据文件;易于实现,但代码过于冗余,并且数据文件太零散,读频繁
- 所有方法对应一个dataProvider,dataProvider根据调用的方法名选择对应的测试数据;实现复杂,且需要考虑并行,但代码精简
public class DataDriver {
public static Map data = null;
@DataProvider(name = "dp")
public static Object[][] getData(Method method) {
if (data == null || data.get(method.getName()) == null) {
return new Object[][] {};
}
return data.get(method.getName());
}
}
这里选择第2种方式,但是并没有读外部文件,只提供按方法名选取数据
考虑到测试用例过多,如果一次性将所有用例数据加载到内存,会过于臃肿,所以决定只加载当前case的数据
3 TestCase
@BeforeClass阶段初始化数据,@AfterClass阶段回收
读取数据时,封装了工具类;根据不同的数据载体,可以编写多套工具类
测试方法均指向同一个dataProvider,不要忘记加上dataProviderClass属性
public class IpCase {
@BeforeClass
public void setup() {
String caseFullName = this.getClass().getName();
String caseName = caseFullName.substring(caseFullName.lastIndexOf('.') + 1);
DataDriver.data = ExcelUtil.readExcel(caseName);
}
@AfterClass
public void teardown() {
DataDriver.data.clear();
}
@Test(dataProvider = "dp", dataProviderClass = DataDriver.class)
public void isIpv41(String ip, boolean expected) {
boolean actual = ip != null;
Assert.assertEquals(actual, expected);
}
@Test(dataProvider = "dp", dataProviderClass = DataDriver.class)
public void isIpv6(String ip, boolean expected) {
boolean actual = ip != null;
Assert.assertEquals(actual, expected);
}
}
4 处理Excel文件
数据存储,可以使用Excel,TXT,数据库也行,挑选一个易于维护、易于阅读的载体即可
这里我们暂定为excel,使用poi包进行解析
excel的名称要求与TestCase一致(IpCase.xlsx),且sheet名称对应各个测试方法(isIpv4, isIpv6),这样便可以根据方法名获取相应的测试数据
isIpv4对应数据如下,首行为标题,解析时注意跳过
IP | Expected |
---|---|
1.1.1.1 | TRUE |
2.2.2.256 | FALSE |
isIpv6对应数据如下
IP | Expected |
---|---|
::1 | TRUE |
FF01::1101 | TRUE |
public class ExcelUtil {
private static final String SUFFIX_2007 = ".xlsx";
public static Map readExcel(String caseName) {
Map result = new HashMap<>();
InputStream is = null;
try {
is = ExcelUtil.class.getClassLoader().getResourceAsStream(String.format("TestCase/%s%s", caseName, SUFFIX_2007));
XSSFWorkbook workbook = new XSSFWorkbook(is);
for (int sheetNum = 0; sheetNum < workbook.getNumberOfSheets(); sheetNum++) {
Sheet sheet = workbook.getSheetAt(sheetNum);
int rows = sheet.getLastRowNum();
if (rows <= 0) continue;
int cols = sheet.getRow(1).getLastCellNum();
Object[][] data = new Object[rows][cols];
for (int rowNum = 1; rowNum <= rows; rowNum++) {
Row row = sheet.getRow(rowNum);
for (int celNum = 0; celNum < cols; celNum++) {
Cell cell = row.getCell(celNum);
Object cellValue = null;
CellType cellType = cell.getCellTypeEnum();
if (cellType == CellType.BOOLEAN) {
cellValue = cell.getBooleanCellValue();
} else if (cellType == CellType.STRING) {
cellValue = cell.getStringCellValue();
} else if (cellType == CellType.NUMERIC) {
cellValue = cell.getNumericCellValue();
} else {
cellValue = "";
}
data[rowNum - 1][celNum] = cellValue;
}
}
result.put(sheet.getSheetName(), data);
}
} catch (IOException e) {
System.out.println("File not found: " + caseName);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}
}
5 @Test注解转换
当测试方法需要加载数据时,都不得不指定属性dataProvider = "dp", dataProviderClass = DataDriver.class
为了省事,可以使用IAnnotationTransformer监听器
- 约定方法名包含“Dp”、“_dp”或其他你喜欢的后缀时,自动给@Test注解添加dataProvider属性
- 指定group名,假设group = dp的测试方法均自动补充属性
public class TestAnnotationTransformer implements IAnnotationTransformer {
@Override
public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
String[] groups = annotation.getGroups();
for(String group: groups) {
if (group.equals("dp")) {
annotation.setDataProvider("dp");
annotation.setDataProviderClass(DataDriver.class);
break;
}
}
}
}
6 后续优化
- 目前只有excel解析工具类,且只支持2007版,可以补充2007以下版本支持
- Excel数据类型判断,浮点数、日期等类型需要加强
- 并行执行时,DataDriver.data可能冲突,需要将引用迁移到Case类下