做过UI的自动化测试都知道,测试是对元素的操作,因此特别依赖元素的定位。但是这就会带来一个很大的问题—“维护成本”,如果你在不同的测试用例脚本中使用的一个元素的定位多次,当该元素的位置或者属性发生变化,就必须查找到脚本中对应的使用的位置进行修改。
Page Object
Page Objects模式是一种测试设计模式,它可以指整个页面,也可以指页面上的某个区域。
Page Object将页面定位和业务操作分开,分离测试对象(元素对象)和测试脚本(用例脚本),提高用例的可维护性。
Page Object的优点
- 减少了代码的重复
- 让测试更具可读性和强大的
- 提高了测试的可维护性
实例
下面以“百度搜索”为例详细说明,Page Object的原理:
1、项目目录:
.
├── pages
│ ├── BaiduPage.py
│ ├── BasePage.py
│ └── __init__.py
├── report
├── run.py
├── testcases
│ └── Test_Baidu.py
└── utils
├── PublicMethod.py
├── __init__.py
└── configfile.py
pages:文件夹下存放测试Page Object的基类和对应的测试页面
report:通过HTMLTestRunner生成的测试报告存放位置
testcases:存放具体的测试脚本,以Test开否的.py文件
utils:存放公共的方法(PublicMethod)和配置文件(configfile)
run.py:从testcases中获取测试用例集,并执行的脚本文件
2.1、pages-定义页面基础类BasePage.py
# -*- coding:utf-8 -*-
class BasePage(object):
"""
This is a base page class for Page Object.
"""
def __init__(self, driver):
self.driver = driver
2.2、pages-定义百度搜索页面的Page Object(BaiduPage.py)
所有页面元素定位都在此层定义,UI一旦有更改,只需在修改这一层页面对象属性即可。
# -*- coding:utf-8 -*-
import BasePage
class Baidu_Page(BasePage.BasePage):
search_box = "id>>>kw" # 通过id定位搜索框
search_button = "id>>>su" # 通过id定位"百度一下"button
# 输入搜索内容到搜索框
def input_search_text(self, text):
input_box = self.driver.get_element(self.search_box)
input_box.send_keys(text)
# 点击搜索按钮
def click_search_button(self):
self.driver.get_element(self.search_button).click()
3、testcases-具体的测试用例
# -*- coding:utf-8 -*-
import unittest, time, sys
sys.path.append("..")
from utils.PublicMethod import PublicMethod
from utils.configfile import env_info
from pages import BaiduPage
class baidutest(unittest.TestCase):
def setUp(self):
# 实例化utils下PublicMethod类,其下包含多个Webdriver二次封装的方法
self.driver = PublicMethod(env_info['browser'])
self.driver.wd.implicitly_wait(30)
self.driver.max_window()
def test_01_search_selenium(self):
self._search_content(u'百度搜索Selenium', 'Selenium', u'Selenium_百度搜索')
def test_02_search_python(self):
self._search_content(u'百度搜索Python', 'Pyhon', u'python_百度搜索')
def tearDown(self):
self.driver.quit()
# 抽象的百度搜索的私有方法,description为case说明、text待搜索的内容、expected期望结果
def _search_content(self, description, text, expected):
driver = self.driver
driver.open_link(env_info['url'])
print 'case description: ', description
baidupage = BaiduPage.Baidu_Page(driver)
baidupage.input_search_text(text)
baidupage.click_search_button()
time.sleep(1)
self._verify_text(expected)
# 验证搜索结果的页面title
def _verify_text(self, expacted):
actual_text = self.driver.get_title()
expact_text = expacted
if actual_text == expact_text:
print 'PASS-----', 'Actual Result:', actual_text
else:
print 'FAIL-----', 'Expect Result:', expact_text, '***Actual Result:', actual_text
if __name__ == "__main__":
suite = unittest.TestSuite()
suite.addTest(baidutest('test_01_search_selenium'))
suite.addTest(baidutest('test_02_search_python'))
unittest.TextTestRunner().run(suite)
采用的是unittest的单元测试框架,封装了两个私有方法_search_content(测试用例的抽象方法)和_verify_text(验证结果并打印,也可以采用unittest提供的断言方法)
4、run.py-运行测试用例集的方法
# -*- coding:utf-8 -*-
import unittest
import HTMLTestRunner
def get_test_cases(dirpath):
test_cases = unittest.TestSuite()
suites = unittest.defaultTestLoader.discover(dirpath, 'Test*.py', top_level_dir=dirpath)
for suite in suites:
test_cases.addTests(suite)
return test_cases
if __name__ == '__main__':
cases = get_test_cases('testcases')
filename = 'report/testReport.html' # 设置报告文件名
f = file(filename, 'wb')
runner = HTMLTestRunner.HTMLTestRunner(stream=f, title=u'Page Object Demo Test', description=u'详细测试结果如下:')
runner.run(cases)
定位到项目目录下,运行python run.py
等待cases suites运行结束后,会在result下生成testReport.html的测试报告。
部分代码未完全贴出(PublicMethod和configfile的代码),全部代码请查看:https://git.oschina.net/ljzhangi/pageobjectdemo.git
另外的:
1. Page Object的一个基本经验法则是,凡是人类能做的事,page对象通过软件客户端都能够做到。它也应当提供一个易于编程的接口并隐藏窗口中低层的部件。所以访问一个文本框应该通过一个访问方法(accessor method)来实现字符串的获取与返回,复选框应当使用布尔值,按钮应当被表示为行为导向的方法名。
2. page 对象应当将在 GUI 控件上所有查询和操作数据的行为封装为方法。一个好的经验法则是, 即使改变具体的控制,page对象的接口也不应当发生变化。
3. 尽管该术语是”页面“对象,并不意味着针对每个页面建立一个这样的对象,比如页面有重要意义的 元素可以独立为一个 page 对象。经验法则的目的在于通过给页面建模,从而对应用程序的使用者变得有意义。