首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 开源软件 >

Selenium用户指南 - 第七章 测试设计的思考[2]

2012-07-05 
Selenium用户指南 - 第七章 测试设计的考虑[2]?封装Selenium调用正如任何编程,你会想要使用实用函数去处理

Selenium用户指南 - 第七章 测试设计的考虑[2]

?

封装Selenium调用

正如任何编程,你会想要使用实用函数去处理,遍及你的测试的重复代码。一种防止重复代码的方式是,使用你自己设计的函数或类方法,封装频繁使用的调用。例如,许多测试在一个页面上,会频繁地点击一个页面元素,并等待页面装载。

selenium.click(elementLocator);
selenium.waitForPageToLoad(waitPeriod);

代替重复这段代码,你可以编写一个封装器方法执行这两个函数。

/**
* Clicks and Waits for page to load.
*
* param elementLocator
* param waitPeriod
*/
public void clickAndWait(String elementLocator, String waitPeriod) {
selenium.click(elementLocator);
selenium.waitForPageToLoad(waitPeriod);
}

元素呈现的“安全操作”

另外一个常用的封装Selenium方法是,在执行某些操作前,检查元素是否呈现。这有时被称为“安全操作”。例如,下面的方法可能被使用于实现一个安全操作,

/**
* Selenum-RC -- Clicks on element only if it is available on page.
*
* param elementLocator
*/
public void safeClick(String elementLocator) {
if(selenium.isElementPresent(elementLocator)) {
selenium.click(elementLocator);
} else {
// Using the TestNG API for logging
Reporter.log("Element: " +elementLocator+ ", is not available on page - "
+selenium.getLocation());
}
}

此示例使用Selenium 1 API,但Selenium 2 也支持。

/**
* Selenium-WebDriver -- Clicks on element only if it is available on page.
*
* param elementLocator
*/
public void safeClick(String elementLocator) {
WebElement webElement = getDriver().findElement(By.XXXX(elementLocator));
if(webElement != null) {
selenium.click(elementLocator);
} else {
// Using the TestNG API for logging
Reporter.log("Element: " +elementLocator+ ", is not available on page - "
+ getDriver().getUrl());
}
}

在第二个示例中"XXXX"仅仅是一个占位符,代表可以被调用的多个方法中的一个。

使用安全方法取决于测试开发者的判断。因此,如果测试执行需要继续,甚至在页面缺失元素的情况下,则可以使用安全方法,然而应该记录一个关于缺失元素的消息到日志。这基本上实现了一个带有报告的验证机制,而非一个退出的断言。但如果元素为了能够执行进一步的操作,必须在页面上是可得到的(例在一个门户站点的登录按钮),那么安全方法技术不应该被使用。

UI映射

一个UI映射是一个存储测试集的所有定位器到一个地方,为了当在一个AUT的UI元素的标识符或路径改变时,方便修改的机制。测试脚本然后使用这个UI映射用来定位要测试的元素。基本上,一个UI映射是一个测试脚本对象的仓库,对应于被测试应用程序的UI元素。

什么使得UI映射有用?它的主要目的是使得测试脚本管理更容易。当一个定位器需要被编辑时,有一个中心的位置用于容易地查找对象,而不是必须从头至尾搜索测试脚本代码。同样,它允许在一个单一的位置改变标识符,而不是不得不在多个地方做出改变,在一个测试脚本中,或者在多个测试脚本中处理。

简言之,一个UI映射有两个重大的优点:

- 为UI对象使用一个中心位置,代替让他们分散在脚本中。这使得脚本维护更有效。

- 含义模糊的HTML标识符和名称可以给于更可读的,改善测试脚本的可读性的名称。

考虑下面的,难以理解的示例(Java):

public void testNew() throws Exception {
selenium.open("http://www.test.com");
selenium.type("loginForm:tbUsername", "xxxxxxxx");
selenium.click("loginForm:btnLogin");
selenium.click("adminHomeForm:_activitynew");
selenium.waitForPageToLoad("30000");
selenium.click("addEditEventForm:_IDcancel");
selenium.waitForPageToLoad("30000");
selenium.click("adminHomeForm:_activityold");
selenium.waitForPageToLoad("30000");
}

对不熟悉AUT的页面源代码的任何人来说,这脚本可能是难以阅读的。甚至是应用程序的正式用户理解这样的脚本在做什么也有困难。一个更好的脚本可以是:

public void testNew() throws Exception {
selenium.open("http://www.test.com");
selenium.type(admin.username, "xxxxxxxx");
selenium.click(admin.loginbutton);
selenium.click(admin.events.createnewevent);
selenium.waitForPageToLoad("30000");
selenium.click(admin.events.cancel);
selenium.waitForPageToLoad("30000");
selenium.click(admin.events.viewoldevents);
selenium.waitForPageToLoad("30000");
}

现在,使用某些注释和空白以及UI映射标识符做出一个非常可读的脚本。


public void testNew() throws Exception {

// 打开应用程序URL

selenium.open("http://www.test.com");

// 提供管理用户名称

selenium.type(admin.username, "xxxxxxxx");

// 点击一个登录(Login)按钮

selenium.click(admin.loginbutton);

// 点击创建新事件(Create New Event)按钮

selenium.click(admin.events.createnewevent);
selenium.waitForPageToLoad("30000");

// 点击取消(Cancel )按钮

selenium.click(admin.events.cancel);
selenium.waitForPageToLoad("30000");

// 点击查看旧事件(View Old Events)按钮

selenium.click(admin.events.viewoldevents);
selenium.waitForPageToLoad("30000");
}

一个UI映射可以被实现,使用各种各样的方法。一个人可以创建一个类或结构,那个仅仅存储公共的字符串变量,每一个存储一个定位器。替代地,可以使用一个存储键值对的文件。在Java一个包含键/值对的属性文件可能是最佳的方法。

考虑一个属性文件prop.properties,为来自上一个示例的UI元素分配具有亲和力的标识符别名。

admin.username = loginForm:tbUsername
admin.loginbutton = loginForm:btnLogin
admin.events.createnewevent = adminHomeForm:_activitynew
admin.events.cancel = addEditEventForm:_IDcancel
admin.events.viewoldevents = adminHomeForm:_activityold

定位器会仍然引用HTML对象,但你已经在测试脚本和UI元素之间引入了一个抽象层。值读取自属性文件,并且使用在测试类,以实现UI映射。参考下面的链接,以获取关于Java属性文件的更多信息。

页面对象设计模式

页面对象是一个,用于增强测试的可维护性和减少代码重复,在测试自动化领域流行的设计模式。一个页面对象是一个面向对象的类,作为你的AUT页面的接口。当测试需要与页面的UI交互时,使用页面对象类的方法。好处是,如果页面UI改变,测试本身不需要改变,仅仅在页面对象中的代码需要改变。结果是,支持新UI的所有改变都位于同一个地方。

页面对象设计模式提供了下面的优点。

1、有一个测试代码和页面特定代码的清晰分离,出入定位器(或者定位器的恶使用,如果你使用UI映射)和布局。

2、有一个单一的页面提供的服务和操作的仓库,而不是让这些服务散布在测试中。

在两种情况下,这允许任何由于UI改变而需要的修改可以在一个地方做出。有关这项技术的有用信息可以在无数的blog中找到,因为这个测试设计模式正得到广泛地使用。我们鼓励希望了解更多的读者,在英特网上搜索有关这个主题的blog。很多人写过这个设计模式,并且可以提供超越本用户指南的有用技巧。为让你开始,我们使用一个简单的示例演示一个页面对象。

First, consider an example, typical of test automation, that does not use a page object.

首先,考虑一个示例,典型的测试自动化,没有使用页面对象。

/***
* Tests login feature
*/
public class Login {

public void testLogin() {
selenium.type("inputBox", "testUser");
selenium.type("password", "my supersecret password");
selenium.click("sign-in");
selenium.waitForPageToLoad("PageWaitPeriod");
Assert.assertTrue(selenium.isElementPresent("compose button"),
Login was unsuccessful");
}
}

使用这个方法有两个问题。

1、在测试方法和AUT的定位器(在这个示例中是id)之间没有分离;两者纠缠在一个方法中。如果AUT的UI改变它的标识符,布局,或者一个登录如何输入和继续,这测试本身必须改变。

2、id定位器散布在多个测试中,那些必须使用这个个登录页的所有测试中。

应用页面对象技术,这个示例可以重写,像下面的这个登录页的页面对象示例。

/**

* 页面对象封装登录页

public class SignInPage {

private Selenium selenium;

public SignInPage(Selenium selenium) {
this.selenium = selenium;
if(!selenium.getTitle().equals("Sign in page")) {
throw new IllegalStateException("This is not sign in page, current page is: "
+selenium.getLocation());
}
}

/**
* Login as valid user
*
* @param userName
* @param password
* @return HomePage object
*/
public HomePage loginValidUser(String userName, String password) {
selenium.type("usernamefield", userName);
selenium.type("passwordfield", password);
selenium.click("sign-in");
selenium.waitForPageToLoad("waitPeriod");

return new HomePage(selenium);
}
}

一个Home页面的页面对象可能像这个。

/**
* Page Object encapsulates the Home Page
*/
public class HomePage {

private Selenium selenium;

public HomePage(Selenium selenium) {
if (!selenium.getTitle().equals("Home Page of logged in user")) {
throw new IllegalStateException("This is not Home Page of logged in user, current page" +
+selenium.getLocation());
}
}

public HomePage manageProfile() {
// Page encapsulation to manage profile functionality
return new HomePage(selenium);
}

/*More methods offering the services represented by Home Page
of Logged User. These methods in turn might return more Page Objects
for example click on Compose mail button could return ComposeMail class object*/

}

现在登录测试可以使用这两个页面对象如下:

/***
* Tests login feature
*/
public class TestLogin {

public void testLogin() {
SignInPage signInPage = new SignInPage(selenium);
HomePage homePage = signInPage.loginValidUser("userName", "password");
Assert.assertTrue(selenium.isElementPresent("compose button"),
Login was unsuccessful");
}
}

在页面对象可以如何设计方面,有大量的灵活性,但有几个基本的规则,用于达成你的测试代码想要的可维护性。页面对象本身应该永远不要做验证和断言。这是你的测试部分,并且应该总是在测试代码中,永远不要在一个页面对象中。页面对象将包含页面的呈现,和页面通过方法提供的服务,但在页面对象中没有代码与即将被测试的内容相关。

有一个,唯一的,验证,可以和应该被写在页面对象里,那就是去验证那个页面,和可能的在页面上的关键元素,应被正确地装载。这个验证应该在页面对象实例化时完成。在上面的示例中,SignInPage和HomePage构造函数检查预期的页面是可得到的,而且已经准备好接受来自测试的请求。

一个页面对象不一定需要代表一个完整的页面。页面对象设计模式可以使用于代表在一个页面上的组件。如果在AUT的一个页面上有多个组件,每一个组件有一个分离的页面对象,这可以改善它的可维护性。

其他的设计模式也可以用于测试。有些人使用页面工厂实例化他们的页面对象。讨论这些设计模式超出了本用户指南的范畴。这里,我们仅仅希望引入这些概念,让读者意识到某些可以做的事情。正如较早提及的,很多人发布过有关这个主题的blog,我们鼓励读者查找有关这些主题的blog。

数据驱动的测试

数据驱动的测试指的是使用带有变化的数据的相同的测试(或多个测试)多次。这些数据集常常来自外部文件,诸如.cvs文件,文本文件,或可能装载自一个数据库。数据驱动的测试是一个常用的数据测试自动化技术,使用于验证面对许多变化的输入的应用程序。当测试是为变化的数据设计时,输入数据可以扩展,本质地创建附加的测试,而无需改变测试代码。

In Python:

这Phython脚本打开一个文本文件。该文件在每一行包含一个不同的搜索串。代码然后保存这个到一个字符串数组,并迭代通过这个数组,执行一个搜索和断言每个搜索串。

只是一个非常基本的示例,但这思想是去运行一个带有变化的数据的测试,可以被容易的完成,使用编程或脚本语言。为了更多的示例,参见Selenium RC wiki,从电子表格读取数据或使用TestNG的数据提供者能力的示例。此外,这是一个在测试自动化专业人士中众所周知的主题,包括那些不使用Selenium的,如此在Internet上可以找到许多有关这个“数据驱动测试”主题的blog

数据库验证

另外一个共同的测试类型是比较在UI中的数据与实际存储在AUT数据库中的数据。因为你也可以从一个编程语言做数据库查询,假定你有数据库支持函数,你可以使用它们去存取数据,然后使用这个数据去验证AUT显示的内容是正确的。

考虑这个取自数据库的注册邮件地址的示例,然后稍后与UI进行比较。一个建立一个DB连接,然后从DB存取数据的示例可能看起来像这样。

In Java:

// 装载Microsoft SQL Server JDBC driver。

Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");

// 准备连接url

String url = "jdbc:sqlserver://192.168.1.180:1433;DatabaseName=TEST_DB";

// 得到数据库连接

public static Connection con =
DriverManager.getConnection(url, "username", "password");

// 创建可以使用于编写DDL和DML SQL语句的语句对象

public static Statement stmt = con.createStatement();

// 通过Statement.executeQuery方法发出SQL SELECT语句到数据库

// 返回一个包含请求信息,以及带有数据行的ResultSet对象


ResultSet result = stmt.executeQuery
("select top 1 email_address from user_register_table");

// 从“result”对象存取“email_address”值

String emailaddress = result.getString("email_address");

// 使用emailAddress值登录到应用程序

selenium.type("userID", emailaddress);
selenium.type("password", secretPassword);
selenium.click("loginButton");
selenium.waitForPageToLoad(timeOut);
Assert.assertTrue(selenium.isTextPresent("Welcome back" +emailaddress), "Unable to log in for user" +emailaddress)

这个简单的,从一个数据库存取数据的Java示例。


? Copyright 2008-2012, Selenium Project. Last updated on Feb 02, 2012.

?

热点排行