acm-header
登录

ACM通信

实践

使用状态对象测试Web应用程序


用状态对象测试Web应用程序,说明性照片

图片来源:Alicia Kubista / Andrij Borys Associates

回到顶部

Web应用程序的端到端测试通常需要通过Selenium WebDriver等框架与Web页面进行复杂的交互。12隐藏此类web页面复杂性的建议方法是使用页面对象,10但是有一些问题需要首先回答:在测试Web应用程序时应该创建哪些页面对象?在页面对象中应该包含哪些操作?给定页面对象,应该指定哪些测试场景?

在过去的几个月里,我们一直在使用页面对象来测试AngularJS (https://angularjs.org) Web应用程序,我通过将页面对象移动到状态的水平。将Web应用程序作为状态图来查看,可以更容易地设计测试场景和相应的页面对象。本文描述了逐渐出现的方法:本质上是基于状态的页面对象泛化,这里称为状态对象。

WebDriver是一种先进的工具,广泛用于测试Web应用程序。它提供了一个API来访问在浏览器中呈现的Web页面上的元素。可以检查元素,例如访问表中包含的文本或元素的样式属性。此外,API还可用于与页面交互,例如单击链接或按钮,或在输入表单中输入文本。因此,可以使用WebDriver通过Web应用程序为单击场景编写脚本,从而形成应用程序的端到端测试套件。

WebDriver可以用来测试您选择的浏览器(Internet Explorer, Firefox, Chrome等)。它提供了不同的语言绑定,允许您用c#、Java、JavaScript或Python编写场景。

页面对象。WebDriver提供了一个API来与浏览器中呈现的Web页面上的元素进行交互。但是,有意义的最终用户场景不应该用Web元素来表示,而应该用应用程序域。

因此,编写WebDriver测试的一个推荐模式是使用页面对象。这些对象提供了在web页面元素上实现的领域概念上的API,如中所示图1,摘自软件设计师马丁·福勒的一幅插图。4

这些页面对象隐藏了使用的特定元素定位器(例如,用于查找按钮或链接)和底层“小部件”的细节。因此,如果页面详细信息发生更改,场景的可读性更强,也更容易维护。

页面对象不需要代表整个页面;它们还可以覆盖用户界面的一部分,如导航窗格或上传按钮。

为了表示通过应用程序的导航,“PageObject上的方法应该返回其他PageObject。”10本文正是基于这一思想进行了进一步的探讨,从而引出了我们将称之为的内容状态对象。

回到顶部

用状态图建模Web应用程序

为了对Web应用程序导航建模,让我们使用统一建模语言(UML)中的状态图。图2显示用于登录到应用程序的状态图。用户正在进行身份验证或验证。它们开始没有被验证,输入它们的凭据,如果它们是OK的,它们将到达验证状态。从那里,他们可以注销,返回到可以进行身份验证的页面。

这个图通常会指向两个页面对象:

  • 一个用于登录页面,对应验证状态。
  • 一个用于注销按钮,显示在以身份验证状态显示的任何页面上。

为了强调这些页面对象表示状态,它们被赋予了状态导航和状态检查的明确职责,它们成为状态对象。

回到顶部

状态对象:检查和触发方法

对于每个状态对象,可以识别两种类型的方法:

  • 检验方法返回浏览器处于给定状态时显示的关键元素的值,例如用户名、文档名称或某些度量值;它们可以在测试场景中使用,以验证浏览器是否显示了预期的值。
  • 触发方法对应于模拟的用户点击,并使浏览器进入新的状态。在身份验证状态下,用户可以输入凭证并单击提交按钮,假设凭证正确,该按钮将引导浏览器进入下一个身份验证状态。在那里,用户可以单击注销按钮返回身份验证状态。

将最重要的检查方法组合成一个属性自检是很有用的,每当应用程序处于特定状态时,这些属性必须保持。例如,在身份验证状态下,您可能希望有用于输入用户名或密码的字段;应该有一个提交按钮;也许URL应该包含登录路由。然后可以使用这种自检方法来验证浏览器确实处于给定的状态。

场景:触发和检查事件。给定一组状态对象,测试用例描述相关场景(路径)通过状态机。例如:

  1. 进入登录URL。
  2. 通过自检验证您处于身份验证状态。
  3. 输入正确的凭证并提交。
  4. 验证您处于Authenticated状态。
  5. 下线。
  6. 验证您处于身份验证状态。

因此,一个场景(测试或验收)是一系列操作,每个操作后面都有一个已达到预期状态的检查。

有条件的事件。除了成功登录之外,一个实际的登录过程还应该处理使用无效凭据登录的尝试,如图3.该图显示了显示错误消息的额外状态。这个额外的状态会产生一个额外的状态对象,对应于适当消息的显示。作为操作,它只有一个关闭按钮,引导回到原始登录页面。

额外的状态自然会导致另一个测试场景:

  1. 进入登录URL。
  2. 执行authentication自检。
  3. 输入无效凭证并提交。
  4. 进行登录错误自检。
  5. 达到接近。
  6. 执行authentication自检。

图3边的形状

事件[条件]/动作

因此,触发器(单击)可以是有条件的,除了导致一个新的状态之外,它还可以导致一个动作(服务器端)。

在测试此类转换时,您触发事件,确保满足条件,然后验证:您可以观察所需操作的任何效果;就会达到相应的状态。

回到顶部

扩展你的州图表

为了驱动测试,您可以扩展状态图以适应其他场景。例如,与身份验证相关的是注册一个新用户,如图4.这个图包括身份验证状态,但不包括它的所有传出边。相反,我们关注的是一种新的注册状态和可能的转换。

这又产生了两个新的状态对象(用于注册和显示错误消息)和另外两个场景。因此,在为给定页面开发测试时,没有必要考虑完整的状态机:关注感兴趣的状态对于派生测试用例非常有帮助。

超级国家。具有共同行为的国家可以被组织成超级州(也称为或状态)。例如,一旦通过身份验证,所有页面可能都有一个公共标题,其中包含用于注销的按钮,以及用于导航到关键页面的按钮(例如,用于管理帐户或获取帮助的按钮,显示在图5).

边缘走出超状态(例如注销)是外向的人的简称注销事件,用于四个内部状态(基状态)中的每一个。对简写进行扩展就会得到in的图表图6其中,超状态的5条外向边为每个内部状态展开,得到4* 5 = 20条(有向)边(绘制为双向边,以保持图的可管理性)。

实现这种超级状态的典型方法是使用可重用的HTML片段,例如,在AngularJS中,这些HTML片段是通过ngInclude指令包含的。1

在这种情况下,最自然的做法是创建一个与公共对象对应的状态对象包括文件。它包含所需链接或按钮的存在性检查和事件检查,以查看是否,例如,单击设置确实导致设置状态。

一个可能的测试场景是:

  1. [登录所需步骤]
  2. 行为投资组合自检。
  3. 点击设置链接。
  4. 自检进行设置。
  5. 点击帮助链接。
  6. 行为帮助自检。
  7. 点击账户链接。
  8. 进行自检。
  9. 点击组合链接。
  10. 行为投资组合自检。
  11. 点击注销链接。
  12. 进行自检进行身份验证。

这对应于一个测试经过身份验证的导航窗格的场景。它测试从“帮助”页单击帐户链接是否有效。但是,它不会检查从设置页面单击帐户链接是否有效。事实上,这个测试只覆盖了展开图中的20条边中的4条。

当然,你可以为所有20条边创建测试。如果被测试的应用程序为每个视图手工制作了导航窗格,而不是使用单个导航窗格,这可能是有意义的包括文件。在这种情况下,你可能有理由相信不同的轨迹可能揭示不同的bug。但是,通常情况下,测试所有扩展路径对于包括文件设置。

遍历状态。导航头的单个测试场景以特定的顺序访问了五个不同的状态,如下所示图7.这是一个相当长的测试,可以分成四个独立的测试用例(图8).

在单元测试中,后者是首选的方法。它的优点是四个测试相互独立:其中一个步骤的失败不会影响其他步骤的测试。此外,故障诊断也更容易,因为失败的测试用例将更清晰。

然而,这四个独立的测试可能要花费相当长的时间:每个测试都需要显式的身份验证,这将大大降低测试的执行速度。因此,在端到端测试中,更常见的是在测试用例之间看到共享的设置。

就JUnit的(http://junit.org)设置方法,9单元测试套件通常会使用@Before设置,它会在类中的每个@Test之前一次又一次地执行。另一方面,端到端测试更可能使用@BeforeClass,以便能够只执行一次昂贵的设置方法。

模态对话框通常用于禁用任何交互,直到用户确认了一个重要的消息(“你确定你…”)。例如前面显示的登录或注册错误消息。这样的模态对话框调用一个单独的状态,因此,对于不同的页面对象,提供一个接受事件来关闭对话框。

模态对话框可以使用浏览器警报(WebDriver必须在测试继续之前接受它们)或JavaScript逻辑来实现。在后一种情况下,需要测试的一个额外检查可能是对话框确实是模态的(也就是说,与页面的任何其他交互都是禁用的)。

如果模态对话框是由包含在超状态中的状态触发的,则对话框状态为超状态的一部分(因为对话框中禁用了超状态交互)。因此,绘制显示错误处理和共享导航的登录状态机的正确方法将如中所示图9.这里的错误对话框不是导航超状态的一部分,因为它只允许关闭事件,而不允许点击,例如,关于链接。

有些应用程序非常注重对话,例如在处理注册、登录、忘记密码等问题时。许多这样的对话框只用于通知用户状态更改成功。为了简化状态图,可以将对话框绘制为边缘的注释,如图10

上图为完整版本,下图为缩略版本。注意< <对话框> >注释对于实现测试非常重要。测试用例必须点击对话框的关闭按钮;否则,测试将被阻塞。

树的过渡。为了支持状态可达性的推理,以及状态和转换覆盖范围,将状态图转换为转换树是很有帮助的,如图所示图11.(要了解更多信息,请参阅Robert Binder关于测试状态机的章节面向对象系统的测试。3.

的树图11派生自显示注册和身份验证的状态机。从初始身份验证状态开始,让我们对图进行宽度优先遍历。因此,每个州你都要先访问它的直接继承者。如果您进入一个您已经访问过的状态,访问过的状态用灰色表示为叶节点。然后去下一个无人探访的州。

该树有助于为单个状态设计测试:树中到该状态的路径是图中到该状态的最短路径。此外,它清楚地表明了一个状态有哪些向外的边。

该树还有助于为完整的状态机设计测试套件:为从根到叶的每条路径编写一个测试用例,生成一个涵盖所有转换的测试套件,因此,涵盖机器中的所有状态。

覆盖路径。虽然关注单个状态和它们的转换是发现和消除基本错误的好方法,但只有当遵循一个规则时,才能看到一组更棘手的缺陷路径在多个州。

例如,考虑客户端缓存。像AngularJS这样的框架可以很容易地为(一些)后端HTTP调用启用缓存。正确地执行此操作将提高响应性并减少网络往返,因为后端调用的结果将被记住,而不是一遍又一遍地请求。

但是,如果结果可能会改变,这可能会导致不正确的结果。例如,客户端可以在一个页面上请求包含所需信息的概览页面,在下一个页面上修改基础数据,然后返回到原始概览页面。这对应于红色的路径图12

启用缓存后,Portfolio状态将缓存后端调用结果。设置状态的正确缓存实现应该是在发生更改时使缓存失效。因此,当重新访问Portfolio状态时,将再次进行调用,并且将使用更新的结果。

这种缓存行为的测试用例可能如下所示:

  1. 选择最短的路线进入投资组合。
  2. 从投资组合中收集感兴趣的价值。
  3. 单击“设置”链接可导航到“设置”。
  4. 修改将影响感兴趣的Portfolio值的设置。
  5. 单击portfolio链接以导航回portfolio。
  6. 断言显示修改后的值。

在前面提到的AngularJS应用程序中,这个测试用例捕捉到了一个实际的bug。不幸的是,提出一个覆盖范围的测试策略是困难的或昂贵的所有这样的路径可能包含bug。

在一般情况下,在环路存在的情况下,有无限多的潜在路径可遵循。因此,测试人员将需要依靠专业知识来确定感兴趣的路径。

前面描述的基于转换树的方法提供了所谓的往返覆盖2也就是说,它执行每个循环一次,直到返回到已经访问过的节点(一次往返)。假设所有超级状态都被扩展了,这个策略将导致缓存示例的测试用例。

备选标准包括所有长度为n的路径,其中每个给定长度的可能路径都必须被执行。额外的成本是大量增加的测试用例,然而,在没有自动化工具的情况下实现这样的标准通常是困难的。

在状态对象方面,测试路径不会导致新的状态对象,因为状态已经存在了。但是,如果需要断言路径上的属性,可能需要在状态对象中调用额外的检查方法。

回到顶部

后退

浏览器的后退按钮提供了需要特别注意的状态导航。虽然这个按钮在传统的超文本导航中很有意义,但在今天的Web应用程序中,它的行为应该是什么并不总是很清楚。

Web开发人员可以通过操作历史堆栈来改变按钮的行为。因此,您希望能够测试Web应用程序的后退按钮行为,而WebDriver为它提供了一个API调用。

就状态机而言,后退按钮不是一个单独的转换。相反,它是对先前(单击)事件的反应。因此,后退按钮的行为是边缘的一个属性,表明可以通过反向跟随它来“撤消”转换。

UML为元素赋予特殊意义的机制是使用注释(概要文件)。在图13显式的< <返回> >而且< < noback > >在边缘添加了注释,以指示在单击返回初始状态后是否可以使用后退按钮。因此,对于关于、注册和身份验证状态之间的简单导航,可以使用后退按钮导航回去。

然而,在Authenticated和Authenticated状态之间,返回按钮是有效禁用的:一旦注销,单击“返回”应该不允许任何人进入需要身份验证的内容。了解哪些转换具有特殊的返回行为将指导验证所需行为的额外测试场景的构建。

回到顶部

有历史的超级大国

作为一个稍微复杂一点的超状态示例,考虑一个跨不同列排序的表。单击列标题将导致对表进行排序,从而为每一列产生一个子状态(图14).

事件来自超状态,表明它们可以从任何子状态触发并进入特定的子状态。例如,当离开可排序的表页面时,通过请求给定rowa设计的详细信息,需要做出决定:返回该页面(在这种情况下通过单击portfolio链接)应该产生一个按默认列排序的表(在这种情况下是a),还是应该根据单击的最后一列恢复排序。

在UML状态图中,第一个选项(返回到超级状态的初始状态)是默认的。第二个选项(回到超状态离开之前的状态)可以通过将超状态标记为History状态来表示,并用圈出的h标记它。在这两种情况下,如果这种行为很重要并且需要测试,则需要一个额外的路径(场景)来验证超状态从非初始状态退出后返回到正确的状态。

和州。今天的Web应用程序通常显示许多独立的小部件,例如其中一个是联系人列表,另一个是图片数组。这些小部件对应于放置在一个页面上的小型独立状态机。

在UML状态图中,这样的状态可以由正交区域(也称为AND-states),如图15.图中显示了Portfolio状态,它包含一个可排序的表和一个用于添加项目的Upload按钮。这些可以被独立使用,正如被虚线分隔的Portfolio状态的两部分所表明的那样。上传对话框是模态的,这就是为什么它在Portfolio类之外。在上传之后,表仍然保持原来的排序,这就是为什么它被标记为H。

这样的正交区域可以用来表示一个页面上的多个小状态机。这些正交区域中的状态转换可能来自于用户交互。它们也可以通过服务器事件(通过Web套接字)触发,例如新电子邮件的推送通知和股票价格调整等。

从测试的角度来看,正交区域原则上是独立的,因此可以独立测试。

与or -state一样,and -state也可以扩展,在这种情况下,可以显式地显示所有可能的交错。这大大破坏了图表,因此,潜在的测试用例的数量。虽然测试一些这样的交错明显是有意义的,并且是可行的,但测试所有它们需要自动化的测试生成。

基于状态的故事。最后但并非最不重要的是,状态和状态图在用用户情景和验收场景描述需求时也很有用。例如,技术顾问丹·诺斯(Dan North)提出的故事格式与之很自然地契合。9这样的故事由一般的叙述构成,其形式是“作为一个……我想要的…,然后是“给定…”这种形式的多个接受场景。当……然后”。

在许多情况下,这些验收场景可以简单地映射到状态图中测试单个转换。这个场景采用了如下形式:

鉴于我已经到了某个州

当我触发一个特定事件时

然后应用程序执行一个操作

应用程序移动到其他状态。

因此,状态对象允许API按照这些接受场景的建议与状态机进行交互。一个测试用例从一个状态移动到另一个状态,一个完整的特性是由许多通过状态机导航的验收测试用例描述的,同时检查应用程序的行为。

回到顶部

AngularJS PhoneCat例子

作为一个使用WebDriver和状态对象进行测试的完整例子,请考虑AngularJS的PhoneCat教程(https://docs.angularjs.org/tutorial).运行中的PhoneCat应用程序的屏幕截图显示在图16.它带有一个用Protractor编写的测试套件,也就是WebDriverJS11为AngularJS应用量身定制的扩展。

该应用程序由一个简单的手机列表组成,可以按姓名进行过滤,并按字母顺序或年龄进行排序。点击一部手机就会得到该手机类型的详细信息。

本教程提供的WebdriverJS测试套件包括两个视图各三个测试用例(电话列表和电话详细信息),以及一个开放URL测试,总共7个测试用例。

原始教程中的测试套件不使用页面(或状态)对象。为了说明状态对象的使用,我将PhoneCat测试套件重写为基于状态对象的测试套件,它可以从GitHub上的PhoneCat fork (https://github.com/avandeursen/angular-phonecat/pull/1).

中显示了我用于PhoneCat应用程序的状态关系图图17.它会导致两个状态对象(每个视图一个)。这些状态对象可以用来表示原始场景集。此外,状态图需要额外的用例,例如原始测试用例中没有包含的“sort- latest”转换。

该数据还表明,没有直接从电话详细信息到电话列表的方法。在这里,浏览器的后退按钮是交互设计的一个显式部分,这就是为什么< <返回> >注释被添加到相应的转换中。(注意,这是该属性的唯一边界:在Phone List状态下,在任何其他过渡之后点击“Back”会退出应用程序,这是AngularJS的默认行为)。

由于后退按钮对于在两个视图之间导航是必不可少的,基于状态的测试套件也通过一个场景描述了这种行为。

最后,由于Protractor和WebDriverJS api完全基于异步JavaScript承诺,11状态对象实现也是异步的。例如,Phone List状态对象提供了一个方法,该方法“调度一个命令来对手机列表进行排序”,而不是在手机排序之前一直阻塞。通过这种方式,实际场景可以将承诺链接在一起,例如,使用然后保证运营商。

AngularJS在生产。本文中展示的大多数图都是基于为代尔夫特理工大学(Delft University of Technology)的一家附属公司开发的Web应用程序创建的图。该应用程序允许用户注册、登录、上传文件、分析和可视化文件,并检查分析结果。

应用程序的端到端测试套件使用状态对象。它由大约25个状态对象和75个场景组成。与PhoneCat测试套件一样,它使用了Protractor,由1750行JavaScript代码组成。

端到端测试套件从TeamCity (https://www.jetbrains.com/teamcity/)持续集成服务器,它将调用大约350个后端单元测试,以及后端或前端任何更改时的所有端到端场景。

该测试套件帮助修复和发现了与客户端缓存、后退按钮行为、表排序和图像加载相关的各种bug。其中一些问题是由错误的数据绑定导致的,例如,JavaScript变量名拼写错误或重命名重构不完整。测试还发现了与不正确的服务器配置和权限相关的后端API问题(例如,导致405和偶尔的500 HTTP状态代码),以及不正确的客户端/服务器假设(服务器返回的JavaScript对象符号不符合前端的预期)。

回到顶部

结论

在对Web应用程序进行端到端测试时,使用状态来驱动测试:

  • 将感兴趣的交互建模为小型状态机。
  • 让每个状态对应一个状态对象。
  • 每个状态都包含一个自检,以验证浏览器确实处于该状态。
  • 对于每个转换,编写一个场景,对原始和目标状态进行自检,并验证操作对转换的影响。
  • 使用转换树来推断状态可达性和转换覆盖范围。
  • 使用高级状态图概念,如and -states、OR-states和注释,以保持图表的简洁和易于理解。
  • 考虑通过状态机的可能容易出错的特定路径;如果您已经为该路径上的状态设置了状态对象,那么沿着该路径测试行为应该很简单。
  • 在持续集成服务器中使用端到端测试套件,以发现HTML、JavaScript和后端服务之间的集成问题。

与页面对象一样,浏览器交互的细节封装在状态对象中,并对测试场景隐藏。最重要的是,状态图和相应的状态对象直接指导您完成测试套件设计的整个过程。

回到顶部

致谢

感谢Michael de Jong、Alex Nederlof和Ali Mesbah的许多精彩讨论和对这篇文章的反馈。本文中的UML图是用免费的UML绘图工具UMLet(13.2版)绘制的。

ACM队列的q戳相关文章
queue.acm.org

移动性能优化规则
Tammy翻转
http://queue.acm.org/detail.cfm?id=2510122

编写Web服务原型脚本
克里斯托弗·文森特
http://queue.acm.org/detail.cfm?id=640158

软件需要安全带和安全气囊
金刚砂d·伯杰
http://queue.acm.org/detail.cfm?id=2333133

回到顶部

参考文献

1.AngularJS。ngInclude指令;https://docs.angularjs.org/api/ng/directive/ngInclude

2.Antoniol, G., Briand, l.c., Di Penta, M.和Labiche, Y.使用双向策略进行基于州的类测试的案例研究。在诉讼13th软件可靠性工程国际研讨会。IEEE(2002), 269279。

3.粘合剂,随机变数面向对象系统的测试。Addison-Wesley, Reading, PA, 1999,第7章。

4.福勒,M. PageObject, 2013;http://martinfowler.com/bliki/PageObject.html

5.Harel, d . 1987。状态图:复杂系统的可视化形式。计算机程序设计学8(3): 231274。

6.Horrocks,我。用Statecharts构造用户界面。Addison-Wesley, Reading, PA, 1999。

7.Leotta, M., Clerissi, D., Ricca, F., Spadaro, C.用页面对象模式提高测试套件的可维护性:一个工业案例研究。在测试录:学术和工业会议实践和研究技术。IEEE(2013), 108113。

8.Mesbah, A., van Deursen, A.和Roest, D. D.现代Web应用程序的基于不变式的自动测试。IEEE软件工程汇刊38, 1(2012) 3553。

9.D.故事里有什么?http://dannorth.net/whats-in-a-story/

10.硒。页面对象,2013;https://github.com/SeleniumHQ/selenium/wiki/PageObjects

11.硒。承诺。在WebDriverJS用户指南,2014;https://code.google.com/p/selenium/wiki/WebDriverJs#Promises

12.SeleniumHQ。WebDriver;http://docs.seleniumhq.org/projects/webdriver/

回到顶部

作者

阿里Van Deursen是代尔夫特理工大学的教授,领导软件工程研究小组。为了将他的研究付诸实践,他于2000年和2010年共同创立了软件改进集团(Software Improvement Group)和Infotron。

回到顶部

数据

F1图1。页面对象。(图:马丁·福勒)

F2图2。登录到应用程序。

F3图3。使用条件事件登录。

F4图4。注册新用户。

F5图5。超级状态下的常见行为。

F6图6。扩张一个超级国家。

F7图7。单个测试遍历多个状态。

F8图8。不同状态的单独测试。

F9图9。带有错误处理和共享导航的登录状态机。

F10图10。使用转换注释来简化图。

季图11。过渡的树。

F12图12。长路径上的故障。

F13图13。后退按钮注释。

F14图14。超级有历史的国家。

F15图15。正交区域。

F16图16。PhoneCat AngularJS应用程序。

F17图17。电话目录的状态图。

回到顶部


版权归作者所有。授权给ACM的出版权。

数字图书馆是由计算机协会出版的。版权所有©2015 ACM股份有限公司


评论


鲁道夫Olah

非常简洁地使用了状态机和图表;很高兴看到有人在直接编写代码之前对想法进行建模。end2end测试代码的行数如此之少,真是令人惊喜:

>应用程序的端到端测试套件使用状态对象。它由大约25个状态对象和75个场景组成。与PhoneCat测试套件一样,它使用了Protractor,由1750行JavaScript代码组成。

在生产环境中,覆盖甚至几个场景都可以减少回归和bug的数量。


显示1评论

Baidu
map