引言
前几天,我在阅读一些Selenium文档时偶然发现了一个奇怪的声明。上面写着:
“页面对象本身绝不应该进行验证或断言测试。因为这是测试的一部分,应该始终放在测试代码中,而不是在页面对象中。 ”
这句话让我的感觉很复杂(™),我决定更深入地研究一下。于是就有了这篇文章。©
那么...使用POM的时候,我们需要弄清楚下面这几个情况:
- 没有断言,从来没有?
- 也许有一些断言。
- 是的,有断言,总是有!
注意:本文使用“伪代码”示例(JavaScript)。请不要关注语法。:-)
POM 是什么?
为了使大家理解一致,让我们从“页面对象(PO)”的定义开始!有一种定义是:
页面对象是一段代码,它知道特定网页上所有重要内容(按钮在哪里,输入在哪里,以及如何与它们交互)。
您将所有的复杂性(又名“内部html详细信息”)隐藏在类的属性和方法里面。然后您从测试用例中调用这些方法。
这有助于:
- 编写测试更容易(保持一致性和更少的重复性)
- 提高维护速度(选择器变了吗?更新一行代码就可以修复!)
这个定义有些太难,太冗长了...让我们简化它吧!页面对象其实就是一个设计模式,它提供了一个方便的接口来指导测试的实施!
翻译过来就是:
当创建测试时,您将输入“pagename dot ...”,然后选择您想要做什么。这些就是接口提供的选项。它们指导你能做什么和不能做什么。
这样您便能创建更多可重复/可重用/可读(和其他R 开头的词语)测试!
断言困惑
如果这指的只是一个便利的接口,为什么他们说“永远不应该进行验证或断言”?如果我们把断言移到测试中,会更方便,对吗?对吧??
也许困惑来自于“断言”概念(在测试的背景下)的不清晰。
让我们来定义它:测试断言指的是决定成功或者失败的逻辑检查。
是时候看一些代码了!
让我们试着证明(或反驳)我们是否应该在POM中使用断言?
想象一下,我们有一个“TODO列表”网站应用程序。我们创建了它的页面对象类:它包含addltem选择器,在方法中使用了断言:
class TodoPage {
#todoItemSelector = '.todo-add-item';
addItem(item) {
//1. 添加item的代码
//2. 验证item已添加的代码
} }
第“2”步是不是看起来可疑?嗯,这篇文章的目的就是为了审查它!让我们先假设把验证放在这里是可以的。
当我们在测试中使用该页面对象时,它看起来是下面这样的:
it('Add item', () => {
todo.addItem('Run far')
})
在上面的示例中,方法名称并没有体现其中有隐藏的断言!这不太好...重新命名会更好吗?会让它更明确吗?比如下面这样:
it( 'Add item', () => {
todo.addAndValidateItem ' Run far') })
好些了吗?好一些了..但这里仍然有一个问题:测试中没有断言。你甚至能称它为“测试”吗?
但这容易修复!让我们在测试中添加一个断言(伪代码)!
it( 'Add item', () => {
todo. addItem 'Run far')
// const item = find by "Run far"
// expect (item).toExist()
})
哈!我们修复了一个问题,但又制造了两个新问题:
- 一个是断言重复!(一个在addltem中,一个在测试中)
- 另一个是将HTML的内部“细节”泄露到测试中。
对于第一个问题,我们可以清理页面对象类并删除验证部分。看起来是下面这样的:
class TodoPage {
#todoItemSelector = '.todo-add-item';
addItem(item) {
//1. 添加item的代码
}}
对于第二个问题,我们可以在页面对象(PO)类中添加一个名为“checkltemExists”的新方法:
class TodoPage {
#todoItemSelector = '. todo-add-item';
addItem(item) {
//1. 添加item的代码 }
checkItemExists(item) {
//2.验证item已添加的代码, 比如:
// const itemFound = find by 'item'
// expect (itemFound). toBe(true) } }
因此,我们的测试可能看起来像这样:
it( 'Add item', () →> {
const item = 'Run far'
todo. addItem(item)
todo.checkItemExists(item)
})
我们的证明错了吗?
乍一看,我们似乎将断言添加到了页面对象类中,它看起来是正确的!?
这是否意味着编写Selenium文档的人错了?
不一定...
有一个断言库被引入页面对象(PO)类中(expect 语句)。
有些人可能会说这很糟糕,它违反了SOLID 原则(或还有一些其他首字母缩写的词)。
也许你可以重写代码来“返回找到的对象”,并在测试中进行断言?但这样会降低测试的可读性…
另一方面,您也可以争辩说,页面对象类及其方法的目的(和责任)是表示页面以及用户可以用它做什么。
当然,用户(或您的测试)将能验证对象是否存在于页面上!
因此,在方法中断言是checkltemExists的“目的”。从字面上看,这就是它存在的原因!
在我看来,决定用那种类型是一个“灰色地带”,是有一个选择的范围。
您可以选择任何一种方式,并且都有“某种意义”的正确性。
请遵循你自己的判断,只是要注意使用统一的模式。
也许从一开始,这可能只是个人选择,而当使用现有/既定模式时是没有选择的。
显式和隐式断言
我们还遗漏了另外一个点,就是如果我们将断言分为下面的子类别呢:
- 显式类型(对测试及其可读性很重要)
- 隐式类型(对测试套件维护和稳定性(又名脆弱性)很重要)
我们在测试(checkltemExists)方法中已经有一个明确的断言,我们可以做些什么来添加一个隐式断言?
也许有一个“网络监控”来确保HTTP请求“POST /items”API调用(保存项目)成功?
class TodoPage {
#todoItemSelector = '. todo-add-item';
addItem(item) {
//1. 添加 item 的代码
//2. 监听网络调用并检查调用是否成功(2XX 代码)
}
checkItemExists(item) {
//3. 验证 item 是否添加的代码
} }
在上面的代码中:
第1步是主要的动作。
第2步是技术性断言(隐式类型)。我们本身不需要它,但它可能有助于避免脆弱性。
第3步主要的断言(显式类型)。我们把它放在我们的测试中,以使它变得友好和可读。
还有一件事
我见过一些文章极力拥护在POM中“不使用断言”。它们也经常引用Martin Fowler的某篇文章。Martin Fowler确实说过:“我赞成在页面对象中没有断言”。
但这些文章没有注意到的是,Martin Fowler还进一步阐述了这一观点。
首先,他提到有2个阵营:支持断言的和反对断言的。
其次,他在一个脚注里也提到:
- 有一种形式的断言其实很好,即使对于像我这样通常喜欢无断言风格的人来说也是如此。
- 这类断言是用来检查页面或应用程序的不变性,而不是测试正在探测的内容。
对照着翻译/改编一下我们的“添加新项目”例子:
- 这里可以检查HTML是否更改(例如现在持有对象的表/列表是否有N+1行,或检查类似于我们上面对网络调用所做的那样)。
- 但您的测试应该检查对象是否被添加并出现在页面上。
总结一下
总体来说(TLDR;):
- 有一些断言是可以放在页面对象里面的。不会有“页面对象警察”来追捕你!
- 此外,有更重要的问题需要你解决,所以不要太关注这样的事情。在巨大的问题海洋中,这是一件小事。
既然你读到了最后,我想说声谢谢!