写点什么

使用 Selenium 测试时必需知道的 7 件事

  • 2015-07-31
  • 本文字数:3658 字

    阅读完需:约 12 分钟

Selenium 是一套用于进行浏览器自动化测试的开源工具集,可进行 Web 应用的端到端测试。Selenium 主要包括两个工具:一是 Selenium IDE,这是一个在 Firefox 上运行的插件,可对用户的行为进行录制与回放,还可以将录制的内容生成代码后在 Selenium Remote Control 上运行。二是本文的重点 Selenium WebDriver(简称 WebDriver),这是一个开源的项目,能够让用户编写在各种主流浏览器上运行的互操作代码。目前已经推出了支持 C#、Java 等语言的类库。 W3C 的 WebDriver 规范也正是在这个开源项目的基础上发展起来的。

WebDriver 可谓 QA 工程师进行 UI 测试最强大的利器,它提供了丰富的 API 以实现访问 DOM、运行 JavaScript、模拟键盘输入等操作。利用 WebDriver 进行编程可实现 UI 测试的完全自动化,为回归测试、乃至持续集成流程提供了极大的便利性。尽管如此,但使用 WebDriver 编写测试需要投入大量的时间,并且由于浏览器行为的多样性,以及 UI 的易变性,需要进行大量的代码维护工作。与应用程序的代码一样,编写测试代码同样需要遵循良好的代码规范与设计,糟糕的代码结构会很快使得测试代码的维护变成一个无底洞,最终被团队无奈地抛弃。

在今年的 OpenWest 2015 大会上,来自 Lucidchart 的 Jared Yarn 进行了一场关于 Selenium WebDriver 测试方面的演讲,并随后撰文总结了演讲的内容。他首先谈起了所在的团队在使用 WebDriver 时所遇到的困境,当时他们维护着由大约 40 个不同开发者编写的 300 多个测试用例(该团队没有专职的测试人员,测试代码全部由开发者编写),每天的运行都会产生 70 个左右的错误,这一情况在分配了专门的维护人员之后也没有多少改善。为了彻底改进测试集的可靠性、可伸缩性以及可维护性,Yarn 与整个团队一起对整个测试代码结构进行了重构。经过重构后,误判的失败率降到了 1% 以下,并且编写测试的时间也大大缩短了。

Yarn 将这次重构的成功归结为以下七点。

创建 Application User 对象

团队首先要解决的问题是编写测试所需投入的精力过大,为了克服这一点,他们设计了一些实体对象。首先创建的是一种 Application User 对象,它代表了网站的后端功能,并且通过一些辅助方法提供了准备测试场景、或是在测试完成前进行 teardown(清理)工作的功能。以下是使用这种对象的一个示例:

复制代码
class EditorPerformanceTest extends LucidSpec {
val user = new ChartUser
override def beforeAll() {
user.login()
user.createDocument()
}
override def afterAll() {
user.finished()
}

通过这种对象的应用,所有的准备工作被简化成两个方法调用(login 与 createDocument),而 teardown 中的逻辑则由 finished 方法实现,因此开发者可以专注于具体的测试逻辑,将精力集中在 bug 修复或特性的检测。

创建 Application Driver 对象

WebDriver 的 API 非常丰富,单是定位某个 UI 元素就有不下 20 种做法,这种巨大的灵活性也令人望而生畏。有数之不尽的方式可以完成拖放、单击、滚动以及输入等操作。为了简化这一点,Yarn 的团队设计了一种 Application Driver 类,以简化一些最常见的操作。它首先继承自 WebDriver 类,并引用了 Selenium 中的 Actions 类,随后加入了一些方法用于实现最常见的用户操作,例如单击元素与执行脚本等等。可以通过下面这个 UML 图概括这个类的设计。

其使用方法如下:

复制代码
def dragAndDrop(cssFrom: String, cssTo: String) {
val elem1 = getElementByCss(cssFrom)
val elem2 = getElementByCss(cssTo)
actions.dragAndDrop(elem1, elem2)
}
def contextClickByCss(css: String)
actions.contextClick(getElementByCss(css))
}

通过 ID 访问 DOM 对象

在 WebDriver 测试过程中,如何定位一个 DOM 元素是最有挑战性的任务之一。常见的方式包括 XPath、CSS 路径以及各种复杂的 CSS 选择器(类似于 jQuery),但这些方式在元素移动了位置或改变了 CSS 类名之后就会失效,不得不重新修改代码。因此,Yarn 建议使用 DOM 元素的 ID 进行定位,这种方式的好处是不受元素所在位置、以及所应用的样式的影响。Yarn 的团队随后对产品的某一重要特性进行了 UI 改版,而由于页面中的 ID 保持不变,因此测试代码的改动非常之少。

页面对象模式

页面对象模式(Page Object Pattern)是测试代码可维护性的关键因素,这一模式本身非常简单,它表示每个页面应了解如何执行该页面当中的所有操作。举例来说,登录页面知道应当如何提交用户的认证信息、如何点击“忘记密码链接”等等操作。如果将这些功能转移到一个公用的地方,就可以在所有测试中重用这部分功能。以下代码表示了一个文档页面的功能:

复制代码
object DocsList extends RetryHelper with MainMenu with Page {
val actionsPanel = new ActionsPanel
val fileBrowser = new FileBrowser
val fileTree = new FileTree
val sharingPanel = new SharingPanel
val invitationPanel = new InvitationPanel

这个页面中的操作非常多,因此 Yarn 将其分解为多个较小的类,每个类都代表了页面中某个块的功能。它们各自包含在这一区域内可执行的操作的相关方法,正如以下代码所示:

复制代码
def clickCreateDocument(implicit user: LucidUser) {
doWithRetry() {
user.clickElement("new-document-button")
}
}
def selectDocument(fileNum: Int=0)(implicit user: LucidUser) {
doWithRetry() {
user.driver.getElements(docIconCss)(fileNum).click()
}
}
def numberOfDocsEquals(numberOfDocs: Int)(implicit user: LucidUser) : Boolean ={
predicateWithRetry(WebUser.longWaitTime *5, WebUser.waitTime) {
numberOfDocuments == numberOfDocs
}
}

行为的重试

在 WebDriver 测试过程中,最糟糕的问题在于误判的错误,这为自动化构建过程带来了很大的困难。对于 Yarn 的团队来说,这个问题也是他们所面对的头号大敌。为了克服这一点,他们为测试加入了重试的功能,使得测试结果得到很大的改善。 以下是这个重试方法的代码:

复制代码
/**
* Try and take an action until it returns a value or we timeout
* @param maxWaitMillis the maximum amount of time to keep trying for in milliseconds
* @param pollIntervalMillis the amount of time to wait between retries in milliseconds
* @param callback a function that gets a value
* @tparam A the type of the callback
* @return whatever the callback returns, or throws an exception
*/
@annotation.tailrec
private def retry[A](maxWaitMillis: Long, pollIntervalMillis: Long)(callback: => A): A = {
val start = System.currentTimeMillis
Try {
callback
} match {
case Success(value) => value
case Failure(thrown) => {
val timeForTest = System.currentTimeMillis - start
val maxTimeToSleep = Math.min(maxWaitMillis - pollIntervalMillis, pollIntervalMillis)
val timeLeftToSleep = maxTimeToSleep - timeForTest
if (maxTimeToSleep <= 0) { throw thrown } else { if (timeLeftToSleep > 0) {
Thread.sleep(timeLeftToSleep)
}
retry(maxWaitMillis - pollIntervalMillis, pollIntervalMillis)(callback)
}
}
}
}

这段代码的功能是通过一个简单的递归算法执行所传入的实际行为,直到该行为成功,或是运行超时为止。以下是使用这个方法的简单示例:

复制代码
def numberOfChildren(implicit user: LucidUser): Int = {
getWithRetry() {
user.driver.getCssElement(visibleCss).children.size
}
}

测试集重试

Yarn 的团队所做的最后一项改善是配置测试集的重试,测试集重试会将失败的测试缓存起来,然后重新运行这些失败的测试。只要在后续的重试中有一次成功,这项测试就会被认为通过。否则将继续重试,直到重试次数达到上限为止。 Yarn 的做法是尽量将一些依赖于第三方功能的行为区分开来,特意为这些功能的集成编写非常健壮的代码似乎没有什么意义,因此可以将它们放到一个可重试的测试集中。对于他们来说,重试的目的不是为了修复测试代码中的问题,而是为了消除测试报告中由误判所带来的影响。

创造乐趣

Selenium 的开发很容易令人感到疲惫,许多测试会无故地失败,让这些测试得到正确的结果是非常繁琐的工作,重复性的样板代码令人提不起兴致。而在 Yarn 的团队建立了一个可靠的、可维护以及可伸缩的框架之后,工作就变得有趣起来了。各种有趣的想法层出不穷,有一位开发者实现了对绘画 canvas 截图并上传至 Amazon S3 服务的功能,随后又加入了一个截图比较的工具以实现图片比较测试。其它令人印象深刻的测试还包括与 Google Drive、Yahoo 与 Google 的单点登录等功能的整合。整个测试工作开始变得生动起来,这也为团队最终实现了重构的目标带来了极大的推动力。


感谢徐川对本文的审校。

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

2015-07-31 08:165504
用户头像

发布了 428 篇内容, 共 197.6 次阅读, 收获喜欢 39 次。

关注

评论

发布
暂无评论
发现更多内容

PWA与小程序技术架构差异及出海布局策略

xuyinyin

rust程序静态编译的两种方法总结

电子尖叫食人鱼

rust 后端

零信任的用户行为分析: 通过综合策略解锁安全洞察力(三)

天翼云开发者社区

安全 零信任 行为感知

阿里云 AI 搜索开放平台新增:服务开发能力

阿里云大数据AI技术

大数据 数据分析 数据处理 人工智能、 AI 搜索引擎

AI工具的普及,让程序员面临当下的应聘困境

Nackydeng

个人感悟 程序员求职 大模型 求职面试 大厂招聘

CST软件散射近场提取和散射截面-法诺共振球

思茂信息

cst CST软件 CST Studio Suite

零信任的用户行为分析: 通过综合策略解锁安全洞察力(二)

天翼云开发者社区

安全 零信任 环境感知

为什么自动化测试总在凌晨崩溃?90%工程师不知道的AI脚本陷阱

测试人

人工智能

资深导师手把手教学,龙蜥社区邀您开启 2025 开源之夏

OpenAnolis小助手

操作系统 龙蜥社区 开源之夏 龙蜥赛事

通义灵码添加上下文能力怎么用?一篇看懂

阿里巴巴云原生

通义灵码

Databend 推出 BendDeploy:一站式可视化管控平台,重塑数据库私有化运维体验

编程猫

从“制造”到“智造”,落地新质生产力的关键

科技热闻

DNS缓存投毒是怎么回事?怎么预防?

防火墙后吃泡面

龙蜥社区走进中国农业大学,共探“AI+生命科学” 操作系统优化实践

OpenAnolis小助手

操作系统 龙蜥社区 OpenAnolis 龙蜥高校行

议题征集 龙蜥邀您共探“AI+运维”发展新趋势 | 龙蜥 MeetUp

OpenAnolis小助手

操作系统 系统运维 龙蜥社区 龙蜥meetup

低代码云MES、轻量级部署、让智造更简单

万界星空科技

制造业 mes 云 原生云 CTO 云mes 低代码云MES

B2B 电商 API 实战:1688 平台实时商品数据采集接口对接指南

代码忍者

比较入站和出站防火墙规则

运维有小邓

日志分析 日志审计 防火墙工具 防火墙日志管理

权威数据 | 商汤万象,再次领跑!

商汤万象开发者

AI 科技

有哪些值得推荐的Markdown编辑器?5个Markdown软件盘点!

职场工具箱

markdown 编辑器 PPT 办公软件 AI生成PPT

Gitee推荐项目!埋点+用户分析系统,适合中小团队的开源方案

ClkLog

开源 SaaS 埋点 用户行为分析 画像

质量管理是干啥的?QA、QC、QE有啥不一样?

积木链小链

质量管理 数字化转型 智能制造 生产管理

通义灵码添加上下文能力怎么用?一篇看懂

阿里云云效

通义灵码

企业级开发平台:从分布式架构到 AI 代理的协同创新

代码制造者

AI DevOps 分布式系统架构

一文读懂HyperWorks有限元分析流程

智造软件

模型 CAE 网格 单元格 Hypermesh

中国自动驾驶研发解决方案,第一!

百度Geek说

智能驾驶

【FAQ】HarmonyOS SDK 闭源开放能力 — PDF Kit

HarmonyOS SDK

harmoyos

低代码如何赋能前沿科技,打造智能企业生态

天津汇柏科技有限公司

人工智能 低代码

FinClip赋能中国应用破浪全球:跨端降本增效引领出海新纪元

xuyinyin

技术实践 | 几乎零代码!像搭乐高一样做AI应用,LazyLLM确实有点东西!

商汤万象开发者

AI 智能体 大模型 LLM AI 智能体

PDF 翻译神器 PDFMathTranslate 安装和使用教程

硅基新手村

PDF rag FastGPT

使用Selenium测试时必需知道的7件事_语言 & 开发_邵思华_InfoQ精选文章