手把手教你java+selenium数据驱动测试框架搭建与实践

最近在看Java+selenium+TestNg+Excel的数据驱动,如何使用TestNg和Excel进行数据驱动测试。我其实是个自动化测试小白,工作之余看看这方面的书,照着敲敲代码,慢慢理解,希望通过自己坚持不懈的努力,在测试这个职位上越来越优秀。

#前言

本文主要实现的功能为登录126邮箱,登录成功后,在通讯录中新建联系人,登录邮箱的账号、密码以及新建的联系人的信息都写在Excel文件中,调用Excel文件中的数据,测试用例执行之后并将测试结果写入Excel文件的最后一列。

#环境准备

(1)新建一个Java工程,命名为“Test126mailDataDriver”;

(2)在该工程中配置好WebDriver和TestNG环境,并导入Excel操作及Log4j相关的JAR文件到工程中;

(3)在工程中新建4个Package:

--cn.wangxy.appModules:主要用于实现复用的业务逻辑封装方法;

--cn.wangxy.pageObjects:主要用于实现被测试对象的页面对象;

--cn.wangxy.testScripts:主要用于实现具体的测试逻辑脚本;

--cn.wangxy.util:主要用于实现测试过程中调用的工具类方法,如文件操作、页面元素的操作方法等。

(4)创建一个Excel文件,命名为“126邮箱的数据驱动测试数据.xlsx”,文件的内容如下:其中登录名和密码是我真实的数据,我在这就隐藏掉了哦!

#代码

一、元素定位表达式

在项目根目录下新建一个objectMap.properties文件,编写页面元素的定位表达式。

解释:以下代码分别表示的页面元素为126邮箱的登录页面部分元素和给通讯录新建联系人时用到的元素,其中定位类型和定位表达式用">"分开。


126mail.loginPage.iframe=xpath>//iframe[contains(@id,'x-URS-iframe')]

126mail.loginPage.username=xpath>//input[@data-placeholder='邮箱帐号或手机号码']

126mail.loginPage.password=xpath>//*[@data-placeholder='输入密码']

126mail.loginPage.loginbutton=id>dologin

126mail.loginPage.addressbook=xpath>//div[contains(text(),'通讯录')]

126mail.addressbook.createContactPerson=xpath>//div/div/*[contains(@id,'_mail_button_')]/span[contains(.,'新建联系人')]

126mail.addressbook.createContactPersonName=xpath>//dt[contains(.,'姓名')]/following-sibling::dd/div/input

126mail.addressbook.createContactPersonEmail=xpath>//dt[contains(.,'电子邮箱')]/following-sibling::dd/div/input

126mail.addressbook.createContactPersonMobile=xpath>//dt[contains(.,'手机号码')]/following-sibling::dd/div/input

126mail.addressBook.saveButton=xpath>//*[contains(@id,'_mail_button_')]/span[contains(.,'确 定')]

二、读取配置文件

在cn.wangxy.util包下新建一个类ObjectMap,用于实现在外部配置文件中配置页面元素的定位表达式。

2.1 读取配置文件代码编写


Properties properties;

public ObjectMap(String propFile){

properties = new Properties();

try {

Reader in = new InputStreamReader(new FileInputStream(propFile),"UTF-8");

properties.load(in);

in.close();

} catch (IOException e) {

System.out.println("读取对象文件出错");

e.printStackTrace();

}

}

2.2 根据配置文件中传入的对象,判断需要使用哪种定位表达式


public By getLocator(String ElementNameInporFile) throws Exception{

//根据传入的变量,从属性配置文件中读取对应的配置对象

String locator = properties.getProperty(ElementNameInporFile);

//将配置对象中的定位类型存入locatorType变量,将定位表达式的值存入locatorValue变量

String locatorType = locator.split(">")[0];

String locatorValue = locator.split(">")[1];

//输出locatorType和locatorValue的值,验证赋值是否正确

System.out.println("获取的定位类型"+locatorType+"\t获取的定位表达式"+locatorValue);

//根据locatorType的变量值内容判断返回何种定位方式的By对象

if (locatorType.toLowerCase().equals("id")){

return By.id(locatorValue);

} else if(locatorType.toLowerCase().equals("name")){

return By.name(locatorValue);

}else if(locatorType.toLowerCase().equals("class")||locatorType.toLowerCase().equals("classname")){

return By.className(locatorValue);

}else if(locatorType.toLowerCase().equals("tagname")||locatorType.toLowerCase().equals("tag")){

return By.tagName(locatorValue);

}else if(locatorType.toLowerCase().equals("linktext")||locatorType.toLowerCase().equals("link")){

return By.linkText(locatorValue);

}else if(locatorType.toLowerCase().equals("partialLinkText")){

return By.partialLinkText(locatorValue);

}else if(locatorType.toLowerCase().equals("cssSelector")||locatorType.toLowerCase().equals("css")){

return By.cssSelector(locatorValue);

}else if(locatorType.toLowerCase().equals("xpath")){

return By.xpath(locatorValue);

}

else {

throw new Exception("输入的locator Type未在程序中被定义:"+locatorType);

}

}

三、获取登录页面的元素对象

在cn.wangxy.pageObjects包下新建一个LoginPage类,用于实现126邮箱登录页面的PageObject对象。


public class LoginPage {

private WebElement element = null;

//指定页面元素定位表达式配置文件的绝对路径

private ObjectMap objectMap = new ObjectMap("D:\\wangxiaoyu\\workspace\\Test126mailDataDriver\\objectMap.properties");

private WebDriver driver;

public LoginPage(WebDriver driver){

this.driver = driver;

}

//进入iframe

public void switchToFrame() throws Exception{

Thread.sleep(5000);

driver.switchTo().frame(driver.findElement(objectMap.getLocator("126mail.loginPage.iframe")));

}

//退出iframe

public void defaultToFrame(){

driver.switchTo().defaultContent();

}

//返回登录页面中的用户名输入框页面元素对象

public WebElement userName() throws Exception{

element = driver.findElement(objectMap.getLocator("126mail.loginPage.username"));

return element;

}

//返回登录页面中密码输入框页面元素对象

public WebElement password() throws Exception{

element = driver.findElement(objectMap.getLocator("126mail.loginPage.password"));

return element;

}

//返回登录页面中的登录按钮页面元素对象

public WebElement loginButton() throws Exception{

element = driver.findElement(objectMap.getLocator("126mail.loginPage.loginbutton"));

return element;

}

}

四、封装登录的测试逻辑

在cn.wangxy.appModules包下新建一个Login_Action类,用来封装登录的测试逻辑,这样的话,在任何一个测试用例中使用登录操作时,直接调用就行,方便后期的代码维护。


public class Login_Action {

public static void execute(WebDriver driver,String userName,String passWord) throws Exception{

driver.get("https://www.126.com/");

LoginPage loginPage = new LoginPage(driver);

loginPage.switchToFrame();

loginPage.userName().sendKeys(userName);

loginPage.password().sendKeys(passWord);

loginPage.loginButton().click();

Thread.sleep(5000);

loginPage.defaultToFrame();

Thread.sleep(3000);

}

}

五、小试牛刀-登录测试

在cn.wangxy.testScripts包下新建一个TestNg的测试类TestMail126Login。登录测试的脚本编写如下所示,邮箱的登录账号和密码用自己真实的数据,下面代码中我自己的隐藏了。


public class TestMail126Login {

public WebDriver driver;

String baseUrl = "https://www.126.com/";

@Test

public void testMailLogin() throws Exception {

Login_Action.execute(driver, "邮箱用户名", "邮箱密码");

Assert.assertTrue(driver.getPageSource().contains("未读邮件"));

}

@BeforeMethod

public void beforeMethod() {

//加载Firefox驱动程序

System.setProperty("webdriver.gecko.driver","D:\\geckodriver.exe");

//打开Firefox浏览器

driver=new FirefoxDriver();

driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);

}

@AfterMethod

public void afterMethod() {

driver.quit();

}

}

现在代码写的差不多了,就让我们怀着激动的心情在这个测试类上点击【右键】→【Run As】→【TestNg Test】,将现在的代码跑起来吧!只有一个超级简单的登录操作,运行正常且通过的话,我们开始进行下一步的操作!

上面的代码是我边敲,同时边写这篇文章的,代码都是我运行成功过的,从实际项目中复制过来的,所以正常情况下的运行是没有问题的。

六、获取登录后主页的页面元素对象

在cn.wangxy.pageObjects包下新建一个HomePage的类,用来获取登录后主页的页面元素对象,因为我们后面要进行的是在通讯录里面添加联系人,所以登录后需要点击主页中的通讯录按钮进入添加联系人的操作页面。以下代码只获取通讯录按钮的元素对象,如果需要在该页面操作更多的链接元素,可以根据需要进行自定义。


public class HomePage {

private WebElement element = null;

private ObjectMap objectMap = new ObjectMap("D:\\workspace\\Test126mailDataDriven\\objectMap.properties");

private WebDriver driver;

public HomePage(WebDriver driver){

this.driver = driver;

}

//获取登录后主页中的“通讯录”链接

public WebElement addressLink() throws Exception{

element = driver.findElement(objectMap.getLocator("126mail.loginPage.addressbook"));

return element;

}

}

七、获取添加联系人的页面元素对象

在cn.wangxy.pageObjects包下新建一个AddressBookPage类,用来获取添加联系人的页面元素对象


public class AddressBookPage {

private WebElement element = null;

private ObjectMap objectMap = new ObjectMap("D:\\workspace\\Test126mailDataDriven\\objectMap.properties");

private WebDriver driver;

public AddressBookPage(WebDriver driver){

this.driver =driver;

}

//获取新增联系人按钮

public WebElement createContactPersonButton() throws Exception{

element = driver.findElement(objectMap.getLocator("126mail.addressbook.createContactPerson"));

return element;

}

//获取新增联系人界面中的姓名输入框

public WebElement contactPersonName() throws Exception{

element =driver.findElement(objectMap.getLocator("126mail.addressbook.createContactPersonName"));

return element;

}

//获取新增联系人界面中的电子邮件输入框

public WebElement contactPersonEmail() throws Exception{

element =driver.findElement(objectMap.getLocator("126mail.addressbook.createContactPersonEmail"));

return element;

}

//获取新增联系人界面中的手机号输入框

public WebElement contactPersonMobil()throws Exception{

element =driver.findElement(objectMap.getLocator("126mail.addressbook.createContactPersonMobile"));

return element;

}

//获取新增联系人界面中保存信息的“确定”按钮

public WebElement saveButton() throws Exception{

element =driver.findElement(objectMap.getLocator("126mail.addressBook.saveButton"));

return element;

}

}

八、封装新建联系人的操作逻辑

在cn.wangxy.appModules包下新建一个AddContactPerson_Action类,用来封装新建联系人的操作逻辑。


public class AddContactPerson_Action {

public static void execute(WebDriver driver,String userName,String password,String contactName,String contactEmail,String contactMobil)throws Exception{

Login_Action.execute(driver, userName, password);

Thread.sleep(3000);

Assert.assertTrue(driver.getPageSource().contains("未读邮件"));

HomePage homePage = new HomePage(driver);

homePage.addressLink().click();

AddressBookPage addressBookPage = new AddressBookPage(driver);

Thread.sleep(3000);

addressBookPage.createContactPersonButton().click();

Thread.sleep(1000);

addressBookPage.contactPersonName().sendKeys(contactName);

Thread.sleep(3000);

addressBookPage.contactPersonEmail().sendKeys(contactEmail);

Thread.sleep(3000);

addressBookPage.contactPersonMobil().sendKeys(contactMobil);

Thread.sleep(3000);

addressBookPage.saveButton().click();

Thread.sleep(5000);

}

}

2、读取Excel文件指定单元格内容的方法


public static String getCellData(int RowNum,int ColNum){

try{

//通过函数参数指定单元格的行号和列号,获取指定的单元格对象

Cell = ExcelWSheet.getRow(RowNum).getCell(ColNum);

String CellData = Cell.getCellType()==XSSFCell.CELL_TYPE_STRING?Cell.getStringCellValue()+""

:String.valueOf(Math.round(Cell.getNumericCellValue()));

return CellData;

}catch(Exception e){

return "";

}

}

下面一行代码主要用来判断单元格内容的类型。如果单元格的内容为字符串类型,则使用getStringCellValue方法获取单元格的内容,如果单元格的内容为数字类型,则使用getNumericCellValue方法获取单元格的内容,注意getNumericCellValue方法返回值是double类型,转换字符串类型必须在Cell.getNumericCellValue前面增加"",用于强制转换double类型为String类型,不加""的话,则会抛出double类型无法转换为String类型的异常。


String CellData = Cell.getCellType()==XSSFCell.CELL_TYPE_STRING?Cell.getStringCellValue()+""

:String.valueOf(Math.round(Cell.getNumericCellValue()));

3、在Excel文件的执行单元格中写入数据

以下代码只支持扩展名为“.xlsx”的Excel文件


public static void setCellData(int RowNum,int ColNum,String Result) throws Exception{

try{

//获取Excel文件的行对象

Row = ExcelWSheet.getRow(RowNum);

//如果单元格为空,则返回null

Cell = Row.getCell(ColNum,Row.RETURN_BLANK_AS_NULL);

if (Cell==null) {

//当单元格对象是null的时候,则创建单元格

//如果单元格为空,无法直接调用单元格对象的setCellValue方法设定单元格的值

Cell = Row.createCell(ColNum);

//创建单元格后,可以调用单元格对象的setCellValue方法设定单元格的值

Cell.setCellValue(Result);

}else{

//单元格中有内容,则可以直接调用单元格对象的setCellValue方法设定单元格的值

Cell.setCellValue(Result);

}

//实例化写入Excel文件的文件输出流对象

FileOutputStream fileOut = new FileOutputStream(Constant.TestDataExcelFilePath);

//将内容写入excel文件

ExcelWBook.write(fileOut);

//调用flush方法强制刷新写入页面

fileOut.flush();

//关闭文件输出流对象

fileOut.close();

}catch (Exception e){

throw (e);

}

}

用于为TestNG提供数据驱动的数据集数组


public static Object[][] getTestData(String excelFilePath,String sheetName) throws IOException{

//根据参数传入的数据文件路径和文件名称,组合出excel数据文件的绝对路径

//声明一个File文件的对象

File file = new File(excelFilePath);

//创建FileInputStream对象用于读取Excel文件

FileInputStream inputStream = new FileInputStream(file);

//声明WorkBook对象

Workbook Workbook =null;

//获取文件名参数的扩展名,判断是“.xlsx”文件还是“.xls”文件

String fileExtensionName = excelFilePath.substring(excelFilePath.indexOf("."));

//文件类型如果是“.xlsx”,则使用XSSFWorkbook

//文件类型如果是".xls",则使用

if (fileExtensionName.equals(".xlsx")) {

Workbook = new XSSFWorkbook(inputStream);

} else if(fileExtensionName.equals(".xls")){

Workbook = new HSSFWorkbook(inputStream);

}

//通过sheetName参数,生成Sheet对象

Sheet Sheet = Workbook.getSheet(sheetName);

/*获取excel数据文件Sheet1中数据的行数,用getLastRowNum方法获取数据的最后一行行号,用getFirstNum

方法获取数据的第一行行号,相减之后算出数据的行数

注意:excel文件的行号和列号都是从0开始的*/

int rowCount = Sheet.getLastRowNum()-Sheet.getFirstRowNum();

//创建名为records的List对象来存储从excel文件读取的数据

ArrayList<Object[]> records = new ArrayList<Object[]>();

//使用两个for循环遍历excel文件的所有数据(除了第一行,第一行是数据列名称),所以i从1开始,而不是从0开始

for (int i = 1; i < rowCount+1; i++) {

Row row = Sheet.getRow(i);

/*

* 声明一个数组,用来存储excel测试数据文件每行中的测试用例和数据,数组的大小用getLastCellNum-2来进行

* 动态声明,实现测试数据个数和数组大小的一致,因为excel测试数据文件中测试数据行的最后一个单元格为测试执行结果,倒数第二个

* 单元格为此测试数据行是否运行的状态位,所以最后两列的单元格数据并不需要传入测试方法,因此,使用getLastCellNum-2

* 的方法去掉每行中最后两个单元格的数据,计算出需要存储的测试数据个数,并作为测试数据数组的初始化大小

*

*/

String fields[] = new String[row.getLastCellNum()-2];

/*

* if用于判断数据行是否要参与测试的执行,excel文件的倒数第二列为数据行的状态位,标记为y表示要执行,非y则不参与测试脚本的执行,会跳过

*/

if (row.getCell(row.getLastCellNum()-2).getStringCellValue().equals("y")) {

for (int j = 0; j < row.getLastCellNum()-2; j++) {

//判断Excel文件的单元格字段是数字还是字符串,字符串格式调用getStringCellValue方法获取

//数字格式调用getNumericCellValue方法获取

fields[j] = (String)(row.getCell(j).getCellType()==XSSFCell.CELL_TYPE_STRING?

row.getCell(j).getStringCellValue():""+row.getCell(j).getNumericCellValue());

}

//将fields的数据对象存储到records的List中

records.add(fields);

}

}

//定义函数返回值,即Object[][]

//将存储测试数据的List转换为一个Object的二维数组

Object[][] results = new Object[records.size()][];

//设置二维数据每行的值,每行是一个Object对象

for (int i = 0; i < records.size(); i++) {

results[i] = records.get(i);

}

return results;

}


5、返回Excel测试数据文件最后一列的列号

最后一列用来写入用例执行结果


public static int getLastColumnNum(){

//返回Excel测试数据文件最后一列的列号,如果有11列,则结果返回10

return ExcelWSheet.getRow(0).getLastCellNum()-1;

}



十、添加联系人测试类

在cn.wangxy.testScripts包下新建测试类TestMail126AddContactPerson,用来编写用户登录邮箱之后在通讯录中添加联系人,同时,测试的数据都来自于Excel文件,执行结果也写入Excel文件最后一列。


public class TestMail126AddContactPerson {

public WebDriver driver;

//调用Constant类中的常量

String baseUrl = Constant.Url;

//定义dataprovider,并命名为“testData”

@DataProvider(name = "testData")

public static Object[][] data() throws IOException{

/*

* 调用ExcelUtil类中的getTestData静态方法,获取excel文件中倒数第二列标记为y的测试数据行,

* 函数参数为常量TestDataExcelFilePath和TestDataExcelFileSheet,指定测试文件的路径和

* sheet名称

*/

return ExcelUtil.getTestData(Constant.TestDataExcelFilePath, Constant.TestDataExcelFileSheet);

}

//使用名称为“testData”的dataProvider作为测试方法的测试数据集

//测试方法一共使用10和参数,对应exce文件第1~10列

@Test(dataProvider = "testData")

public void testAddressBook(String CaseRowNumber,String testCaseName,String maliUserName,

String mailPassWord,String contactPersonName,String contactPersonEmail,

String contactPersonMobile,String assertContactPersonName,

String assertContactPersonEmail,String assertContactPersonMobile) throws Exception {


driver.get(baseUrl);

//使用变量maliUserName、mailPassWord,contactPersonName,contactPersonEmail,contactPersonMobile作为添加联系人动作的参数

try {

AddContactPerson_Action.execute(driver, maliUserName, mailPassWord, contactPersonName, contactPersonEmail, contactPersonMobile);

} catch (AssertionError error) {

System.out.println("添加联系人失败");

/*

* 执行AddContactPerson_Action类的execute方法失败时,catch语句会捕获AssertionError

* 类型的异常,并设置其测试执行结果失败,由于excel文件中的序号格式被默认设定为带有一位小数,

* 所以使用split("[.]")[0]语句获取序号的整数部分,并传给setCellData函数,在对应序号

* 的测试数据行的最后一列设定为“测试执行失败”

*/

ExcelUtil.setCellData(Integer.parseInt(CaseRowNumber.split(".")[0]), ExcelUtil.getLastColumnNum(), "测试执行失败");

//调用Assert类的fail方法将此测试用例设定为执行失败,后续代码将不再执行

Assert.fail("执行AddContactPerson_Action类的execute方法失败");

}

Thread.sleep(3000);

try {

Assert.assertTrue(driver.getPageSource().contains(assertContactPersonName));

} catch (AssertionError error) {

ExcelUtil.setCellData(Integer.parseInt(CaseRowNumber.split("[.]")[0]), ExcelUtil.getLastColumnNum(), "测试执行失败");

Assert.fail("断言通讯录的页面是否包含联系人姓名的关键字失败");

}


try {

Assert.assertTrue(driver.getPageSource().contains(assertContactPersonEmail));

} catch (AssertionError error) {

ExcelUtil.setCellData(Integer.parseInt(CaseRowNumber.split("[.]")[0]), ExcelUtil.getLastColumnNum(), "测试执行失败");

Assert.fail("断言通讯录的页面是否包含联系人邮箱的关键字失败");

}

try {

Assert.assertTrue(driver.getPageSource().contains(assertContactPersonMobile));

} catch (AssertionError error) {

ExcelUtil.setCellData(Integer.parseInt(CaseRowNumber.split("[.]")[0]), ExcelUtil.getLastColumnNum(), "测试执行失败");

Assert.fail("断言通讯录的页面是否包含联系人手机号的关键字失败");

}

ExcelUtil.setCellData(Integer.parseInt(CaseRowNumber.split("[.]")[0]), ExcelUtil.getLastColumnNum(), "执行成功");

}

@BeforeMethod

public void beforeMethod() {

//加载Firefox驱动程序

System.setProperty("webdriver.gecko.driver","D:\\wangxiaoyu\\geckodriver.exe");

//打开Firefox浏览器

driver=new FirefoxDriver();

driver.manage().window().maximize();

driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);

}


@AfterMethod

public void afterMethod() {

driver.quit();

}


@BeforeClass

public void BeforeClass() throws Exception{

//使用Constant类中的常量,设定测试数据文件的文件路径和测试数据所在的sheet名称

ExcelUtil.setExcelFile(Constant.TestDataExcelFilePath,Constant.TestDataExcelFileSheet);

}

}

十一、执行结果

打开Excel测试数据文件,我们可以看到序号为1和3的测试数据行的最后一列均显示“执行成功”,序号为2的测试数据行的最后一列依旧显示“/”,表示此数据行并未被测试方法调用。Excel测试数据文件的具体内容如图:

十二、数据驱动测试框架优点

(1)通过配置文件,实现页面元素定位表达式和测试代码的分离;

(2)使用ObjectMap方式,简化与页面元素定位相关的代码量;

(3)使用PageObject模式,封装了网页中的页面元素,方便测试代码调用,也实现了一处维护、全局生效的目标。

(4)在cn.wangxy.appModules的包下封装了常用的页面对象操作方法,简化了测试脚本编写的工作量。

(5)在Excel文件中定义多个测试数据,测试框架可自动调用测试数据完成数据驱动测试。

(6)在Excel文件的测试数据中,通过设定“测试数据是否执行”列的内容为“y”或者“n”,可自定义选择测试数据。测试执行结束后会在“测试结果”列中显示测试执行的结果,方便测试人员的查看。

十三、感悟

自学自动化测试其实真的挺累的,但是请不要放弃,坚持下去,一定会成功的,照着书本或者视频上一步一步的练习,我目前就是这样的,不过每一步都要扎实,要不然就会纯粹的浪费时间。我上面代码也是照着书上练习,后来都理解了,代码跑通之后,再写出来的,想着以后即使是参考的话也会有很大的用处。

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值