页面对象

2013年9月10日

当您针对网页编写测试时,需要引用网页中的元素才能点击链接并确定显示的内容。但是,如果您编写直接操作 HTML 元素的测试,那么您的测试将对 UI 的更改很脆弱。页面对象将 HTML 页面或片段包装在特定于应用程序的 API 中,允许您操作页面元素而不深入研究 HTML。

页面对象的经验法则是在允许软件客户端执行任何操作并查看人类可以执行的任何操作。它还应提供易于编程的接口,并隐藏窗口中的底层小部件。因此,要访问文本字段,您应该拥有接受和返回字符串的访问器方法,复选框应使用布尔值,按钮应由面向操作的方法名称表示。页面对象应封装在 GUI 控件本身中查找和操作数据的机制。一个好的经验法则是想象更改具体控件 - 在这种情况下,页面对象接口不应该改变。

尽管有“页面”对象这个术语,但这些对象通常不应为每个页面构建,而应为页面上的重要元素构建 [1]。因此,显示多个专辑的页面将有一个包含多个专辑页面对象的专辑列表页面对象。可能还会有一个页眉页面对象和一个页脚页面对象。也就是说,复杂 UI 的某些层次结构仅用于构建 UI - 这些复合结构不应由页面对象公开。经验法则是对应用程序用户有意义的页面结构进行建模。

同样,如果您导航到另一个页面,初始页面对象应返回另一个用于新页面的页面对象 [2]。通常,页面对象操作应返回基本类型(字符串、日期)或其他页面对象。

关于页面对象是否应包含断言本身,或者只是为测试脚本提供数据以进行断言,存在不同的意见。支持在页面对象中包含断言的人认为,这有助于避免测试脚本中断言的重复,使提供更好的错误消息变得更容易,并支持更 TellDontAsk 风格的 API。支持无断言页面对象的人认为,包含断言会将提供对页面数据的访问的责任与断言逻辑混合在一起,并导致页面对象膨胀。

我赞成页面对象中没有断言。我认为您可以通过为常见断言提供断言库来避免重复 - 这也可以使提供良好的诊断变得更容易。 [3]

页面对象通常用于测试,但本身不应进行断言。他们的责任是提供对底层页面状态的访问。由测试客户端执行断言逻辑。

我已经用 HTML 术语描述了这种模式,但相同的模式同样适用于任何 UI 技术。我已经看到这种模式有效地用于隐藏 Java Swing UI 的细节,我毫不怀疑它也已广泛用于几乎所有其他 UI 框架。

并发问题是页面对象可以封装的另一个主题。这可能涉及隐藏异步操作中的异步性,这些异步操作对用户来说并不像异步那样。它还可能涉及封装 UI 框架中的线程问题,在这些框架中,您必须担心在 UI 线程和工作线程之间分配行为。

页面对象最常用于测试,但也可以用于在应用程序之上提供脚本接口。通常最好将脚本接口放在 UI 下面,这通常更简单且更快。但是,对于将太多行为放入 UI 的应用程序,使用页面对象可能是最好的选择。(但如果可以,请尝试移动该逻辑,这将对脚本和 UI 的长期健康都有好处。)

使用某种形式的 领域特定语言(如 Cucumber 或内部 DSL)编写测试很常见。如果您这样做,最好将测试 DSL 分层在页面对象之上,以便您拥有一个将 DSL 语句转换为页面对象调用解析器的解析器。

如果您的测试方法中有 WebDriver API,那么您做错了。-- Simon Stewart.

旨在将逻辑从 UI 元素中移出的模式(如 表示模型监督控制器被动视图)使通过 UI 进行测试变得不那么有用,从而减少了对页面对象的需要。

页面对象是封装的典型示例 - 它们将 UI 结构和小部件的细节隐藏在其他组件(测试)中。在开发过程中寻找类似情况是一个好的设计原则 - 问问自己“如何将某些细节隐藏在软件的其余部分?”与任何封装一样,这会带来两个好处。我已经强调,通过将操作 UI 的逻辑限制在一个地方,您可以在那里修改它,而不会影响系统中的其他组件。随之而来的好处是,它使客户端(测试)代码更容易理解,因为那里的逻辑是关于测试的意图,而不是被 UI 细节所掩盖。

进一步阅读

我第一次在 窗口驱动程序 这个名字下描述了这种模式。但是从那时起,术语“页面对象”被 Selenium Web 测试框架普及,并且已成为普遍使用的名称。

Selenium 的维基 强烈建议使用页面对象,并提供有关如何使用它们的建议。它也支持无断言页面对象。

一个团队测量了在软件升级后更新两个版本的 Selenium 测试套件的时间。他们发现使用页面对象的版本在第一个测试用例中花费的时间略长,但在其余测试用例中花费的时间要快得多。有关更多详细信息,请参阅 Leotta 等人,“使用页面对象模式提高测试套件的可维护性”,ICSTW 2013

致谢

Perryn Fowler、Pete Hodgson 和 Simon Stewart 对本文草稿提出了特别有用的评论 - 尽管像往常一样,我非常感谢 Thoughtworks 内部软件开发列表中的各种居民对他们的建议和更正。

笔记

1: 这里有一个论点,即“页面对象”这个名字具有误导性,因为它让你认为每个页面应该只有一个页面对象。类似“面板对象”会更好 - 但“页面对象”这个术语已经成为公认的术语。另一个说明为什么命名是 两件难事 之一。

2: 让页面对象负责创建其他页面对象以响应诸如导航之类的事件是常见的建议。但是,一些从业人员更喜欢页面对象返回一些通用的浏览器上下文,而测试根据测试流程(尤其是条件流程)控制在该上下文之上构建哪些页面对象。他们的偏好基于测试脚本知道接下来应该出现哪些页面,并且不需要在页面对象本身中复制此知识。当使用通常在类型签名中显示页面导航的静态类型语言时,他们会增加他们的偏好。

3: 即使对于像我这样通常支持无断言风格的人来说,一种形式的断言也是可以的。这些断言是检查页面或应用程序在此处的固定不变性,而不是测试正在探测的特定内容。