BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

借鉴ASP.NET的控件模型辅助UI自动化测试

| 作者 余昭辉 关注 1 他的粉丝 发布于 2012年5月1日. 估计阅读时间: 16 分钟 | QCon上海2018 关注大数据平台技术选型、搭建、系统迁移和优化的经验。

概述

在敏捷测试中UI的自动化测试(一般我们也称这层测试为功能测试或验收测试,本文单指Web UI的自动化测试)虽然没有单元测试那么广为提及,但因为其与最终用户最近,所以基于用户场景的UI自动化测试还是有其重要的意义的。使用UI自动化测试对产品的关键功能路径进行验证及回归,比起传统的QA手工执行Test case可以更快地得到反馈,也让发布变得更有信心。

理想状况下,我们应该将所有可以固化下来的Test case都自动化起来,而让我们的测试人员进行更有挑战性的探索性测试活动。让机器做已知领域的事儿,让人对未知领域进行探索。不过理想归理想,现实是残酷的。虽然UI层的测试距离交付最近,但是成本也最高。编写和维护UI自动化测试需要付出比其他自动化测试更高昂的成本,这也是大多数团队放弃UI自动化测试的主要原因。相比较系统的其他部分,UI是一个多变的层,如果UI自动化测试没有构建好,即使界面的一个微小改动,整个测试集可能就天崩地裂。这也就是为什么我经常对team里其他人说:对于UI自动化测试,可维护性必须牢记心头。每当你写下一行测试代码时,你就必须记住你又给公司添加了一笔成本,而且这个成本是持续增长的,如果review code的时候发现哪条测试代码维护性不好我会毫不犹豫的删掉。

或许有人觉得这有点小题大作,不就UI测试么,有什么难的。定位元素,然后拿到页面元素的值与期望进行比较不就可以了。难就难在定位元素上。一般我们会使用Selenium WebDriver, Watir, Sahi等工具驱动浏览器,进行元素定位(关于这些工具的详细使用可以参见官方文档,后文主要以Selenium WebDriver为示例)。这些工具在定位元素上基本上是大同小异:通过id, name, css, tagName, xpath等方式定位。这些定位方式,从前到后,一个比一个不靠谱。比如这个xpath,好不容易写出个xpath定位,然后突然有一天前端觉得某个地方不美观,插入一个小东西,马上测试废掉。看着这种没有改变功能也把功能测试搞垮掉的现象是不是欲哭无泪。我有时天真的在想如果页面上每一个元素都有唯一的id该多好啊。即使没有唯一的id,有name我也可以接受。不过这一切在遇到ExtJS之后都变了。

遭遇ExtJS

ExtJS是一个非常霸道的前端框架。使用ExtJS后,页面上几乎所有的一切都被ExtJS接管。尽管互联网提供给用户的系统鲜有使用ExtJS,但是对于后台系统使用ExtJS确实带来了一些便利。使用ExtJS的基本组件就能组装出一个看起来还不错,功能强大的应用。但是ExtJS非常霸道,被他接管后页面的生成基本上就是个黑盒子,而为了在各个浏览器的兼容它在各浏览器上生成的html还不一样。更可恨的是默认情况下它给元素提供的id都是动态生成的。

在刚选择这个ExtJS的系统作为我们自动化测试的第一个试点时,我还有点暗暗高兴。比起那些提供给普通用户使用的丰富多彩的前端来说,这些后台系统大多中规中矩,使用ExtJS后更是层次分明。而且后台系统UI的变动也不会太过于频繁,我想或许这个系统很容易测试吧。

后来我看到同事代码里出现:

webDriver.findElement(By.id("ext-gen-1306"))

我还在想,我们的前端同学真有“创意”,还用这么随机的名字啊。后来厄运来了,我check out代码在我这里死活通过不了。Selenium报告找不到指定元素。不是吧,我可是使用id进行定位的啊。通过翻阅ExtJS的文档发现,原来类似ext-gen-xxx这类id都是ExtJS动态生成的。好吧,我使用name进行定位吧,后来发现很多元素居然没有name属性。再来看看ExtJS生成的html,基本上把通过xpath进行定位的路给堵死了。要了解ExtJS生成的html,可以去ExtJS官方查看一些Demo。

曙光

阅读ExtJS文档我们发现,ExtJS极其强调它的组件模型。而用ExtJS写的前端代码也呈现出很好的结构。因为之前曾从事过ASP.NET的开发,我想是不是可以使用ASP.NET类似的方式先编写一些小控件类,这些类对ExtJS的基本组件进行包装。然后利用这些小控件类组装出一个个页面。这样不仅能把单个元素的定位分散到单个控件类里,而且可以做到极大程度的复用。在传统的UI自动化测试中我们使用Page Object模式来封装一个个页面,但是对于ExtJS来讲页面的粒度还显得过大。如是模仿ASP.NET的控件模型,我创建了Control, Button, TextBox等一系列基本的控件类。而原来Page Object中的Page不再使用WebDriver直接定位元素了,我们通过这些基本控件组装页面。

实现

在这里我用一个简单的用户登录作为例子:

Control是我们的基本类型,所有的控件包括页面都从这个类派生。

Control只提供了很少几个方法:

public abstract class Control {
    protected WebDriver webDriver;


    protected Control parent;


    public Control(WebDriver webDriver) {
        this.webDriver = webDriver;
    }


    public String getQuery() {
        return StringUtils.EMPTY;
    }


    public String getId() {
        JavascriptExecutor executor = (JavascriptExecutor) webDriver;
        return (String) executor.executeScript("return " + this.getQuery() + ".id");
    }
}

在这里getQuery是一个非常重要的方法,这在后面会介绍。

public abstract class CompositeControl extends Control {
    protected List <Control> children;


    public CompositeControl(WebDriver webDriver) {
        super(webDriver);
        children = new ArrayList<Control> ();
    }


    public void addChild(Control control) {
        this.children.add(control);
        control.parent = this;
    }
}

所有的可以包含其他控件的类型都从CompositeControl派生,包括Page。比如下面的Window就是这类元素:

public class Window extends CompositeControl {
    private String title;


    public Window(String title,WebDriver webDriver) {
        super(webDriver);
        this.title = title;
    }


    @Override
    public String getQuery(){
        return String.format("Ext.ComponentQuery.query(\"window[title='%s']\")[0]",title);
    }
}

下面是一个基本控件Button的封装:

public class Button extends Control {
    private String text;


    public Button(String text, WebDriver webDriver) {
        super(webDriver);
        this.text = text;
    }


    @Override
    public String getQuery() {
        return this.parent.getQuery() + String.format(".query(\"button[text='%s']\")[0]", text);
    }


    public void click() {
        webDriver.findElement(By.id(getId())).click();
    }
}

ExtJS提供了一个query接口,我们可以利用这个接口传入一些查询表达式查询到页面上的Ext控件,而这里的getQuery就是每个控件的查询表达式吧。因为页面上的ExtJS控件是层次的,所以我们可以利用这种嵌套关系进行精确的定位。

好了,来看看我们的登陆页面如何封装吧:

public class LoginPage extends ExtJSPage{
     public LoginPage(WebDriver webDriver){
          super(webDriver);
     }


     private TextBox txtUserName;
     private TextBox txtPassword;
     private Button btnLogin;
     
     @Override
     protected void init(){
          txtUserName = new TextBox("userName", webDriver);
          txtPassword = new TextBox("password", webDriver);
          btnLogin = new Button("登录", webDriver);


          Window win = new Window("登陆", webDriver);
          win.addChild(txtUserName);
          win.addChild(txtPassword);
          win.addChild(btnLogin);


          this.addChild(win);
     }


     public void login(String userName, String password){
          txtUserName.setValue(userName);
          txtPassword.setValue(password);
          btnLogin.click();
     }
}

上面的TextBox和ExtJSPage没有提供代码,都很简单可以自行进行封装一下(熟悉ASP.NET的同学可能对这里代码有点眼熟)。

按照这种思路,只要我们封装好所有的基本ExtJS控件,对于所有的页面我们剩下的工作就是组装的工作了。在完成这些之后,我甚至发现使用ExtJS的应用比那些没有使用ExtJS的应用更容易进行测试。在这里我们只需要完善我们的基本控件封装就可以让我们的测试更佳稳固,而对于编写测试的人来说只需要集中精力关注Test case。

下图是目前我们已经实现的一些控件,每个控件实现起来都非常简单,每个控件只需要关注自己的查询表达式和自己应该提供什么方法。但是所有这些基本控件组装起来威力却很大。

扩展

后来我们发现ExtJS应用大多有很丰富的表单,一个表单填写页通常有几十个输入项,即使使用这种组装的方式比传统的使用findElement一个个定位来得快,但也非常繁琐。我们如是更进一步,建立一些FormModel(这里的form model的意思就是建立数据到表单元素之间的映射),对于这些表单的填写我们甚至不用编写组装代码了,只需要关注那几个FormModel就ok了。我们还给这些输入控件加上了验证错误,验证默认值等方法。

感谢前同事 @咖啡屋的鼠标 ,是我偷窃了你的创意:ExtJS的UI测试不应该使用Page Object pattern,应该使用组件模型。才让我可以这么简单的来实现这个测试。


感谢张凯峰对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ)或者腾讯微博(@InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

有点意思,顶一个 by 李 韩飞

有点意思,顶一个
不过Ext官方到底是如何测试的呢,一直没搞明白

webDriver.findElement(By.id("ext-gen-1306"))不能用xpath (//input [start-with]) by victor cai

webDriver.findElement(By.id("ext-gen-1306"))不能用xpath (//input [start-with(@id,"ext-gen-")])么

Ext应该好好想想怎么让人们进行更加友好的测试 by Wang Lei

最近试用了Sencha。我遇到的最大问题就是调试和测试。因为JS都是动态取到的,结果chrome根本无法调试。也没有任何地方能找到单元测试和调试的资料。报错也无法知道真正的原因。

Re: 有点意思,顶一个 by 张 鑫

记得在官方论坛的人说过,没有特定的工具去搞测试,估计他们就算是搞的话,也是搞得比较晚,,

Re: webDriver.findElement(By.id( by 余 昭辉

这个根本无法定位啊,页面上成百上千的都是ext-gen这样的id

从面临的情况得到的另外的一个启示 by lee laoya

最后没有如题告诉如何进行自动化测试,而是得出如何组装界面

Re: 从面临的情况得到的另外的一个启示 by 余 昭辉

在web ui自动化测试中,最难的地方就是定位页面元素,页面元素如果很好定位其他的就容易了。
当然,本文有一些假设:假设读者了解如何使用selenium进行web ui自动化测试,假设读者了解这种自动化测试中常用的Page Pattern,这个应该在文章开始的时候提一下,抱歉。

学习了 by sfsdfsdfsd sfssdfsd

写的很好。有空可以去IT社区论坛:www.itsqe.com/ 多交流下,诚邀您的光临。

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

8 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT