P age Object模式是一种设计模式,在测试自动化中流行,可以减小测试维护的成本和代码量。 页面对象是面向对象的类,封装了页面的属性和方法供其他业务调用。 如果页面的UI更改,测试业务本身不需要改变,只需要修改对象类中的代码就行
一、Page Object设计模式好处
封装页面类,提供页面服务,创建可在多个测试用例间的共用代码
减小代码重复的数量,提高代码的易维护性
如果页面元素发生改变,不需要对业务进行更改,只需要更改页面类
二、传统测试用例和Page Object设计模式实践
假设现在我们有个自动化用例需求,验证CSDN登陆功能:
通过账号密码登陆方式,输入正确的用户名和密码登陆
通过账号密码登陆方式,输入错误的用户名和密码登陆
首先我们用传统的方式写测试用例,继承unittest类
# coding = utf-8from selenium import webdriverimport unittestimport timeclass test_csdn_login(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome() self.driver.maximize_window() self.csdn_login_url = 'https://passport.csdn.net/login?code=public' print("测试用例初始化完毕======") # 输入正确密码登陆用例 def test_csdn_correct_login(self): self.driver.get(self.csdn_login_url) #定位用户名密码方式登陆方式的位置 user_password_login_ele = self.driver.find_element_by_partial_link_text("账号密码登录") #点击用户名密码方式 user_password_login_ele.click() #定位用户名输入框 user_element = self.driver.find_element_by_id('all') user_element.send_keys("您的用户名") #定位密码输入框 password_ele = self.driver.find_element_by_id('password-number') password_ele.send_keys("您的正确密码") time.sleep(3) login_button_ele = self.driver.find_element_by_xpath( '//*[@id="app"]/div/div/div[1]/div[2]/div[5]/div/div[6]/div/button') login_button_ele.click() time.sleep(3) # 假设csdn登陆成功页面的title是登陆成功 assert self.driver.title,"登陆成功" time.sleep(60) #输入错误密码登陆用例 def test_csdn_incorrect_login(self): self.driver.get(self.csdn_login_url) user_password_login_ele = self.driver.find_element_by_partial_link_text("账号密码登录") user_password_login_ele.click() user_element = self.driver.find_element_by_id('all') user_element.send_keys("您的用户名") password_ele = self.driver.find_element_by_id('password-number') password_ele.send_keys("您的错误密码") time.sleep(3) login_button_ele = self.driver.find_element_by_xpath( '//*[@id="app"]/div/div/div[1]/div[2]/div[5]/div/div[6]/div/button') login_button_ele.click() time.sleep(3) #断言当前页面的输入框内容是"您的用户名",表示未登陆成功 assert self.driver.find_element_by_id('all').get_attribute("value"),'您的用户名' def tearDown(self): self.driver.close() print("测试用例清理完毕======")
我们已经完成了用正确用户名密码登陆和用错误用户名密码登陆两个测试用例。如果在未来这个页面元素的id或者xpath变动或者页面重构了,元素定位需要重新写过,介于目前只有两个用例你觉得工作量可以接受。但是后续UI用例越来越多,并且都需要依赖用户登陆时,我们来看下以这种方式产生的维护结果。显而易见每增加一个用例就需要额外多一份维护
接下来看使用Page object设计用例的一个流程
用例统一通过调用登陆类的登陆方法来实现登陆,假如未来登陆的页面重构了,或者元素位置发生改变了,我们无需逐一修改用例的步骤,只需要修改登陆类的登陆方法。用例越多这种维护成本的减少就越明显,并且用例可读性也更高
现在开始实践如何使用Page Obejct设计模式,首先我们新建一个page文件夹用来保存页面对象类,再新建一个testcase文件夹用来保存测试用例类,完整目录结构如下
接下来在page文件夹下新建BasePage.py,BasePage类作为未来其他Page对象类的父类,可根据需要对selenium库提供的方法进行二次封装。代码如下
import timefrom selenium import webdriverfrom selenium.webdriver.support.wait import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECclass BasePage: def __init__(self,driver:webdriver=None): if driver is None: self._driver = webdriver.Chrome() else: self._driver = driver ''' 二次封装元素定位方法,确保执行前页面元素可用,否则抛出自定义错误。增加代码的健壮性 和之前讲过的八大定位元素效果一样,写法不一样。参数为元祖,例如(By.ID,"kw")表示定位id属性为"kw"的元素 ''' def find_ele(self, *loc): # return self.driver.find_element(*loc) try: # 确保元素是可见的。 # 注意:以下入参为元组的元素,需要加*。Python存在这种特性,就是将入参放在元组里。 #ebDriverWait(self.driver,10).until(lambda driver: driver.find_element(*loc).is_displayed()) # 注意:以下入参本身是元组,不需要加* WebDriverWait(self._driver, 10).until(EC.visibility_of_element_located(loc)) return self._driver.find_element(*loc) except: print(" 页面中未能找到元素") def close(self): time.sleep(10) self._driver.quit()
下一步在page文件夹下创建CSDN登陆界面对应的CsdnLoginPage类。本次示例只封装了通过用户名和密码登陆的方式登陆的服务(方法)供其他调用,你可在本例扩展通过其他方式登陆的服务(方法),代码如下
# coding = utf-8from page.BasePage import BasePagefrom selenium.webdriver.common.by import Byimport timeclass CsdnLoginPage(BasePage): csdn_login_url = 'https://passport.csdn.net/login?code=public' user_password_login_loc = (By.LINK_TEXT,"账号密码登录") user_element_loc = (By.ID,"all") password_ele_loc = (By.ID,"password-number") login_button_ele_loc = (By.XPATH,'//*[@id="app"]/div/div/div[1]/div[2]/div[5]/div/div[6]/div/button') # 通过用户名和密码方式登陆 def login_by_username_and_password(self,username,password): self._driver.get(self.csdn_login_url) user_password_login_ele = self.find_ele(*self.user_password_login_loc) user_password_login_ele.click() user_element = self.find_ele(*self.user_element_loc) user_element.send_keys(username) password_ele = self.find_ele(*self.password_ele_loc) password_ele.send_keys(password) time.sleep(3) login_button_ele = self.find_ele(*self.login_button_ele_loc) login_button_ele.click() time.sleep(3)
我们已经封装好了通过用户名和密码方式登陆的服务,现在我们在testcase文件夹下中新建test_csdn_login.py,完成正确用户名密码登陆和用错误用户名密码登陆两个测试用例编写。示例如下:
# coding = utf-8from selenium import webdriverimport unittestimport timefrom page.CsdnLoginPage import CsdnLoginPageclass test_csdn_login(unittest.TestCase): def setUp(self): print("测试用例开始======") self.csdn_login_page = CsdnLoginPage() # 输入正确密码登陆用例 def test_csdn_correct_login(self): self.csdn_login_page.login_by_username_and_password("您的用户名","正确密码") assert self.csdn_login_page._driver.title,"登陆成功" #输入错误密码登陆用例 def test_csdn_incorrect_login(self): self.csdn_login_page.login_by_username_and_password("您的用户名", "错误密码") assert self.csdn_login_page.find_ele(*self.csdn_login_page.user_element_loc).get_attribute("value"),'您的用户名' def tearDown(self): self.csdn_login_page._driver.quit() print("测试用例清理完毕======")
运行代码看看效果。由于CSDN网站的防御机制,我们通过自动化脚本登陆成功后会跳转到手机验证登陆的界面。这都是小问题,重点是今天讲的Page Object设计用例的编写方法和理解这种分层思想
三、Page Object设计模式总结
今天学习了如何使用Page Object设计模式,再来回顾一下编写流程:
编写BasePage类,根据业务需要二次封装基本方法,增加优化提示,打印日志等方法,提升用例的异常可读性和稳定性,
编写Page对象类,封装该页面的操作,提供对外公共的方法
编写测试类,根据业务流程调用Page对象类的服务而无需额外实现,并在用例中增加断言
如果一时无法理解这种设计模式,多实践这种模式。当用例越多时这种设计模式的优势越加明显,越容易体会这种模式的分层思想。
如果你喜欢我的文章,关注下方公众号,获取更多知识