【unittest单元测试框架】(9)认识Page Object
认识Page Object
Page Object 是 UI 自动化测试项目开发实践的最佳设计模式之一,它的主要特点体现在对界面交互细节的封装上,使测试用例更专注于业务的操作,从而提高测试用例的可维护性。 当为 Web 页面编写测试时,需要操作该 Web 页面上的元素。然而,如果在测试代码中直接操作 Web 页面上的元素,那么这样的代码是极其脆弱的,因为 UI 会经常变动。 page 对象的一个基本经验法则是:凡是人能做的事,page 对象通过软件客户端都能做到。因此,它应当提供一个易于编程的接口,并隐藏窗口中底层的部件。当访问一个文本框时,应该通过一个访问方法(Accessor Method)实现字符串的获取与返回,复选框应当使用布尔值,按钮应当被表示为行为导向的方法名。page 对象应当把在 GUI 控件上所有查询和操作数据的行为封装为方法。 一个好的经验法则是,即使改变具体的元素,page 对象的接口也不应当发生变化。 尽管该术语是 page 对象,但并不意味着需要针对每个页面建立一个这样的对象。例如,页面上有重要意义的元素可以独立为一个 page 对象。经验法则的目的是通过给页面建模,使其对应用程序的使用者变得有意义。 Page Object 是一种设计模式,在自动化测试开发中应遵循这种设计模式来编写代码。 Page Object 应该遵循以下原则进行开发:- Page Object 应该易于使用。
- 有清晰的结构,如 PageObjects 对应页面对象,PageModules 对应页面内容。
- 只写测试内容,不写基础内容。
- 在可能的情况下防止样板代码。
- 不需要自己管理浏览器。
- 在运行时选择浏览器,而不是类级别。
- 不需要直接接触 Selenium。
# -*- coding:utf-8 -*- # filename: base.py # author: hello.yin # date: 2021/11/17 15:50 import time class BasePage: """ 基础page层,封装一些常用方法 """ def __init__(self, driver): self.driver = driver # 打开页面 def open(self, url=None): if url is None: self.driver.get(self.url) else: self.driver.get(url) # id定位 def by_id(self, id_): return self.driver.find_element_by_id(id_) # xpath定位 def by_xpath(self, xpath): return self.driver.find_element_by_xpath(xpath) # class定位 def by_class(self, class_name): return self.driver.find_element_by_class_name(class_name) # name定位 def by_name(self, name): return self.driver.find_element_by_name(name) # css定位 def by_css(self, css): return self.driver.find_element_by_css_selector(css) # 获取title def get_title(self): return self.driver.title # 获取页面text def get_text(self, xpath): return self.by_xpath(xpath).text # 执行js脚本 def js(self, script): self.driver.execute_script(script)
创建 BasePage 类作为所有 Page 类的基类,在 BasePage 类中封装一些方法,这些方法是我们在做自动化时经常用到的。
- open()方法用于打开网页,它接收一个 url 参数,默认为 None。如果 url 参数为None,则默认打开子类中定义的 url。稍后会在子类中定义 url 变量。
- by_id()和 by_name()方法。我们知道,Selenium 提供的元素定位方法很长,这里做了简化,只是为了在子类中使用更加简便。
- get_title()和 get_text()方法。这些方法是在写自动化测试时经常用到的方法,也可以定义在 BasePage 类中。需要注意的是,get_text()方法需要接收元素定位,这里默认为 XPath 定位。
# -*- coding:utf-8 -*- # filename: baidu_page.py # author: hello.yin # date: 2021/11/17 16:12 from base import BasePage class BaiduPage(BasePage): """ 百度 Page 层,百度页面封装操作到的元素""" url = "https://www.baidu.com" def search_input(self, search_key): self.by_id("kw").clear() self.by_id("kw").send_keys(search_key) def search_button(self): self.by_id("su").click()创建 BaiduPage.py 类继承 BasePage 类,定义 url 变量,供父类中的 open()方法使用。这里可能会有点绕,所以举个例子:小明的父亲有一辆电动玩具汽车,电动玩具汽车需要电池才能跑起来,但小明的父亲并没有为电动玩具汽车安装电池。小明继承了父亲的这辆电动玩具汽车,为了让电动玩具汽车跑起来,小明购买了电池。在这个例子中,open()方法就是“电动玩具汽车”,open()方法中使用的 self.url 就是“电池”,子类中定义的 url 是为了给父类中的 open()方法使用的。 在 search_input()和 search_button()方法中使用了父类的 self.by_id()方法来定位元素,比原生的 Selenium 方法简短了不少。 在测试用例中,使用 BaiduPage 类及它所继承的父类中的方法。 因为前面封装了元素的定位,所以在编写测试用例时会方便不少,当需要用到哪个 Page类时,只需将它传入浏览器驱动,就可以使用该类中提供的方法了。
# -*- coding:utf-8 -*- # filename: test_BaiduPage.py # author: hello.yin # date: 2021/11/17/ 16:23 import unittest from time import sleep from selenium import webdriver from baidu_page import BaiduPage class TestBaidu(unittest.TestCase): @classmethod def setUpClass(cls): cls.driver = webdriver.Firefox() def test_baidu_search(self): page = BaiduPage(self.driver) page.open() page.search_input("selenium") page.search_button() sleep(3) self.assertEqual(page.get_title(), "selenium_百度搜索") @classmethod def tearDownClass(cls): cls.driver.quit() if __name__ == "__main__": unittest.main(verbosity=2)
执行结果:
Testing started at 16:32 ... C:\Users\yzp\AppData\Local\Programs\Python\Python37\python.exe "C:\Program Files\JetBrains\PyCharm 2018.2\helpers\pycharm\_jb_unittest_runner.py" --path D:/00test/base_practice/page_object/page/test_BaiduPage.py Launching unittests with arguments python -m unittest D:/00test/base_practice/page_object/page/test_BaiduPage.py in D:\00test\base_practice\page_object\page Ran 1 test in 11.866s OK Process finished with exit code 0