4、页面对象模式 页面对象模式使得测试用例的可扩展性变得更好。页面对象模式可以使得测试用例的结构变得简单明了,因为每个页面对象中包含了该页面的所有操作。举个例子,登录页面知道怎么确认登录,点击忘记密码,用google账号注册等等。所有的测试用例都可以使用页面的操作(即调用页面对象的方法)。因为测试用例是由不同的测试开发人员编写的,在我们的产品中,你可以有很多不同的方法去实现相同的操作。就好像在Lucidchart的文档页面上选择一个文档。我们可以写6种css 路径去定位一个文档,有三种方法去点击一个文档。我们有50个用例会用到上述的操作。假如没有使用页面对象模式,维护这五十个用例对我们来说将会是一场噩梦。下面的例子就是我们文档页面的页面对象。 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由于我们要执行很多操作,我们把它拆分成了多个子页面对象。这些子的页面对象组合起来就是一个完整的页面。
每一个小的页面对象都包含了它代表的页面中的所有操作,举个例子,在文件浏览的子页面对象,我们有个方法用来点击“创建文档”按钮,选择一个文档模板,然后校验文档库中文档数的用例。代码如下: 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 }}这使得我们把原先晦涩难懂的测试用例变的简洁明了,任何人都可以轻松地读懂它。
因为使用了页面对象模型,我们的测试框架很容易维护和扩展。每当一个feature被更新的时候,我们要做的仅仅是去更新页面对象,而测试开发人员很清楚需要去修改哪一个部分。当我们需要编写测试用例去覆盖新场景的时候,我们只需要把以前写过的功能点重新组合起来就可以了。原先需要两三个小时的活现在只需要10分钟就可以搞定了! 使测试用例更可靠 误报通常是我们在使用selenium的最头疼的问题,这使得很难把selenium测试用例加入到自动构建中。有些构建是必须要成功的,如果失败将会阻塞整个发布流程。然而并没有人会乐意处理构建中的报错。在Lucid,如何避免误报是我们让selenium变得有价值的头等大事。我们的解决方案是在测试步骤和测试集中都加入重试机制。 5、重试操作 产生误报最大原因是selenium在页面加载完成之前就开始请求页面资源,举个栗子~selenium打开了点击按钮去打开一个弹窗。在js还没有执行完成,弹窗还没有打开的时候,selenium就已经要去使用它。这当然就会各种报错了,element not found exception、element notclickable ....... 解决这个问题最原始的方法就是加等待了。什么?还是找不到element?等待时间加长!这种方法用的多了,我们的代码看上去就很蠢。不单单是蠢,很多时候浏览器早就加载完了,然而测试代码还在等待中。本来selenium执行用例的速度就比较慢,现在更是雪上加霜。 那么怎么改善这一现状呢?我们首先想到的是使用selenium自带的解决方案(FluentWait,Explicit Waits, and Implicit Waits),但在我们的应用中,这并不能满足所有的需求,那么就只有自己动手了。 /*** 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.tailrecprivate 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) } } }}我们自己写的重写代码以一个递归算法为基础,包含最大等待时间和轮询时间这两个参数。这个方法将会不停地执行下去,直到方法执行成功或者超过我们设置的最大等待时间。根据不同的情况,我们以这个方法为原型另外实现了三个方法[4] 1、需要在重试方法中获取返回值 def numberOfChildren(implicit user: LucidUser): Int = { getWithRetry() { user.driver.getCssElement(visibleCss).children.size }}2、不需要重试方法提供返回值 def clickFillColorWell(implicit user: LucidUser) { doWithRetry() { user.clickElementByCss("#fill-colorwell-color-well-wrapper") }3、通过返回一个Boolean值判断执行结果,发现失败则继续重试 def onPage(implicit user: LucidUser): Boolean = { predicateWithRetry() { user.driver.getCurrentUrl.contains(pageUrl) }}通过这三个方法,我们大致可以把误报率降到2%以下。我们所有的方法默认最大等待时间为1秒,轮询时间为50毫秒。所以重试方法对测试用例执行速度的影响是微乎其微的。在我们最好的一次实践中,我们把一个测试用例的误报率从10%降低到0,并且执行时间从原先的45秒降低到33秒。
6、测试重试 在提升测试用例可靠性的努力中,我们最后所做的工作就是测试重试。所谓测试重试就是把执行失败的用例重新执行。只要在随后的重试中有一次成功,我们就认为此项测试是通过的。我们敢这么做,是因为假使这个用例真的是因为应用中的bug才执行失败的,那么不管你执行多少次肯定都是错误的。 在Lucid,我们尽可能保守地使用测试重试。频繁的误报可能意味这测试用例写的太烂了。但有时候确实没有必要耗费很大的精力去使一个测试用例变得很健壮。对我们来说,我们不会在依赖第三方服务的用例上耗费过多的精力。我们可以把这些测试用例写的更好,但去修正这些只是偶尔发生的误报并没有太大的价值。重试并不能修正一个测试用例,却能消除误报对于测试报告的影响。 在使用中找到乐趣 我第一次使用selenium的时候是非常痛苦的,我的测试用例经常会莫名其妙地失败。然而修改他们令人感到无聊而烦躁。测试用例很多都是重复的并且很难写。并不是只有我一个人有这种感觉,整个公司的测试开发人员都这么认为。在新feature要上线之前编写selenium测试用例让我们感到头疼。 通过使用selenium测试集,我们在开发的过程中,每周都能完成好几次回归测试。在我们的测试结果中,只有1%的误报率。我们已经见证了过去几个月在使用上述方法之后,我们的测试集在可靠性、可维护性、可伸缩性上取得的长足进步。在过去的几周里,我们的selenium测试用例的数量每天都在增长,在我们提高软件质量的工作中扮演着越来越重要的角色。
|