手把手教你搭建数据驱动测试框架
手把手教你搭建数据驱动测试框架 (一)#
前言
在自动化测试框架中,数据驱动的意思指定的是测试用例或者说测试套件是由外部数据集合来驱动的框架。这里说的数据集可以是任何类型的数据文件比如xls,xlsx,csv等等。它的核心的思想就是数据和测试代码分离,及时当测试数据发生大量变化的情况下测试代码(或者说测试用例)可以保持不变。数据集合里有1条数据和100条甚至更多,是不会影响到测试用例的执行。如果想使用一组不同的数据来执行的相同的操作没那么你就可以选择数据驱动。
比如,在这么一种场景下,例如你需要用多个不同的账号和密码来登陆某个邮箱,来验证哪些是有效的值,哪些是错误的值,或者哪些值可以导致出错,这只是一个简单的例子。另外一个简单的例子就是网络电话的测试,当我们模拟拨号、挂断、回拨等等操作时我们希望验证每个按钮是否能正常工作并能得到正确的结果。
本篇文章是基于Selenium WebDriver的数据驱动测试框架,如果大家跟着一步一步搭建下来,最终的框架将最终实现下面的这些功能:
- 可以在测试套件列表里设置指定的测试套件是否执行
- 可以在测试用例列表里设置指定的测试用例是否执行
- 可以在测试数据表里设置某行测试数据是否被读取执行
- 一个测试用例可以由多个测试数据驱动运行
- 可以打印测试结果报告,显示指定数据行执行之后的结果
- 可以打印整体测试结果报告,显示哪些用例执行通过,哪些执行失败、哪些跳过没执行
- 报告可以是testng的形式或者是XSLT
接下来的过程中不会讲太多理论的东西,更注重于实践的步骤,我将一步一步的介绍如何搭建这个框架,演示总个过程。
在开始之前还是要啰嗦一下,大家要做自动化测试,开发能力是必须的。所以下面所有过程都是假定一个前提,那就是你必须熟悉java,eclipse,TestNg,ant,POI等等工具,如果还不清楚的同学可以先熟悉一下这
搭建
创建工作空间
按照下面步骤在创建 项目工作空间
- 在F盘下创建文件夹backup
- 在backup文件夹下创建Training文件夹
创建完毕就有这样的目录结构F:\backup\Training。然后打开eclipse,选择上面创建的文件夹为工作空间,如下图所示:
创建项目####
创建一个名为WDDF的项目,我们将使用这个项目来搭建我们的测试框架,创建完毕,包结构如图所示:
创建项目的目录结构
创建项目目录结构也就是在项目下创建需要的包和文件夹结构。包结构合理就很容易对项目的资源进行管理而不胡乱。在“WDDF”项目下创建如下包:
- com.stta.ExcelFiles: --存放.xsl文件
- com.stta.Logging: --存放.log文件
- com.stta.property: --存放.property文件
- com.stta.SuiteOne: --存放测试套件一相关文件
- com.stta.SuiteTwo: --存放测试套件二相关文件
- com.stta.TestSuiteBase: --存放基本类文件
- com.stta.utility: --存放工具类文件
- com.stta.xslt: --存放testng-results.xsl文件
同时创建文件夹:
1.JarFiles: --存放所有需要的相关的jar文件
到此,项目结构就如下图所示:
针对不同的文件类型创建分离的包的是由好处的,这样子可以在我们查找、修改文件的时候很便利,例如你要修稿.xls文件的数据,那么你几可以直接在com.stta.ExcelFiles包里面找,同样的你想查看执行的日志,就可以直接在com.stta.Logging里面查看,非常直观。
好了,到此为止,可以说我们项目的基本初始化工作基本完成。后续,在需要的时候,我们还会添加新的的包和文件到项目中去。
下载需要的Jar包
现在项目结构都创建好,接下来要做的就是下载所有用到的Jar包。这里我会罗列所有需要下载jar包。并一个个下载下来保存到JarFiles文件夹下。
Apache POI API####
Apache POI API是用来从.xls文件中写入或者读取数据用的,所以必须下载Apache POI API和它依赖相关的jar包,并设置它门在build path中,我们才能使用它。
Apache POI API可以在它的官网直接下载。进去后点击“poi-bin-3.10-FINAL-20140208.tar.gz”。下载完毕后解压,解压目录下和子目录里面的下列文件拷贝到“WDDF"项目的JarFiles文件夹下:
-
poi-3.10-FINAL-20140208.jar
-
poi-ooxml-3.10-FINAL-20140208.jar
-
poi-ooxml-schemas-3.10-FINAL-20140208.jar
-
xmlbeans-2.3.0.jar (在子目录 ooxml-lib 下)
-
dom4j-1.6.1.jar (在子目录 ooxml-lib 下)
注:上述jar包在以后可能会变,因为版本会一直更新嘛。用最新的就是了,下面提到的jar包情况和此处一样
Apache log4j
Apache log4j 是用来记录测试执行期间产生的日志的,我们也需要把它加到build path中。从Apache log4j主页下载Apache log4j jar文件,并拷贝到“WDDF"项目的JarFiles文件夹下:
- log4j-1.2.17.jar
Selenium WebDriver#####
如果你使用过Selenium WebDriver,估计就知道使用selenium webdriver需要下载那些jar包了。从Selenium WebDriver主页下载Selenium WebDriver的zip包,解压,并把"selenium-x.xx.x"目录和它的子目录“libs”下的所有jar文件拷贝到“WDDF"项目的JarFiles文件夹下。
与生成XSLT报告相关的jar
为了能够产生交互式的测试报告,我们还需要一些工具的支持。到这个页面下载一个zip包。解压,并把"testng-xslt-1.1.2-master" -> "lib" 下的下列文件拷贝到“WDDF"项目的JarFiles文件夹下:
- saxon-8.7.jar
- SaxonLiaison.jar
拷贝 “testng-xslt-1.1.2-master\src\main\resources"目录下的testng-results.xsl文件到包com.stta.xslt下面。
- testng-results.xsl
还要在这里下载testng-xslt-maven-plugin-test-0.0.jar,并拷贝到JarFiles文件夹下。后续如果需要,我们再下载其他jar包。
到此为止,我们的JarFile目录和com.stta.xslt包看起来如下图所示:
配置环境
本框架中,我们使用Apache POI API 来从.xls 中读取数据,Apache log4j来记录测试执行过程中产生的日志,而xslt 报告用来产生交互式HTML报告。下载完相关的jar包后,我们就需要把他们加到 项目的build path中。
- 选择邮件点击项目 WDDF,选择 "Build Path" -> "Configure Build Path"
- 点击Libraries->Add external JARs
- 选择上述下载的所有jar包,点添加->OK
这样所有jar就添加到build path中了。查看一下eclipse,会多了一个“Referenced Libraries”,Referenced Libraries下包含所有所需 的jar包:
创建类文件
创建基类####
com.stta.TestSuiteBase下创建SuiteBase.java,这个类用于总个框架测试套件的基类
1.SuiteBase.java
com.stta.SuiteOne这个包用来存放测试套件一的相关的类,创建如下类:
- SuiteOneBase.java ---这个类用于测试套件一的基类
- SuiteOneCaseOne.java ---这个为测试套件一的用例一
- SuiteOneCaseOne.java ---这个为测试套件二的用例二
com.stta.SuiteTwo 这个包用来存放测试套件二相关的类,创建如下类:
- SuiteTwoBase.java
- SuiteTwoCaseOne.java
- SuiteTwoCaseTwo.java
com.stta.utility 包作为测试框架的基本工具包,创建如下两个类:
- Read_XLS.java ---读取.xls文件相关更能的类
- SuiteUtility.java ---工具类
创建完毕,目录结构如图所示:
创建xls文件
为了简单起见,这次搭建的测试框架只支持.xls文件,通过检索.xls文件,测试框架可以获取要测试套件名称、是否执行的标志,测试用例的名称以及是否执行用例的标志位,而且等测试执行完毕,可以把测试结果.xls文件的末尾。现在先创建如三个.xls文件:
- TestSuiteList.xls
- SuiteOne.xls
- SuiteTwo.xls
创建完,com.stta.ExcelFiles看起来像下面的样子:
下面讲下这三个.xls文件怎么创建:
- TestSuiteList.xls
TestSuiteList.xls文件只有第一个sheet有数据,把第一个sheet命名为SuitesList,SuitesListl里面有三列分别是SuiteName、SuiteToRun、Skipped/Executed 如图:
- SuiteOne.xls与SuiteTwo.xls
这两文件里面的表格是一致的,就放在一起说明,这两个.xls文件有三个sheet,第一个是TestCasesList,测试用例列表,指明本测试套有几个测试用例。第二和第三个sheet名称分别是SuiteOneCaseOne和SuiteOneCaseTwo,分别是测试用例一个测试用例二的测试数据。TestCasesList如下图所示,表格也是有三列,分别表示测试用例名称、是否执行、和执行结果 三个。
SuiteOneCaseOne和SuiteOneCaseTwo的表格结构如下图所示:
记住一个原则,如下图所示,即eclipse里从测试类的名称、TestCasesList表的测试用例名称和测试用例数据的sheet名称这个三个要保持一致。后续增加任何的测试用例都要遵循这个原则。
开始写代码
前面把要用掉的类文件先创建起来了,需要的用到的文件也都准备好了,可以开始写代码了。首先先写框架的工具类来读取数据,因为这些工具类在其他类中频繁用到先写。
- Read_XLS.java代码如下
<p>
package com.stta.utility;
import java.io.FileInputStream;
import java.io.FileOutputStream;
//import java.io.IOException;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
public class Read_XLS {
public String filelocation;
public FileInputStream ipstr = null;
public FileOutputStream opstr =null;
private HSSFWorkbook wb = null;
private HSSFSheet ws = null;
public Read_XLS(String filelocation) {
this.filelocation=filelocation;
try {
ipstr = new FileInputStream(filelocation);
wb = new HSSFWorkbook(ipstr);
ws = wb.getSheetAt(0);
ipstr.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//检索 .xls 文件 sheets的行数.
public int retrieveNoOfRows(String wsName){
int sheetIndex=wb.getSheetIndex(wsName);
if(sheetIndex==-1)
return 0;
else{
ws = wb.getSheetAt(sheetIndex);
int rowCount=ws.getLastRowNum()+1;
return rowCount;
}
}
//检索.xls文件sheets的列数
public int retrieveNoOfCols(String wsName){
int sheetIndex=wb.getSheetIndex(wsName);
if(sheetIndex==-1)
return 0;
else{
ws = wb.getSheetAt(sheetIndex);
int colCount=ws.getRow(0).getLastCellNum();
return colCount;
}
}
//读取测试套件和测试用例的SuiteToRun and CaseToRun标志
public String retrieveToRunFlag(String wsName, String colName, String rowName){
int sheetIndex=wb.getSheetIndex(wsName);
if(sheetIndex==-1)
return null;
else{
int rowNum = retrieveNoOfRows(wsName);
int colNum = retrieveNoOfCols(wsName);
int colNumber=-1;
int rowNumber=-1;
HSSFRow Suiterow = ws.getRow(0);
for(int i=0; i<colNum; i++){
if(Suiterow.getCell(i).getStringCellValue().equals(colName.trim())){
colNumber=i;
}
}
if(colNumber==-1){
return "";
}
for(int j=0; j<rowNum; j++){
HSSFRow Suitecol = ws.getRow(j);
if(Suitecol.getCell(0).getStringCellValue().equals(rowName.trim())){
rowNumber=j;
}
}
if(rowNumber==-1){
return "";
}
HSSFRow row = ws.getRow(rowNumber);
HSSFCell cell = row.getCell(colNumber);
if(cell==null){
return "";
}
String value = cellToString(cell);
return value;
}
}
//读取测试数据的DataToRun标志.
public String[] retrieveToRunFlagTestData(String wsName, String colName){
int sheetIndex=wb.getSheetIndex(wsName);
if(sheetIndex==-1)
return null;
else{
int rowNum = retrieveNoOfRows(wsName);
int colNum = retrieveNoOfCols(wsName);
int colNumber=-1;
HSSFRow Suiterow = ws.getRow(0);
String data[] = new String[rowNum-1];
for(int i=0; i<colNum; i++){
if(Suiterow.getCell(i).getStringCellValue().equals(colName.trim())){
colNumber=i;
}
}
if(colNumber==-1){
return null;
}
for(int j=0; j<rowNum-1; j++){
HSSFRow Row = ws.getRow(j+1);
if(Row==null){
data[j] = "";
}
else{
HSSFCell cell = Row.getCell(colNumber);
if(cell==null){
data[j] = "";
}
else{
String value = cellToString(cell);
data[j] = value;
}
}
}
return data;
}
}
//从测试用例数据sheets读取测试数据.
public Object[][] retrieveTestData(String wsName){
int sheetIndex=wb.getSheetIndex(wsName);
if(sheetIndex==-1)
return null;
else{
int rowNum = retrieveNoOfRows(wsName);
int colNum = retrieveNoOfCols(wsName);
Object data[][] = new Object[rowNum-1][colNum-2];
for (int i=0; i<rowNum-1; i++){
HSSFRow row = ws.getRow(i+1);
for(int j=0; j< colNum-2; j++){
if(row==null){
data[i][j] = "";
}
else{
HSSFCell cell = row.getCell(j);
if(cell==null){
data[i][j] = "";
}
else{
cell.setCellType(Cell.CELL_TYPE_STRING);
String value = cellToString(cell);
data[i][j] = value;
}
}
}
}
return data;
}
}
public static String cellToString(HSSFCell cell){
int type;
Object result;
type = cell.getCellType();
switch (type){
case 0 :
result = cell.getNumericCellValue();
break;
case 1 :
result = cell.getStringCellValue();
break;
default :
throw new RuntimeException("Unsupportd cell.");
}
return result.toString();
}
//在测试数据和测试用例表里写入测试结果
public boolean writeResult(String wsName, String colName, int rowNumber, String Result){
try{
int sheetIndex=wb.getSheetIndex(wsName);
if(sheetIndex==-1)
return false;
int colNum = retrieveNoOfCols(wsName);
int colNumber=-1;
HSSFRow Suiterow = ws.getRow(0);
for(int i=0; i<colNum; i++){
if(Suiterow.getCell(i).getStringCellValue().equals(colName.trim())){
colNumber=i;
}
}
if(colNumber==-1){
return false;
}
HSSFRow Row = ws.getRow(rowNumber);
HSSFCell cell = Row.getCell(colNumber);
if (cell == null)
cell = Row.createCell(colNumber);
cell.setCellValue(Result);
opstr = new FileOutputStream(filelocation);
wb.write(opstr);
opstr.close();
}catch(Exception e){
e.printStackTrace();
return false;
}
return true;
}
//在测试套件表里写入测试结果.
public boolean writeResult(String wsName, String colName, String rowName, String Result){
try{
int rowNum = retrieveNoOfRows(wsName);
int rowNumber=-1;
int sheetIndex=wb.getSheetIndex(wsName);
if(sheetIndex==-1)
return false;
int colNum = retrieveNoOfCols(wsName);
int colNumber=-1;
HSSFRow Suiterow = ws.getRow(0);
for(int i=0; i<colNum; i++){
if(Suiterow.getCell(i).getStringCellValue().equals(colName.trim())){
colNumber=i;
}
}
if(colNumber==-1){
return false;
}
for (int i=0; i<rowNum-1; i++){
HSSFRow row = ws.getRow(i+1);
HSSFCell cell = row.getCell(0);
cell.setCellType(Cell.CELL_TYPE_STRING);
String value = cellToString(cell);
if(value.equals(rowName)){
rowNumber=i+1;
break;
}
}
HSSFRow Row = ws.getRow(rowNumber);
HSSFCell cell = Row.getCell(colNumber);
if (cell == null)
cell = Row.createCell(colNumber);
cell.setCellValue(Result);
opstr = new FileOutputStream(filelocation);
wb.write(opstr);
opstr.close();
}catch(Exception e){
e.printStackTrace();
return false;
}
return true;
}
}
<p>
- SuiteUtility.java代码:
<p>
package com.stta.utility;
public class SuiteUtility {
public static boolean checkToRunUtility(Read_XLS xls, String sheetName, String ToRun, String testSuite){
boolean Flag = false;
if(xls.retrieveToRunFlag(sheetName,ToRun,testSuite).equalsIgnoreCase("y")){
Flag = true;
}
else{
Flag = false;
}
return Flag;
}
public static String[] checkToRunUtilityOfData(Read_XLS xls, String sheetName, String ColName){
return xls.retrieveToRunFlagTestData(sheetName,ColName);
}
public static Object[][] GetTestDataUtility(Read_XLS xls, String sheetName){
return xls.retrieveTestData(sheetName);
}
public static boolean WriteResultUtility(Read_XLS xls, String sheetName, String ColName, int rowNum, String Result){
return xls.writeResult(sheetName, ColName, rowNum, Result);
}
public static boolean WriteResultUtility(Read_XLS xls, String sheetName, String ColName, String rowName, String Result){
return xls.writeResult(sheetName, ColName, rowName, Result);
}
}
<p>
简单数据读取测试
测试框架的.xls文件读取工具已经准备好,按照前面创建的测试类,我们要创建两个测试套件,每个测试套件有两个测试用例,总共就是2个测试套件和4个测试用例。对于每个测试套件分别对应两个不同.xls文件。测试套件、测试用例与.xls文件之间的映射关系如下所示:
- com.stta.SuiteOne -> SuiteOne.xls
- SuiteOneCaseOne.java -> SuiteOneCaseOne Sheet
- SuiteOneCaseTwo.java -> SuiteOneCaseTwo Sheet
- com.stta.SuiteTwo -> SuiteTwo.xls
- SuiteTwoCaseOne.java -> SuiteTwoCaseOne Sheet
- SuiteTwoCaseTwo.java -> SuiteTwoCaseTwo Sheet
编写测试代码
我们先尝试从SuiteOne.xls文件的SuiteOneCaseOne sheet里读取数据,并驱动测试用例SuiteOneCaseOne.java执行。首先先写SuiteBase.java。
-
SuiteBase.java
SuiteBase类中创建一个初始化方法,用来初始化.xls文件路径
<p>package com.stta.TestSuiteBase;
import java.io.IOException;
import com.stta.utility.Read_XLS;public class SuiteBase {
public static Read_XLS TestSuiteListExcel=null;
public static Read_XLS TestCaseListExcelOne=null;
public static Read_XLS TestCaseListExcelTwo=null;public void init() throws IOException{
//代码中文件路径根据实际情况填写
//使用Read_XLS工具类初始化测试测试套件列表TestSuiteList.xls
TestSuiteListExcel = new Read_XLS(System.getProperty("user.dir")+"\src\com\stta\ExcelFiles\TestSuiteList.xls");
//使用Read_XLS工具类初始化测试套件一SuiteOne.xls
TestCaseListExcelOne = new Read_XLS(System.getProperty("user.dir")+"\src\com\stta\ExcelFiles\SuiteOne.xls");
//使用Read_XLS工具类初始化测试套件二SuiteTwo.xls
TestCaseListExcelTwo = new Read_XLS(System.getProperty("user.dir")+"\src\com\stta\ExcelFiles\SuiteTwo.xls");
}
}
<p>
- SuiteOneBase.java
SuiteOneBase类在这个步骤中我们还不实现任何功能,仅仅继承了SuiteBase类
<p>
package com.stta.SuiteOne;
import com.stta.TestSuiteBase.SuiteBase;
//SuiteOneBase 类继承 SuiteBase 类.
public class SuiteOneBase extends SuiteBase{
}
<p>
- SuiteOneCaseOne.java
这是我们的测试类,在这个文件中我们从.xls文件读取数据并打印出来。这个类 包含了下列三个方法:
- checkCaseToRun()---使用testNG的@BeforeTest标签,它将在所有@Test方法之前被运行,这个方法只要就是调用SuiteBase类的Init() 来初始化.xls文件路径,保存在变量FilePath中。
- SuiteOneCaseOneTest():这个是测试方法,附带@Test标签,并使用dataProvider 来获取数据
- SuiteOneCaseOneData():从.xls文件读取数据并返回给SuiteOneCaseOneTest()方法
<p>
package com.stta.SuiteOne;
import java.io.IOException;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import com.stta.utility.Read_XLS;
import com.stta.utility.SuiteUtility;
//SuiteOneCaseOne 类 继承自 SuiteOneBase 类.
public class SuiteOneCaseOne extends SuiteOneBase{
Read_XLS FilePath = null;
String TestCaseName = null;
@BeforeTest
public void checkCaseToRun() throws IOException{
//调用SuiteBase类的init()来初始化.xls文件
init();
FilePath = TestCaseListExcelOne;
System.out.println("FilePath Is : "+FilePath);
TestCaseName = this.getClass().getSimpleName();
System.out.println("TestCaseName Is : "+TestCaseName);
}
//在每个迭代中接收4列的数据.
@Test(dataProvider="SuiteOneCaseOneData")
public void SuiteOneCaseOneTest(String DataCol1,String DataCol2,String DataCol3,String ExpectedResult){
System.out.println("Value Of DataCol1 = "+DataCol1);
System.out.println("Value Of DataCol2 = "+DataCol2);
System.out.println("Value Of DataCol3 = "+DataCol3);
System.out.println("Value Of ExpectedResult = "+ExpectedResult);
}
//data provider回在每个迭代中一个一个返回4列数据
@DataProvider
public Object[][] SuiteOneCaseOneData(){
//To retrieve data from Data 1 Column,Data 2 Column,Data 3 Column and Expected Result column of SuiteOneCaseOne data Sheet.
//Last two columns (DataToRun and Pass/Fail/Skip) are Ignored programatically when reading test data.
return SuiteUtility.GetTestDataUtility(FilePath, TestCaseName);
}
}
<p>
运行数据读取测试
到此为止,我们简单的数据读取测试已经准备完毕,可以试运行一下确保没问题,再继续往下。在SuiteOneCaseOne.java文件点击右键选择 Run As -> TestNG 。如果你是按照前述的步骤一步一步来的话,测试运行时没问题的。因为SuiteOneCaseOne数据sheet里有两行数据,所以@Test将会被执行两次。执行完,就可以在控制台看到如下信息:
如果你看到上述消息,说明到目前为止我们搭建的基于数据驱动的自动化是框架暂时是没有问题的,已经可以成功简单的数据读入测试。
关于数据区驱动自动化测试框架的搭建教程第一部分先到这,后续将继续实现真实的测试用例、融合testng.xml来管理测试用例、整合Selenium WebDriver、ant,添加报告打印等等,逐渐完善成一个功能齐全的框架。大家都可以见到真实的搭建过程。敬请关注!