使用分层的Selenium框架进行复杂 Web 应用的自动测试
继续上文谷歌搜索的场景,通过实例来了解TestNG的用法与功能。
清单5.TestNG应用示例
@Parameters({"url","query-string","btn-id","txt-id","verify-String"})
@Test
public void testGoogle(String url,String queryString,String btnID,
String txtID,String verifyString){
selenium=new DefaultSelenium("localhost",4444,"*firefox",url);
selenium.start();
selenium.open("/");
selenium.type(txtID,queryString);
selenium.click(btnID);
selenium.waitForPageToLoad("30000");
verifyTrue(selenium.isTextPresent(verifyString));
selenium.stop();
}
上面的代码清单中,注释Parameters指定的参数在TestNG测试框架的配置文件testng.xml里有具体定义,如下所示:
清单6.testng.xml示例
不难想到,只要修改testng.xml中的参数值,能由输入参数驱动不同的测试用例。然而,仅仅在testng.xml中指定参数有很大的局限性,显然过多的参数会难以维护,无法井井有条地组织分属不同Test Cases的输入。在下文中,我们来解决这个问题。
回页首
基于Selenium的分层测试框架
作者在工作中,测试基于OSGi平台的多个插件。每个插件实现特有的功能,有多条测试路径需要覆盖,同时,各个插件之间又有共通之处,可以抽取某些部分进行复用。对此,我们假设这样的场景:分别在谷歌、百度和必应中搜索各种关键字,并在返回的结果页面中验证是否存在目标字符串。每个搜索引擎都可以视为一个待测的组件,分别为它们撰写Test Cases,并组织成一个Test Suite,用于执行测试。事实上,3个搜索引擎的测试由于同质性,还能够合并为一种测试,用不同的输入参数来指定所要测试的那个搜索引擎。这里视为三个组件,只是为了说明如何在Selenium+TestNG环境中组织多个测试模块。
自上而下地考虑,上段描述的测试场景能够进行分解。Test Suite包含三类Test Cases(谷歌、百度与必应),每类Test Cases的一个Test Case由若干可复用的Test Tasks组成,通过传入不同的参数,Test Task完成同质的不同行为。在Test Task之下,定义相关文件,包含待测试的Web页面元素的定位信息。因此,分层Selenium框架有三个层次:
appObjects——Web页面元素定位信息,如按钮与文本框等;
tasks——测试步骤中可复用的行为;
test cases——由tasks组成的测试用例。
Web元素locators定义与收集
Selenium根据XPath来定位Web元素,XPath的相关知识不属于本文的内容。前面例子中,在TestNG的配置文件testng.xml里定义文本框与按钮的locators,对于复杂的测试场景而言,这不是好的实践。因此,我们在appObjects层建立文件,将Web页面元素locators归入,便于维护使用。Selenium-IDE的Find功能适于完成这一步骤。文件googlePages.properties的内容如下:
清单7.locators文件示例
#define the keys and corresponding XPaht locators of google page.
googleSearchTxtField=//input[@name='q']
googleSearchBtn=//input[@name='btnG']
这时,在testng.xml中,删去locators相关的parameters,只需要解析.properties文件,生成locators的properties备用。在所附的源码中可以看到.properties文件的解析器PropUtils的简单实现。
测试任务分解与实现
为说明任务分解,以简单的搜索过程为例,可以分为输入搜索关键字、点击搜索按钮、以及验证结果页面。实际代码如下所示,不难发现,由参数决定行为方式的测试任务,都接受一个paraMap数据结构,并根据其内容在方法内采取适当的行为。通过这种方式,test cases能够以参数配置文件来驱动测试任务实施其想要的行为。
清单8.Test Task代码示例
public void openSite(){
selenium.open("/");
}
public void typeSearchTxtField(HashMap paraMap){
utils.waitForElement((String)elemMap
.get(TestGoogleConstants.GOOGLE_SEARCH_TXT_FIELD),30);
selenium.type((String)paraMap
.get(TestGoogleConstants.GOOGLE_SEARCH_TXT_FIELD),
(String)elemMap
.get(TestGoogleConstants.GOOGLE_SEARCH_TXT_FIELD));