利用 F# 库 canopy 进行 UI 测试

  • Pierre-Luc Maheu
  • 邵思华

2016 年 3 月 18 日

话题:.NET语言 & 开发架构

虽然 Selenium 是一个流行的 UI 测试库,但基于它创建的测试却有着脆弱不可靠等常见的问题。InfoQ 与canopy的作者 Chris Holt 进行了一次访谈,以便更深入地了解 canopy 这个在 Selenium 的基础上所创建的 F# 库。

InfoQ:你能否为我们介绍一下 canopy?

Chris Holt:canopy 是基于 Selenium 之上,以 F# 实现的一层功能,它的目标是使 UI 测试的行为符合使用者的预期。Selenium 虽然表现很出色,但有时会显得过于刻板。在 canopy 中,所有行为不再会因为无法点击某个元素而立即报错,而是不断地尝试点击某个元素,直至一段合理的时间后仍无法点击才会报错。这将帮助你创建更可靠的测试,而不是往往只在第一次尝试时成功的测试。

InfoQ:canopy 如何简化通过 Selenium 进行 UI 测试的过程?

CH:canopy 内置了重试功能,包括元素的获取以及用户在屏幕上的行为,也包括验证功能。此外,canopy 还可以通过实用的错误信息帮助用户修复一些常见的问题,例如某个选择器的拼写错误。它还支持以多种方式选择元素,并且易于对这些功能进行扩展。

举例来说,如果用户的屏幕上有一个显示“Save”文字的按钮,那么只需要在代码中写为click "Save",就可以实现单击的目的。而在常规的 Selenium 代码中,用户必须在 ByText、ById、ByCSS、ByXPath 等方法中进行选择。如果要扩展这一功能,只需为用户网页的惯用方式添加对应的 finder 实现,例如在表单数据中的 placeholder 值,或是为了表示元数据而人为定义的 data-* 标签。

canopy 还提供了一套简明的 API,让用户更方便地阅读与编写测试。它还能够克服 html 中的各种区别。比方说,每种输入类型在 html 中都有着不同的表现方式,因此通过原始的 Selenium 操作他们的方式也是不同的。而在 canopy 中,操作方式都是相同的。举例来说:

// Assign a value to a textbox or dropdown
"#state" 

InfoQ:canopy 是否支持与外部自动化服务的集成,例如 Browserstack?

CH:是的,只要是 Selenium 支持的功能,canopy 也同样支持。为了支持 Browserstack,用户需要使用 RemoteWebdriver。由于 canopy 本身内置了大量的重试功能,因此交互的次数将有所增加。不过,因为用户不必经常调用 Sleep,因此这种增加的交互是可以接受的。canopy 还提供了大量可选的优化方式,如果你打算具体地表述选择器的类型,而不是让 canopy 分析你的选择器类型,就可以应用这些优化。

InfoQ:在页面中选择元素有没有推荐的做法?比方说,通过 id 选择元素是否会为测试带来更好的可维护性与健壮性?

CH:在 UI 自动化中所用到的多数“技巧”都与选择元素相关。至于是应该使用整洁的选择器,还是必须要在标签中加入 class 或 id 等属性,这两者之间需要找到一个平衡点。我认为,CSS 与 JQuery 选择器的语法是最优秀的,用户可以在 80-90% 的场景中使用这种方式。而在其余 10%-20% 的场景中可以使用 XPath。在进行准确的文字匹配或是找到某个元素的父元素时需要用到 XPath。而通过值或内部文字进行查找也是非常方便的做法,假如上文所述的 click "Save"这个示例,它的内部实现就用到了 XPath。

经过一段时间的实践之后,用户就可以熟练地掌握创建选择器的方法了,工建议用户通过实践进行学习,而不是通过某种工具去生成选择器。这种方式更准确,并且当页面结构发生变化而影响了用户的测试时,它也更容易进行修正。一旦用户对于选择器有了一定的心得体会之后,用户就会懂得如何让 html 代码更易于维护,从而简化了选择器的创建。

选择器的编写可以通过某些方式让人更易于理解,例如 #header .links 这个选择器就表示在页面的 header 这个 div 中所有的 links 元素。而在自动生成的 XPath 中,它或许会变成 html/body/div/div/div[2]/ul/li/a 这种形式,这对于理解它的意义毫无帮助。而且如果一旦在这条选择链中多加了或是删除了某个 div,就会使选择器无法工作。通过 CSS 方式编写的选择器“永远”都是有效的,除非有人改动了 header 这个 id,或者删除 / 改变了 links 这个 class。

InfoQ:用户是否可以自定义错误报告?比方说,我们是否能够在测试失败时自动截屏呢?

CH:canopy 目前内置了 3 种 reporter实现,即 ConsoleReporter、TeamCityReporter 和 HtmlReporter。如果用户需要新增一个自定义的功能,只需简单地实现 IReporter 这个接口就行了。

InfoQ:有哪些方法能够扩展 canopy 的功能?

1)通过实现 IReporter 接口,自定义测试结果的输出

2) 在 canopy 所使用的 finder 集合中添加新的通用 finder 实现,以帮助用户找到页面元素。

3) 为用户常用的 action 添加新的函数。由于 F# 会运行某个函数最新定义的版本,因此用户还能够“重写”现有的函数,以满足自身的需求。

比方说,用户可以实现自定义的“click”功能,只需创建一个模块,例如“canopyExtensions”,并在“打开 canopy”操作后“打开”这一模块,将所有扩展方法与重写的方法定义在其中。这样一来,所有测试都会调用由用户所定义的功能,而无需改动任何现有的功能。

这个示例表现的是某人希望能够在多选框中实现对元素的 Ctrl+Click 操作。由于 canopy 本身不具备这一特性,因此作者编写了一段扩展方法。

4) canopy 并没有隐藏任何 Selenium 中的特性,或是对其进行抽象化。它只是使用了 IWebDriver 与 IWebElement 接口。用户在 Stack

overflow 网站上看到的各种问题与回复对 canopy 都是 100% 有效的。用户所要做的唯一一件事就是将代码转换为 F#。

InfoQ:在持续集成(CI)服务器上运行 canopy 测试需要经过哪些步骤?

CH:测试的运行方式是执行由构建过程所生成的控制台应用。测试结果支持不同的输出格式。支持 TeamCity 的配置过程只需一行代码:

reporter <- new TeamCityReporter() :> IReporter

我个人会选择使用一些简单的任务来实现整个构建过程,因此我在 TeamCity 中创建的任务都是很简单的,例如从源代码控制系统中获取最新代码,随后通过一个命令行语句启动我的任务,并完成其余工作。

在 Jenkins 环境中,我会选用 HtmlReporter,通过一个 Jenkins 的插件生成 html 文档,并保存到整个任务的结果中。这同样也可以通过简短的几行代码完成设置工作。

canopy 是一个托管在GitHub上的开源项目。如果读者有兴趣了解对它的更多介绍,可以观看 Chris Holt 最近在 fsharpConf 大会上关于 canopy 的演讲

查看英文原文UI Testing in F# with canopy

.NET语言 & 开发架构