什么是数据驱动测试,关键字驱动测试框架

&&国之画&&&& &&
版权所有 京ICP备号-2
迷上了代码!工具类服务
编辑部专用服务
作者专用服务
一种关键字驱动的UI自动化测试框架的设计与实现
在整个软件测试过程中,由于UI在提升用户体验方面的特殊作用,UI级别的测试不但在于验证系统的正确性和有效性,而且在验证整个系统的易用性、行为一致性和稳定性方面有着非常重要的作用。在敏捷开发的项目中,最优先要做的是通过尽早的、持续的交付有价值的软件来使客户满意,即使到了开发的后期,也欢迎改变需求。因此,开发一种能够适应项目快速变化的轻量级的UI自动化测试框架是非常必要的。  
为了更好地实现敏捷开发项目的UI自动化测试,本文利用微软的UI自动化技术实现了一个轻量级的关键字驱动的UI自动化测试框架。在.NET平台上,我们可以利用UI Automation的技术通过编写代码来操作UI对象或者获得UI的信息,从而实现UI的自动化。关键字驱动测试是数据驱动测试的一种改进型,用关键字的形式将测试逻辑封装起来,测试框架只要能够解释这些关键字即可对其应用自动化。  
本文实现的UI自动化测试框架包含测试用例执行程序QuickTest,以及基于QuickTest的测试用例集成开发管理工具QuickTestStudio。测试人员使用该测试框架可以高效地完成各种项目的UI自动化测试。  
最后,文章说明了使用该测试框架实现一个具体项目的自动化冒烟测试的方法和步骤,以及应用自动化测试后所取得的成果。  
学科专业:
授予学位:
学位授予单位:
导师姓名:
学位年度:
本文读者也读过
相关检索词
万方数据知识服务平台--国家科技支撑计划资助项目(编号:2006BAH03B01)(C)北京万方数据股份有限公司
万方数据电子出版社关键字驱动测试模式初探
关键字驱动测试模式初探
曾经在“”
这篇文章中论述过,“测试效率的提高关键是测试手段的改进”。尤其在软件测试领域,没有千遍一律的测试方法,别人都说好的商业工具拿到你产品线来却未必合
适。没有最好只有更好,如何才能产出符合淘宝框架的特色测试工具呢?之前在入淘宝之初,对淘宝架构、测试工具不甚熟悉的情况下,提出过《》一文,但却与淘宝现有的测试工具不相符合。随着对淘宝环境逐渐熟悉,一直都在思考改进测试的方法,这种方法一定要以现在使用的ITEST为基础,在经过不断地实践摸索以后,结合自己的经验,提出以下测试理论,望大家参详。
一、概念提出
在阐述我的观点之前,先来看看下面的例子。
在ITEST中,订购一个套餐的用例代码如下所示:
/*****************************************代码分割线*****************************************/
public class PlanSubTest extends BaseCase{
&&&&&&&& final static String NICK= "leizang_test";
&&&&&&&& final static String PASS_WORD= "taobao1234";
&&&&&&&& @BeforeClass
&&&&&&&& public static void login(){
&&&&&&&&&&&&&&&&&& command.login(LOGIN_URL, NICK, PASS_WORD);
&&&&&&&& }
&&&&&&&& @AfterClass
&&&&&&&& public static void loginOut(){
&&&&&&&&&&&&&&&&&& command.loginOut();
&&&&&&&& }
&&&&&&&& @Before
&&&&&&&& public void cleanDB(){
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&& String nick= NICK;
&&&&&&&&&&&&&&&&&& command.dbExecute(
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& "DELETE FROM
upp_sub_plan WHERE nick= '"+ nick+ "'",
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& "DELETE FROM
upp_biz_order WHERE nick = '"+ nick+ "'",
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& "DELETE FROM
upp_prod_subscription WHERE nick = '"+ nick+ "'");
&&&&&&&& }
&&&&&&&& @Test
&&& && public void test_planSub_雷藏_case01(){
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&& logTestName();
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&& //构造入参
&&&&&&&&&&&&&&&&&& SubOption
subOption= new SubOption(getPlanSubUrl(827L),
CycleEnum.HALF_YEAR, false);
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&& //从页面订购
&&&&&&&&&&&&&&&&&& command.doSub(subOption);
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&& //结果校验
&&&&&&&&&&&&&&&&&& Command.checkSubResult(subOption,
TableEnum.UPP_BIZ_ORDER,
TableEnum.UPP_SUB_PLAN,
TableEnum.UPP_PROD_SUBSCRIPTION);
/*****************************************代码分割线*****************************************/
好了,虽然例子比较简单,但足以说明问题。
“command”是在“BaseCase”中生成的一个静态的“遥控器”(姑且这么理解):
“protected static ActionCommand command= new ActionCommandImpl(); “
就像我们的电视遥控器,空调遥控器一样,一旦你拥有了它,你就可以发出遥控器所支持的各种指令。所以,下面就理所当然地发出了各种“登录”,“退出”,
“清除数据库“,“订购”,“校验”等各种指令,而代码就会依照我们发出的指令去执行,这就是所谓“关键字驱动测试”理念。
二、测试建模
想一下,现在呈现在你面前的是一个万能机器人,而操控这个机器人的“遥控器“就在你手中,你按下”做饭“键,它会去做饭,你按下”洗衣“键,它会遵照你的
命令去洗衣服。但是”巧妇难为无米之炊“,更何况是个没有生命的机器人。你在发出”做饭“指令之前,需要事先给它准备好”米“和”水“,这样它才会按照你
预期的要求去做。当它完成任务的时候你需要去检查看看它完成的如何,饭做熟了没有。按照这种思路,我们对”指挥机器人做饭“的任务进行分解:
1)准备米和水
2)发出做饭指令
3)检查饭做好了没有
当你把这些跟上面的测试代码联系起来思考的时候,你会发现这一切是惊人的相似。在你对套餐订购进行测试的时候,你需要做如下几件事情:
1)准备相关数据
2)发出订购指令
3)校验订购结果
我们在编写测试用例的时候,如果能够方便地准备“入参“、”预期“,然后发出指令,代码就能自动地完成测试工作那该多好啊!
那如何才能实现我们这一套方便、智能系统呢?
聪明的你可能已经发现,要想达成愿望,关键在于解决以下三个难点:
1)相关数据准备方便(用户关心)
2)要有一个好的遥控器(用户不关心,制造商的事情)
3)要有一个能正确完成指令的机器人(用户不关心,制造商的事情)
这里存在对应关系:
用户& ——&自动化用例编写者
制造商——&测试框架搭建人员
我们先来解决制造商的两个困扰。
1、制造商困扰之一——遥控器问题
遥控器就是一个各种指令的集合。在这里涉及一个问题,“如何划分指令的粒度?”
比如说“登录”,可以划分为:
A.“获取登录页面”、“输入用户名”、“输入密码”、“提交”四个指令
也可以不进行划分
B.就一个“登录”指令,包含A中所有步骤,只是将“登录URL”,“用户名”,“密码”作为参数暴露
这里我倾向于B的分法,也就是说“将一个流程作为一个指令,将流程中所涉及的所有可变因素作为指令的参数暴露”。这样,我们只要对每个流程做好封装,以后就可以一劳永逸地重复使用它。
从技术的角度来看,我们可以定义一个接口,并将可供用户使用的指令放置其中。代码如下:
/*****************************************代码分割线*****************************************/
&* @author leizang.cs
public interface ActionCommand {
&&&&&&&& /**
&&&&&&&& &* 用户登录
&&&&&&&& &* @param url&& &&&&登录url
&&&&&&&& &* @param nick&&&&& 用户名
&&&&&&&& &* @param passWord& 密码
&&&&&&&& &*/
&&&&&&&& public void login(String url, String nick,
String passWord);
&&&&&&&& /**
&&&&&&&& &* 退出
&&&&&&&& &*/
&&&&&&&& public void loginOut();
&&&&&&&& /**
&&&&&&&& &* 执行订购
&&&&&&&& &* @param subOption 订购入参
&&&&&&&& &*/
&&&&&&&& public void doSub(SubOption subOption);
&&&&&&&& /**
&&&&&&&& &* 订购成功后校验数据库
&&&&&&&& &* @param dbCheckOption&&&& 校验入参
&&&&&&&& &* @param needCheckedTables 需要校验的表格
&&&&&&&& &*/
&&&&&&&& public void checkSubDB(SubDbCheckOption
dbCheckOption, TableEnum...needCheckedTables);
&&&&&&&& /**
&&&&&&&& &* 数据库修改或删除
&&&&&&&& &* @param sql 需要执行的sql
&&&&&&&& &*/
&&&&&&&& public void dbExecute(String... sqls);
/*****************************************代码分割线*****************************************/
这样我们第一个问题就解决了。下面来看第二个问题。
机器人可以正确执行遥控器发出的各种指令。从技术的角度说就是要求测试框架搭建人员,正确、稳定地实现遥控器中的各种指令。至于如何实现,这跟具体的产品线功能有关,这里仅给出我实现的部分代码,仅供参考:
/*****************************************代码分割线*****************************************/
public class ActionCommandImpl implements ActionCommand{
&&&&&&&& private WebDriver driver;
&&&&&&&& private JdbcTemplate jdbc;
&&&&&&&& @Override
&&&&&&&& public void dbExecute(String... sqls){
&&&&&&&&&&&&&&&&&& for(String sql: sqls){
&&&&&&&&&&&&&&&&&&&&&&&&&&& jdbc= CommonUtil.getJdbcFromSql(sql);
&&&&&&&&&&&&&&&&&&&&&&&&&&& jdbc.execute(sql);
&&&&&&&&&&&&&&&&&& }
&&&&&&&& }
&&&&&&&& @Override
&&&&&&&& public void login(String url, String nick,
String passWord){
&&&&&&&&&&&&&&&&&& try{
&&&&&&&&&&&&&&&&&&&&&&&&&&& driver= new HtmlUnitDriver();
&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&& driver.get(url);
&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&& WebElement
userName= driver.findElement(By.id("TPL_username_1"));
&&&&&&&&&&&&&&&&&&&&&&&&&&& userName.sendKeys(nick);
&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&& WebElement
passWd= driver.findElement(By.name("TPL_password"));
&&&&&&&&&&&&&&&&&&&&&&&&&&& passWd.sendKeys(passWord);
&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&& WebElement
submit= driver.findElement(By.className("J_Submit"));
&&&&&&&&&&&&&&&&&&&&&&&&&&& submit.click();
&&&&&&&&&&&&&&&&&& }finally{
&&&&&&&&&&&&&&&&&&&&&&&&&&& writePage();
&&&&&&&&&&&&&&&&&& }&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&& }
&&&&&&&& @Override
&&&&&&&& public void loginOut(){
&&&&&&&&&&&&&&&&&& driver.quit();
&&&&&&&& }
&&&&&&&& /**
&&&&&&&& &* @dscription 订购接口
&&&&&&&& &* @param subOption 订购参数
&&&&&&&& &* @throws ITestException
&&&&&&&& &*/
&&&&&&&& @Override
&&& && public void doSub(SubOption subOption)throws ITestException{
&&& &&&&&& if(subOption== null){
&&& &&&&&&&&&&&&&&&& Assert.fail("订购参数不能为空!");
&&& &&&&&& }
&&& &&&&&&
&&& &&&&&& String
subUrl= subOption.getSubUrl();
&&& &&&&&& CycleEnum
cycle= subOption.getCycle();
&&& &&&&&&
&&& &&&&&& log("传入参数为:");
&&& &&&&&& look(subOption);
&&& &&&&&&
&&& &&&&&& if(subUrl== null || subUrl.isEmpty()){
&&& &&&&&&&&&&&&&&&& Assert.fail("订购Url不能为空!");
&&& &&&&&& }
&&& &&&&&& if(cycle== null){
&&& &&&&&&&&&&&&&&&& Assert.fail("订购周期不能为空!");
&&& &&&&&& }
&&& &&&&&&
&&&&&&&&&&&&&&&&&& try{
&&&&&&&&&&&&&&&&&&&&&&&&&&& driver.get(subUrl);
&&&&&&&&&&&&&&&&&&&&&&&&&&& log("\n获取页面:"+ subUrl);
&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&& WebElement
period= null;
&&&&&&&&&&&&&&&&&&&&&&&&&&& switch(cycle){
&&&&&&&&&&&&&&&&&&&&&&&&&&& case ONE_MONTH:
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& period=driver.findElement(By.id("p-month"));
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& period.setSelected();
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& break;
&&&&&&&&&&&&&&&&&&&&&&&&&&& case ONE_SEASON:
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& period=driver.findElement(By.id("p-season"));
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& period.setSelected();
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& break;
&&&&&&&&&&&&&&&&&&&&&&&&&&& case HALF_YEAR:
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& period=driver.findElement(By.id("p-half"));
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& period.setSelected();
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& break;
&&&&&&&&&&&&&&&&&&&&&&&&&&& case ONE_YEAR:
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& period=driver.findElement(By.id("p-half"));
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& period.setSelected();
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& break;
&&&&&&&&&&&&&&&&&&&&&&&&&&& default:
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& Assert.fail("入参中周期值不合法!");
&&&&&&&&&&&&&&&&&&&&&&&&&&& }
&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&& WebElement
isAgree= driver.findElement(By.id("J_Agreement"));
&&&&&&&&&&&&&&&&&&&&&&&&&&& isAgree.click();
&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&& ((HtmlUnitDriver)driver).setJavascriptEnabled(true);
&&&&&&&&&&&&&&&&&&&&&&&&&&& String
js= "document.getElementById(\"J_PayMoney\").disabled
&&&&&&&&&&&&&&&&&&&&&&&&&&& ((HtmlUnitDriver)driver).executeScript(js);
&&&&&&&&&&&&&&&&&&&&&&&&&&& log("执行JS:"+ js);
&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&& WebElement
payMoney= driver.findElement(By.id("J_PayMoney"));
&&&&&&&&&&&&&&&&&&&&&&&&&&& String
prePayUrl= driver.getCurrentUrl();
&&&&&&&&&&&&&&&&&&&&&&&&&&& payMoney.click();
&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&& String
afterPayUrl= driver.getCurrentUrl();
&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&& if(!isPageSkip(prePayUrl,
afterPayUrl)){
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& throw new ITestException("订购失败!请查看"+ DIRECT+ "目录确认页面信息\n");
&&&&&&&&&&&&&&&&&&&&&&&&&&& }
&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&& WebElement
bd= driver.findElement(By.className("bd"));
&&&&&&&&&&&&&&&&&&&&&&&&&&& log("\n"+ bd.getText());
&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&& }catch(NoSuchElementException e1){
&&&&&&&&&&&&&&&&&&&&&&&&&&& throw new ITestException(e1);
&&&&&&&&&&&&&&&&&& }finally{
&&&&&&&&&&&&&&&&&&&&&&&&&&& writePage();
&&&&&&&&&&&&&&&&&& }
&&&&&&&& }
&&&&&&&& &/**
&&&&&&&& &*
&&&&&&&& &* @param dbCheckOption 数据库校验参数
&&&&&&&& &* @param checkedTables 需要校验的表
&&&&&&&& &*/
&&&&&&&& @Override
&&&&&&&& public void checkSubDB(SubDbCheckOption
dbCheckOption, TableEnum...needCheckedTables){
&&&&&&&&&&&&&&&&&& for(TableEnum table:
needCheckedTables){
&&& &&&&&&&&&&&&&&&& log("\n");
&&& &&&&&&&&&&&&&&&& switch(table){
&&& &&&&&&&&&&&&&&&& case UPP_BIZ_ORDER:
&&& &&&&&&&&&&&&&&&&&&&&&&&&& checkUppBizOrder(dbCheckOption);
&&& &&&&&&&&&&&&&&&&&&&&&&&&& break;
&&& &&&&&&&&&&&&&&&& case UPP_SUB_PLAN:
&&& &&&&&&&&&&&&&&&&&&&&&&&&& checkUppPlanSub(dbCheckOption);
&&& &&&&&&&&&&&&&&&&&&&&&&&&& break;
&&& &&&&&&&&&&&&&&&& case UPP_PROD_SUBSCRIPTION:
&&& &&&&&&&&&&&&&&&&&&&&&&&&& checkUppProdSubscription(dbCheckOption);
&&& &&&&&&&&&&&&&&&&&&&&&&&&& break;
&&& &&&&&&&&&&&&&&&& default:
&&& &&&&&&&&&&&&&&&&&&&&&&&&& Assert.fail("暂无此表校验逻辑:"+ table.name());
&&& &&&&&&&&&&& }
/*****************************************代码分割线*****************************************/
在这里我引入了JAVA的GUI测试技术。经过实践证明:
1)对WebDriver的使用不仅方便,而且执行快速,平均一个用例5S就能运行完成
2)更重要的是测试代码完全独立于开发代码,测试环境最接近真实的手工测试环境,用这种方法实现的自动化,只是模拟手工测试工作,并将其自动进行
3)指令正确实现以后,编写用例相当快捷方便,大大提高用例编写效率
4)脚本稳定、健壮且易于维护,只要页面不发生变化,对指令的实现就无需变化,大大降低维护成本
这样,上面提出的两个问题就解决了,我们编写出的代码就会像第一节所示的一样,只要准备好相关数据,发发指令就可以了。下面我们来解决用户的困扰。
3、用户的困扰——数据准备问题
记得上节划分指令粒度的时候我们是按流程来划分的吗?在这里它的好处就体现出来了。我们把一个流程作为一个指令,将流程中涉及的可变因素作为参数暴露,并
将指令在接口中定义,实现与定义分开,这样对于每一个指令来说,其参数个数是固定的,而且对于每条产品线来说指令的个数也比较有限。这非常有利于我们将其
“模板化”。说到模板化,大家自然会想到界面,于是就有三种方式进行模板化:“页面”,“软件客户端”,“eclipse插件”。我认为最简单、最方便的当属“eclipse插件。”下面我给出插件示意图:
最左边①是用例的目录树,当选中一条用例后第②部分为该用例的有序指令,第③部分为“指令池”,可以从中选择需要的“指令”。
这样我们编写用例就可以分为三步:
1、在①中新建一条用例并输入用例名称,此时第②部分应该为空
2、选择方法类型,有“@BeforeClass”,”@Before”,”@Test”,”@After”,”@AfterClass”五种选择,并从③中选择需要的“指令”
3、录入数据,双击“doSub”指令,此时弹出如下图所示的参数录入框,将数据填入其内并保存,保存后在eclipse中就会自动生成如第一节所列出的用例代码,其中“SUB_PLAN_URL”为poperties中定义的变量,也可以在界面中进行关联、维护。
由此可见,用户只和界面打交道,在此进行用例的增、删、改、执行操作。这样,用例设计人员专心设计场景与用例,测试框架维护人员维护自己产品线的框架,分工协作,效率大大提升。
好了,到此为止,我们所有的困难都解决了,下面给出该套测试框架的架构图。
三、测试架构
根据上面的论述,不难得出如下图所示的测试架构图:
“话说天下大势,分久必合,合久必分”,其不仅可用于WEB层测试,也可以用作HSF接口测试,也就是说我们的测试工程可以不再需要根据应用划分为很多个,只要这一套就可以通吃所有应用。
发表评论:
TA的最新馆藏[转]&[转]&[转]&[转]&您的位置: &
一种关键字驱动的Java API自动测试框架
优质期刊推荐

我要回帖

更多关于 关键字驱动自动化测试 的文章

 

随机推荐