Pytest 测试框架
把豆瓣登录的用例结合 Pytest 来编写如下:
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
import pytest
douban_account = {"username":"****", "password":"****"}
def login(driver, account):
driver.get("https://www.douban.com/")
login_frame = driver.find_element_by_css_selector("div.login iframe")
WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it(login_frame))
driver.find_element_by_css_selector("li.account-tab-account").click()
driver.find_element_by_css_selector("#username").send_keys(account["username"])
driver.find_element_by_css_selector("#password").send_keys(account["password"])
driver.find_element_by_css_selector(".account-form-field-submit").click()
time.sleep(2)
driver.switch_to.default_content()
class TestDouban(object):
def setup_class(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
def teardown_class(self):
self.driver.quit()
def test_login(self):
login(self.driver, douban_account)
assert WebDriverWait(self.driver, 10).until(EC.visibility_of_element_located((By.CLASS_NAME, "nav-user-account")))
if __name__ == "__main__":
pytest.main()
这时候,如果元素的位置发生了变化,需要修改定位方式,那么所有用到这个元素的测试用例都需要修改,代码维护程度依旧很高,这时候我们就需要用到 Page Object(PO)模式来重构自动化脚本。
02
PO 模型简介
如果 100 个不同的脚本使用了相同的页面元素,只要该元素做了任何更改,那么就需要改动所有的脚本,不仅耗时,而且容易出错。
为了解决这个问题,我们可以为每个 Web 页面创建一个单独的类文件,封装该 Web 页面的元素以及操作,那么后续需要用到该 Web 页面元素或操作的脚本都可以调用这个类。如果该 Web 页面元素发生了变化,只需要修改相应的类文件即可,这种方式就是页面对象模型(POM),它有助于提高代码的可读性、可维护性和可重用性。
优点:
·减少代码的重复
·提高测试用例的可读性
·提高测试用例的可维护性
PO的实现:将单个页面上所有Web元素和在这些Web元素的操作方法都放在一个Page类文件中(所有的page文件可以统一放到 page_object 模块),而用于验证的测试用例则单独作为测试方法的一部分(可以放到 test_case 模块)。我们以登录豆瓣为例:
文件结构:
示例代码:
1. 首先创建一个 demo_login_page.py,封装登录页面的元素和操作
import time
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
class LoginPage:
def __init__(self, driver):
self.driver = driver
def open_url(self, url):
self.driver.get(url)
def login(self, account, password):
login_frame = self.driver.find_element(*("css selector", "div.login iframe")) # 切换 iframe
WebDriverWait(self.driver, 10).until(EC.frame_to_be_available_and_switch_to_it(login_frame))
self.driver.find_element(*("css selector", "li.account-tab-account")).click() # 点击密码登录
self.driver.find_element(*("id", "username")).send_keys(account) # 输入账号
self.driver.find_element(*("id", "password")).send_keys(password) # 输入密码
self.driver.find_element(*("css selector", ".btn-account")).click() # 点击登录
time.sleep(2)
self.driver.switch_to.default_content()
注:find_element(*("css selector", "div.login iframe")) 等同于 find_element_by_css_selector("div.login iframe")
2. 首先创建一个 demo_home_page.py,封装首页的元素和操作(如获取登录账号信息,判断是否登录成功)
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class HomePage:
def __init__(self, driver):
self.driver = driver
def is_account_info_exist(self):
try:
WebDriverWait(self.driver, 30).until(EC.visibility_of_element_located(("class name", "nav-user-account")))
return True
except Exception:
return False
3. 在 test_demo.py 中编写测试用例,所有的元素获取和操作都调用对应的页面类来实现
import pytest
from selenium import webdriver
from po.page_object.demo_home_page import HomePage
from po.page_object.demo_login_page import LoginPage
class TestLogin():
def setup_class(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.implicitly_wait(5)
def teardown_class(self):
self.driver.quit()
def test_demo(self):
self.login_page = LoginPage(self.driver)
self.home_page = HomePage(self.driver)
self.login_page.open_url("https://www.douban.com/")
self.login_page.login("****", "****")
assert self.home_page.is_account_info_exist()
if __name__ == "__main__":
pytest.main()
这样,无论 login 、home 页面元素如何变化,都只需要修改相应页面中的代码即可,大大降低了自动化脚本的维护成本及因脚本更新引发的出错概率。
03
优化
PO 模式让我们仅需要在Page页面类单个文件中对变化的元素定位进行更新,但是一个项目一般都有很多个页面,如果页面都发生了变化仍然需要修改多个页面类文件,那是否尽可能使任何一个自动化脚本文件都不发生变更呢?很简单,只需要把这些定位作为数据单独存放在配置文件中即可(配置文件可以是 ini、csv、excel 等),以 ini 配置文件为例
文件结构:
示例代码:
1. page_object 模块用来存放页面操作
demo_login_page.py 用来存放登录页面的操作
import os
import time
from configparser import ConfigParser
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
class LoginPage:
def get_locator(self):
conf = ConfigParser()
conf.read(os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "\\page_locator\\demo_locator.ini", encoding="utf-8")
self.url = conf.get("Base", "url")
section = "LoginPageLocator"
# eval 将字符串元组转换为元组
self.login_frame = eval(conf.get(section, "login_frame"))
self.login_by_pwd_btn = eval(conf.get(section, "login_by_pwd_btn"))
self.account = eval(conf.get(section, "account"))
self.pwd = eval(conf.get(section, "pwd"))
self.login_btn = eval(conf.get(section, "login_btn"))
def __init__(self, driver):
self.driver = driver
self.get_locator()
def open_url(self):
self.driver.get(self.url)
def input(self, locator, value):
self.driver.find_element(*locator).send_keys(value)
def click(self, locator):
self.driver.find_element(*locator).click()
def change_frame(self, locator):
frame = self.driver.find_element(*locator)
WebDriverWait(self.driver, 10).until(EC.frame_to_be_available_and_switch_to_it(frame))
def login(self, account, password):
self.change_frame(self.login_frame) # 切换iframe
self.click(self.login_by_pwd_btn) # 点击密码登录
self.input(self.account, account) # 输入账号
self.input(self.pwd, password) # 输入密码
self.click(self.login_btn) # 点击登录
time.sleep(2)
self.driver.switch_to.default_content() # 切换到原来的页面
def logout(self):
pass
以上脚本将页面上的不同操作都封装成了方法,使脚本看起来更为简洁,但是如果页面很多,每个页面都要封装点击、输入的操作会特别繁琐,可以把所有的公共方法抽离出来,封装到一个公共模块中,将在下一节详细介绍~~
demo_home_page.py 用来存放home页面的操作
import os
from configparser import ConfigParser
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class HomePage:
def get_locator(self):
conf = ConfigParser()
conf.read(os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "\\page_locator\\demo_locator.ini", encoding="utf-8")
section = "HomePageLocator"
self.account_info = eval(conf.get(section, "account_info"))
def __init__(self, driver):
self.driver = driver
self.get_locator()
def is_account_info_exist(self):
try:
WebDriverWait(self.driver, 30).until(EC.visibility_of_element_located(self.account_info))
return True
except Exception:
return False
注:ConfigParser 模块用于读取配置文件,用法请自行百度。
2. page_locator 模块用来存放通用地址和页面元素的定位,如 demo_locator.ini 用来存放登录页面的元素定位等信息,可以把多个页面的 locator 都存放在一个 ini 文件中,这样只需要修改 ini 文件即可
[Base]
url = https://www.douban.com/
;"id"、"xpath"、"link text"、"partial link text"、"name"、"tag name"、"class name"、"css selector"
[LoginPageLocator]
# iframe
login_frame = ("css selector", "div.login iframe")
# 密码登录按钮
login_by_pwd_btn = ("css selector", ".account-tab-account")
# 账号输入框
account = ("id", "username")
# 密码输入框
pwd = ("id", "password")
# 登录豆瓣按钮
login_btn = ("css selector", ".btn-account")
;登录错误信息
error_msg = ("css selector", ".account-form-error span")
[HomePageLocator]
# 账号信息
account_info = ("class name", "nav-user-account")
3. test_case 模块用来编写测试用例
import pytest
from selenium import webdriver
from po.page_object.demo_home_page import HomePage
from po.page_object.demo_login_page import LoginPage
class TestLogin():
def setup_class(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.implicitly_wait(5)
def teardown_class(self):
self.driver.quit()
def test_demo(self):
self.login_page = LoginPage(self.driver)
self.home_page = HomePage(self.driver)
self.login_page.open_url()
self.login_page.login("****", "****")
assert self.home_page.is_account_info_exist()
if __name__ == "__main__":
pytest.main()
现在所有的页面元素定位都写到一个 locator.ini 文件中,当页面元素的定位方式发生变化时,只需要修改 locator.ini 一个文件即可,大家可以自行挑选合适的配置文件保存元素定位信息。
上面是我收集的一些视频资源包
对于软件测试的的朋友来说应该是最全面最完整的备战仓库了,为了更好地整理每个模块,我也参考了很多网上的优质博文和项目,力求不漏掉每一个知识点,很多朋友靠着这些内容进行复习,拿到了BATJ等大厂的offer,这个仓库也已经帮助了很多的软件测试的学习者,希望也能帮助到你!
关注我的微信公众号【程序员二黑】免费获取
如果你对软件测试、接口测试、自动化测试、面试经验交流感兴趣欢迎加入:软件测试技术群:785128166 里面有大牛分享学习经验