自动化测试模型

第6章 自动化测试模型

在介绍自动化测试模型之前,我们试着来解释自动化测试库、框架和工具之间的区别。

库的英文单词叫Library,库是将代码集合成的一个产品,供程序员调用。面向对象的代码组织形式而成的库也叫类库。面向过程的代码组织形式而成的库也叫函数库。所以从这个角度来看,我们在第四章是所介绍的WebDriver 就属于库的范畴,因为它提供了一组操作Web 页面的类与方法,所以,我们可以称它为Web 自动化测试库。

框架的英文单词叫Framework,框架则是为解决一个或一类问题而开发的产品,用户一般只需要使用框架提供的类或函数,即可实现全部功能。所以从这个角度来理解unittest 就框架,它主要用于实现测试用例的组织和执行,以及测试结果的生成。因为它的主要任务就是帮助我们完成测试工作,所以我们通常把它叫做单元测试框架。

工具的英文单词叫Tools,在笔者看来工具与框架所做的事情类似,只是工具会有更高的抽象,屏蔽了底层的代码,一般会提供单独的操作界面供用户操作。例如,Selenium IDE 和QTP 就是自动化测试工具。

回到自动化测试模型的概念上,笔者认为自动化测试模型可以看做是自动化测试框架与工具设计的思想。随着自动化测试技术的发展,演化以下几种模型:线性测试、模块化驱动测试、数据驱动测试和关键字驱动测试。

6.1 自动化测试模型介绍

下面分别介绍这几种自动化测试模型的特点。

6.1.1 线性测试

通过录制或编写对应用程序的操作步骤产生相应的线性脚本,每个测试脚本相对独立,且不产生其它的依赖与调用,这也是早期自动化测试的一种形式:它们其实就是单纯地来模拟用户完整的操作场景。在本书第4章所编写的测试脚本就属于线性测试,如图5.1 所示。

这种模型的优势就是每一个脚本都是完整且独立的。所以,任何一个测试用例脚本拿出来都可以单独执行。

当然,缺点也相当明显,测试用例的开发与维护成本很高:

开发成本很高,测试用例之间可能会存在重复的操作,不得不为每一个用例去录制或编写这些重复的操作。例如每个用例中重复的用户登录和退出操作等。
维护成本很高,正是因为测试用例之间存着重复的操作,当这些重复的操作发生改变时,就需要逐一的对它们进行修改。例如登录输入框的定位发生了改变,就需要对每一个包含登录的用例进行调整。

6.1.2 模块化驱动测试

正是由于线性测试的缺陷非常明显,因此早期的自动化测试专家就考虑用新的自动化测模型来代替线性测试。做法也很简单,借鉴了编程语言中模块化的思想,把重复的操作独立成公共模块,当用例执行过程中需要用到这一模块操作时则被调用,这样就最大程度上消除了重复,从而提高测试用例的可维护性。

图6.2 模块化结构

如图5.2 所示,(需要说明的是,早期的自动化测试以工具为主,非图5.2 的代码形式。)模块化的结构就很好的解决线性结构的两个问题:

提高了开发效率,不用重复的编写相同的操作脚本。假如,已经写好一个登录模块,后续在测试用例在需要登录的地方调用即可。
简化了维护的复杂性,假如登录按钮的定位发生了变化,那么只需修改登录模块的脚本即可,对于所有调用登录模块的测试脚本来说不需要做任何修改。

6.1.3 数据驱动测试

虽然模块化驱动测试很好地解决了脚本的重复问题,但是,自化测脚本在开发的过程中还是发现了诸多不便。例如,现在我要测试不同用户的登录,首先用的是“张三”的用户名登录;下一个测试用例要换成“李四”的用户名登录。在这种情况下,还是需要重复的编写登录脚本,因为虽然登录的步骤相同,但是登录所用的测试数据不同。

于是,数据驱动测试的概念就为解决这类问题而被提出。从它的本意来解释,就是数据的改变从而驱动自动化测试的执行,最终引起测试结果的改变。这听上去的确是个高大上的概念,而在早期的商业自动化工具中,也的确把这一概念作为一个卖点。对于数据驱动所需要的测试数据,也是通过工具内置的Datapool 管理。

如图5.3 所示,数据驱动说的直白点就是数据的参数化,因为输入数据的不同从而引起输出结果的不同。

不管我们读取的是定义的数组、字典,或者是外部文件(excel、csv、txt、xml 等)都可以看作是数据驱动。它的目的就是实现数据与脚本的分离。

图6.3 通过脚本读取数据文件

这样做的好处理同样显而易见,它进一步增强了脚本的复用性。同样以登录为例,首先是重新设计登录模块,使可以接收不同的数据,把接收到的数据作为登录操作的一部分。这样就可以很好的适应了相同操作不同数据的情况。当指定登录用户是“张三”时,那么登录之后的结果就是“欢迎张三”;指定登录用户是“李四”,登录结果就显示“欢迎李四”。这就是数据驱动所希望达到的目的。

6.1.4 关键字驱动测试

理解了数据驱动后,无非是把“数据”换成“关键字”,通过关键字的改变引起测试结果的改变。
目前市面上典型关键字驱动工具以QTP(目前已更名为UFT - Unified Functional Testing)、Robot Framework(RIDE)工具为主。这类工具封装了底层的代码,提供给用户独立的图形界面,以“填表格”的形式避免测试人员对写代码的恐惧,从而降低脚本的编写难度,我们只需要使用工具所提供的关键字以“过程式”的方式来编写用例即可。

当然,Selenium 家族中的Selenium IDE 也可以看作是一种传统的关键字驱动的自动化工具。

上面的脚本由Selenium IDE 录制产生,它把每一个动作分为三部分:

做什么?例如打开、输入、点击等动作。
对谁做?通过定位方式找到要操作的对象。
如何做?例如输入框输入的内容为“selenium”等。

当然,关键字驱动技术也在不断发展和进步。下面以功能更为强大的关键字驱动测试框架Robot Framework为例,它也可以像编程一样写测试用例。

首先,定义a、b 两个变量,分别赋值2 和5;然后,通过run keyword if 关键字判断a 是否大于等于1,如果满足条件则通过log 输出“a 大于1”,否则继续判断b 是否小于等于5,如果满足条件则通过log 输出“b 小于等于5”。如果以上两个条件都不满足,则通过log 输出“上面两个条件都不满足”。

这个例子很好理解,for 循环i 从到1 到10,每循环一次通过log 输出i 的值。

通过Import Resource 关键字读取指定的外部文件。

通过有Import Libray 关键字引入外部文件。

关键字驱动也可以像写代码一样写用例,在编程的世界中,没有什么不能做,不过这样的用例同样需要较高的学习成本,与学习一门编程语言几乎相当。这样的框架越到后期越难维护,可靠性也会变差,关键字的用途与经验被局限在自己的框架内,你所学习的知识也很难重用到其它地方。所以,从测试人员的经验与技术积累价值来讲,笔者更倾向于直接通过编程的方式开发自动化脚本。

这里简单介绍了几种测试模型的发展过程与特点,虽然是从自动化测试模型的发展顺序逐一介绍的,但它们并非后者淘汰前者的关系。在实际自动化实施的过程中,应以项目需求为出发点,综合运用上述模型来开展自动化测试。

6.2 模块化实例

通过对自动化测试模型的介绍,我们了解到模块化设计的优点。本节我们就以具体的例子来介绍模块块的具体应用,当然,使用它的基础是Java 语言中的函数与类方法的调用。下面以126 邮箱为例。

mailLogin.java

package com.mypro.model;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

public class mailLogin {

public static void main(String[] args) throws InterruptedException{

System.out.println(“126 mail login.”);

WebDriver driver = new ChromeDriver();
driver.get(“http://www.126.com”);

//登录
WebElement xf =
driver.findElement(By.xpath("//*[@id=‘loginDiv’]/iframe"));
driver.switchTo().frame(xf);
driver.findElement(By.name(“email”)).clear();
driver.findElement(By.name(“email”)).sendKeys(“username”);
driver.findElement(By.name(“password”)).clear();
driver.findElement(By.name(“password”)).sendKeys(“password”);
driver.findElement(By.id(“dologin”)).click();
driver.switchTo().defaultContent();

Thread.sleep(5000);
System.out.println(“login success!”);

//登录之后的一些操作。
//…
//退出
driver.findElement(By.linkText(“退出”)).click();

driver.quit();

}
}

从126 邮箱业务流程分析,邮箱所提供的功能都需要登录之后进行,例如收信、写信、删除信件等操作。对于手工来说,测试人员在执行用例的过程中可以一次登录后验证多个功能后退出,但自动化测试的
执行有别于手测试的执行,需要保持测试用例的独立性和完整性,所以每一条用例在执行时都需要登录和退出操作。这个时候就可以把登录和退出的操作封装为公共函数。当每一条用例需要登录/退出时,只需调
用它们即可,从而消除代码重复,提高脚本的可维护性。

下面对登录和退出进行模块的封装。

mailLogin.java

package com.mypro.model;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

public class mailLogin {
//登录方法
public static void login(WebDriver driver){
WebElement xf =
driver.findElement(By.xpath("//*[@id=‘loginDiv’]/iframe"));
driver.switchTo().frame(xf);
driver.findElement(By.name(“email”)).clear();
driver.findElement(By.name(“email”)).sendKeys(“username”);
driver.findElement(By.name(“password”)).clear();
driver.findElement(By.name(“password”)).sendKeys(“password”);
driver.findElement(By.id(“dologin”)).click();
driver.switchTo().defaultContent();

}

//退出方法
public static void logout(WebDriver driver){
driver.findElement(By.linkText(“退出”)).click();

}

public static void main(String[] args) throws InterruptedException{

System.out.println(“126 mail login.”);

WebDriver driver = new ChromeDriver();
driver.get(“http://www.126.com”);

//登录
login(driver);

Thread.sleep(5000);

System.out.println(“login success!”);

//登录之后的一些操作。
//…
//退出

logout(driver);
driver.quit();

}

}

现在我们将登录的操作步骤封装到login()方法中,把退出的操作封装到logout()方法中,对于用例本身只用调用这两个函数即可,可以把更多的注意力放到本例本身的操作步骤中。
当然,如果只是把步骤封装成函数并没简便太多,我们需要将其放到单独的文件中供其它用例调用。

publicModel.java

package com.mypro.model;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class publicModel {
public static void login(WebDriver driver){
WebElement xf =
driver.findElement(By.xpath("//*[@id=‘loginDiv’]/iframe"));
driver.switchTo().frame(xf);
driver.findElement(By.name(“email”)).clear();
driver.findElement(By.name(“email”)).sendKeys(“username”);
driver.findElement(By.name(“password”)).clear();
driver.findElement(By.name(“password”)).sendKeys(“password”);
driver.findElement(By.id(“dologin”)).click();
driver.switchTo().defaultContent();
}
public static void logout(WebDriver driver){
driver.findElement(By.linkText(“退出”)).click();
}
}
当函数被独立到单独的文件中时做了一点调整,主要在函数的传参上,因为函数内部的操作需要使用driver,但是driver 并没有在此文件中定义,所以需要调用的用例传递driver 给调用的函数。

mailLogin.java

package com.mypro.model;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import com.mypro.model.publicModel; //导入公共模块

public class mailLogin {
public static void main(String[] args) throws InterruptedException{
System.out.println(“126 mail login.”);
WebDriver driver = new ChromeDriver();
driver.get(“http://www.126.com”);

//调用登录方法
publicModel.login(driver);
Thread.sleep(5000);
System.out.println(“login success!”);

//登录之后的一些操作。
//…
//调用退出方法

publicModel.logout(driver);
driver.quit();
}
}

首先,需要导入当前目录中的publicModel.java,在需要的位置调用文件中的login()和logout()方法。这样对于每个用例来说就简便了许多,也更易于维护。

6.3 数据驱动实例

前面提到关于数据驱动的形式有很多,我们既可以通过定义变量的方式进行参数化,也可以通过定义数组、字典的方式进行参数化,还可以通过读取文件(txt\csv\xml)的方式进行参数化。这一小节我们就
通过一些例子来展示数据驱动在自动化测试中的应用。

6.3.1 126 邮箱登录

同样以126 邮箱的登录为例,现在的需求是测试不同用户的登录。对于测试用例来说,不变的是登录的步骤,变化的是每次登录的用户名和密码不同,这种情况下就需要用到数据驱动方式来编写测试用例。基于前面的例子做如下修改。

修改publicModel.java 中的实现。

publicModel.java

……
//登录方法,参数化用户名、密码
public static void login(WebDriver driver, String username ,String password){
WebElement xf = driver.findElement(By.xpath("//*[@id=‘loginDiv’]/iframe"));
driver.switchTo().frame(xf);
driver.findElement(By.name(“email”)).clear();
driver.findElement(By.name(“email”)).sendKeys(username);
driver.findElement(By.name(“password”)).clear();
driver.findElement(By.name(“password”)).sendKeys(password);
driver.findElement(By.id(“dologin”)).click();
driver.switchTo().defaultContent();
}
……
dataDriver.java

package com.mypro.model;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import com.mypro.model.publicModel; //导入公共模块

public class dataDriver {

public static void main(String[] args) throws InterruptedException{

System.out.println(“126 mail login.”);
WebDriver driver = new ChromeDriver();
driver.get(“http://www.126.com”);
//登录方法
String username=“admin”;
String password=“123456”;
publicModel.login(driver,username,password);
Thread.sleep(5000);
System.out.println(“login success!”);
driver.quit();

}

}
首先创建login()方法,它需要三个参数,驱动、用户名和密码,在main()方法中调用login()方法,并且将相关数据传给它。login()拿到这些数据后做其做为测试脚本中的数据(用户名、密码)执行。

对于登录的用户名和密码,我们也可以将其存放到txt 文件中。

首先,创建info.txt 文件。

info.txt
testing;123456

接下来,通过Java 读取info.txt 文件。

dataDriver.java

package com.mypro.model;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

import com.model.demo.publicModel;

public class ReadTxt {

public static void main(String[] args) throws InterruptedException, IOException
{

WebDriver driver = new ChromeDriver();
driver.get(“http://www.126.com”);

File file = new File(“D:\javase\info.txt”);

BufferedReader bufread = new BufferedReader(new FileReader(file));
String lineTxt = null;
while ((lineTxt = bufread.readLine())!=null) {
//testingwtb;a123456 [“testing”,“123456”]
String username = lineTxt.split(";")[0];
String password = lineTxt.split(";")[1];
System.out.println(username);
System.out.println(password);

publicModel.login(driver,username,password);
Thread.sleep(8000);
/*……

  • 发邮件的操作
    */
    publicModel.logout(driver);
    driver.quit();

}

bufread.close();

}

}

这里Java 是按行来读取txt 文件的,那么读取的数据为“testing;123456”,Java 提供了split()方法可以将字符串拆分成两部分,并且放到数组中。此处,通过分号“;”做为分割点,“testing”和“123456”
进行分割,并放入数组;所以,取数组的[0] 为“testing”;取数组的[1] 为“123456”。
再接下来得到的用户名和密码作为login()方法的入参,从而实现用户的登录。

6.3.2 百度搜索
再来看一个百度搜索的例子。我们每天上网一般要用很多次百度搜索,而我们每次在使用百度搜索时步骤都是一样的,不一样的是每一次搜索的“关键字”不同。下面我们就以数组的方式对搜索的关键字进行参数化。

baiduData.java

package com.mypro.model;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

public class BaiduData {

static WebDriver driver;

public static String sreach(String text) throws InterruptedException{
driver.get(“https://www.baidu.com”);
driver.findElement(By.id(“kw”)).sendKeys(text);
driver.findElement(By.id(“su”)).click();
Thread.sleep(2000);
return driver.getTitle();
}

public static void main(String [] args) throws InterruptedException{

driver = new ChromeDriver();
//定义搜索关键字数组
String[] sreach_key = new String[3];
sreach_key[0] = “java”;
sreach_key[1] = “selenium”;
sreach_key[2] = “webdriver”;

for(int i=0;i<3;i++){
String text = sreach(sreach_key[i]);
System.out.println(text);

}

driver.close();

}
}

首先将百度搜索的步骤定义为sreach()方法,搜索的关键字作为方法的入参。

接下来在main()方法中定义字典sreach_key,然后通过for 循环读取字典的元组做为sreach()方法的入参对其进行调用。最后将sreach()方法返回的页面title 打印。

6.3.3 读取csv 文件

假如,现在要读取的是一组用户数据,这一组数据包括用户名、邮箱、年龄、性别等信息。这个时候显然txt 文件不管是存放还是读取都不太方便。

下面通过读取csv 文件的方法来解决这个每次要读取多个信息的问题。Java 本身并不支持CSV 文件的读取,需要安装第三方jar 包。

javacsv2.1 下载地址:http://sourceforge.net/projects/javacsv/files/

通过上面的链接,将jar 包下载并导入项目。具体步骤参考selenium 的导入。

首先创建userinfo.csv 文件,通过WPS 表格或Excel 创建表格,文件另存为选择CSV 格式进行保存,注意不要直接修改Excel 的后缀名来创建CSV 文件,这样创建出来的并非真正的CSV 文件。

下面修改readCVS.java 文件进行循环读取:

readCSV.java

package com.mypro.model;

import java.io.IOException;
import java.nio.charset.Charset;
import com.csvreader.CsvReader;

public class ReadCSV {
public static void main(String[] args) throws IOException {
String filePath = “D:\javase\info.csv”;
CsvReader reader = new CsvReader(filePath, ‘,’, Charset.forName(“GBK”));
reader.readHeaders(); // 跳过表头, 如果不需要表头的话,不要写这句。
while (reader.readRecord()) {
// 读取一条记录
System.out.println(reader.getRawRecord());
System.out.println("==========");
// 按列名读取这条记录的值
System.out.println(reader.get(“Name”));
System.out.println(reader.get(“Password”));
System.out.println("--------");
}
}
}
运行结果:

Console
testing,123456@126.com,23,男,123456

testing
123456

testing2,123456@qq.com,18,女,654321

testing2
654321

testing3,123456@163.com,29,女,333666

testing3
333666

我们一般会在表格的第一行定义标题,从而来说明每一列的内容,readHeaders()方法可以跳过表头,也就是表格的第一行。

getRawRecord()方法表示获取一行的数据。

get(“Name”)方法表示获取一行中某一列的数据,“name”为该的标题。在上面的例子中只需要取所有用户的“Name”和“Password”。

在本中是以读取CSV 文件为例,读取Excel 文件的方式也类似,只是所调用的模块就需要从csv 切换为xlrd,针对Excel 文件操作的方法也有所不同。

6.3.4 读取xml 文件

有些时候我们所需要读取的文件并没有固定的行和例,而是一些不规则的配置信息,例如我们需要一个配置文件来配置当前自动化测试脚本的URL、浏览器、登录用户名/密码等。这个时候可以选择XML文件来配置这些信息。

什么是XML?

XML 即可扩展标记语言,它可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。

有时候我们所需要读取的数据是不规则的。例如,我们需要一个配置文件来配置当前自动化测试脚本的URL、浏览器、登录的用户名和密码等,这个时候就可以考虑选择使用XML 文件来存放这些信息。

什么是XML 文件?我们在本书第1 章的1.6 节作过简单的介绍,所以这里不再赘述。

下面以读取info.xml 文件为例介绍读取XML 文件的方法。

info.xml

<?xml version="1.0" encoding="utf-8"?> 4 Java test Zope 获得任意标签名

readXML.java

import java.io.File;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
public class readXML {
public static void main(String arge[]) {
try {
File xmlFile = new File(“D:\jase\info.xml”);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(xmlFile);
NodeList ma = doc.getElementsByTagName(“maxid”);
NodeList ca = doc.getElementsByTagName(“caption”);
NodeList it = doc.getElementsByTagName(“item”);
System.out.println(ma.item(0).getNodeName());
System.out.println(ca.item(0).getNodeName());
System.out.println(it.item(0).getNodeName());
} catch(Exception e) {
e.printStackTrace();
}
}
}
运行结果:

Console

maxid
caption
item

getElementByTagName()可以通过标签名获取某个标签。它所获取的对象是以数组形式存放。如“caption”和“item”标签在info.xml 文件中有多个,那么可以指定数组的下标在获取某个标签。

root.getElementsByTagName(‘caption’) 获得的是标签为caption 一组标签;

item(0).getNodeName() 表示一组标签中的第一个。

item(2).getNodeName() 表示一组标签中的第三个。

获得标签的属性值

readXML.java

import java.io.File;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
public class readXML {
public static void main(String arge[]) {
try {
File xmlFile = new File(“E:\jase\info.xml”);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(xmlFile);
NodeList ma = doc.getElementsByTagName(“login”);
System.out.println(ma.item(0).getAttributes()
.getNamedItem(“username”));
System.out.println(ma.item(0).getAttributes()
.getNamedItem(“passwd”));
} catch(Exception e) {
e.printStackTrace();
}
}
}
运行结果:

Console

username=“pytest”
passwd=“123456”
getAttributes()方法可以获得元素的属性,getNamedItem()指定属性的名字。
获得标签对之间的数据

readXml.java

import java.io.File;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
public class readXML {
public static void main(String arge[]) {
try {
File xmlFile = new File(“E:\jase\info.xml”);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(xmlFile);
NodeList ma = doc.getElementsByTagName(“caption”);
System.out.println(ma.item(0).getTextContent());
System.out.println(ma.item(1).getTextContent());
System.out.println(ma.item(2).getTextContent());
} catch(Exception e) {
e.printStackTrace();
}
}
}
运行结果:

Console

Java
test
Zope
getTextContent()方法用于获取标签对之前的数据。

本章小结

在本章的学习过程中,我们首先介绍了自动化测试模型的发展,然后通过实例介绍了模块化的应用。

模块化和数据驱动在脚本开发过程中是必不可少的两个知识点,这也是开发出可复用和可维护的脚本的基础,希望读者灵活运行。在数据驱动小节里,分别介绍了不同形式的数据驱动,读者可以根据实际需求选择合适的数据驱动形式。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值