UI自动化测试及框架
python + pytest框架
依赖包:pytest(单元测试框架)
allure-pytest(生成html报告)
pytest-html(生成html报告)
selenium(web自动化工具)
appium(app自动化工具)
parameterized(参数化)
目录结构:base(封装PO基类,对象库层基类、操作层基类)
page(封装PO页面对象类,定义对象库层、操作层、业务层)
script(存放测试的脚本)
data(存放测试数据)
report(存放测试报告)
log(日志目录)
screenshot(存放截图)
config.py(定义项目的配置信息)
conftest.py(专门存放fixture的配置文件,跨.py文件调用)
utils.py(自定义工具类)
pytest.ini(pytest配置文件)
PO模式先确定几个页面,根据对应的页面去创建页面对象的文件
确定每个页面要用到多少个元素对象库层
操作层
业务层
utils.py预览:
获取浏览器驱动:
# 定义工具类import timefrom selenium import webdriverfrom selenium.webdriver.common.by import Byclass UtilsDriver: _mp_driver = None # 表示的是自媒体平台的浏览器驱动 _mis_driver = None # 表示的是后台管理的浏览器驱动 _app_driver = None # 表示的是app的驱动 # 定义获取自媒体平台的浏览器驱动 @classmethod def get_mp_driver(cls): if cls._mp_driver is None: cls._mp_driver = webdriver.Chrome() cls._mp_driver.maximize_window() cls._mp_driver.get("http://ttmp.research.itcast.cn/") return cls._mp_driver
退出浏览器驱动:
# 定义退出自媒体平台的浏览器驱动 @classmethod def quit_mp_driver(cls): if cls._mp_driver is not None: cls.get_mp_driver().quit() cls._mp_driver = None
获取app驱动:
from appium import webdriver as app_driver # 定义获取app的驱动 @classmethod def get_app_driver(cls): if cls._app_driver is None: des_cap = { "platformName": "android", # 表示的是android 或者ios "platformVersion": "5.1.1", # 表示的是平台系统的版本号 "deviceName": "****", # 表示的是设备的ID名称(如果只有一个设备可以用****来代替) "appPackage": "com.itcast.toutiaoApp", # 表示app的包名 "appActivity": ".MainActivity", # 表示的是app的界面名 "noReset": True, # 用来记住app的session,如果有登陆或做过初始化的操作,为True时,后面不需要再操作 "resetKeyboard": True, # 重置设备的输入键盘 "unicodeKeyboard": True # 采用unicode编码输入 } cls._app_driver = app_driver.Remote("http://127.0.0.1:4723/wd/hub", des_cap) return cls._app_driver
退出app驱动:
# 定义退出app驱动的方法 @classmethod def quit_app_driver(cls): if cls._app_driver is not None: cls.get_app_driver().quit() cls._app_driver = None
base.py类预览:
定义获取浏览器驱动和定义显示等待的基类:对象库层基类封装
# 定义自媒体平台的基类from selenium.webdriver.support.wait import WebDriverWaitfrom utils import UtilsDriver# 对象库层基类封装class BasePage: def __init__(self): self.driver = UtilsDriver.get_mp_driver() # 获取自媒体浏览器驱动 def get_element(self, location): wait = WebDriverWait(self.driver, 10, 1) element = wait.until(lambda x: x.find_element(*location)) return element
定义输入文本操作的基类:操作层基类封装
# 操作层基类封装class BaseHandle: def input_text(self, element, text): element.clear() element.send_keys(text)
xxx_page.py预览:
定义对象库层:
1、继承对象库层基类、重写__init__方法、定位元素
# 自媒体平台的发布文章页面对象from selenium.webdriver.common.by import Byfrom base.mp.base import BasePage, BaseHandle# 定义对象库层from utils import UtilsDriver, choice_channelclass PublishPage(BasePage): def __init__(self): super().__init__() # 文章名称输入框 self.title = By.XPATH, "//*[@placeholder='文章名称']" # iframe元素对象 self.iframe_ele = By.ID, "publishTinymce_ifr" # 文章内容输入框 self.content = By.CSS_SELECTOR, ".mce-content-body " # 封面选择 self.cover = By.XPATH, "//*[@role='radiogroup']/label[4]/span[2]" # 频道选择 self.channel = By.XPATH, "//*[@placeholder='请选择']" # 发表按钮 self.publish_btn = By.CSS_SELECTOR, "[class='el-button filter-item el-button--primary']"
2、返回元素:return ...
# 找文章的标题 def find_title(self): return self.get_element(self.title) # 找切换的iframe def find_iframe(self): return self.get_element(self.iframe_ele) # 找文章内容输入框 def find_input_kw(self): return self.get_element(self.content) # 定位选择封面 def find_cover(self): return self.get_element(self.cover) # 定位频道选择框 def find_channel_choice(self): return self.get_element(self.channel) # 定位发表按钮 def find_publish_btn(self): return self.get_element(self.publish_btn)
定义操作层:
继承操作层基类、实例化对象库层、操作点击、输入、切换iframe等
# 定义操作层class PublishHandle(BaseHandle): def __init__(self): self.publish_page = PublishPage() self.driver = UtilsDriver.get_mp_driver() # 输入文章标题 def input_title(self, title): self.input_text(self.publish_page.find_title(), title) # 输入文章内容 def input_content(self, content): # 切换到iframe当中 self.driver.switch_to.frame(self.publish_page.find_iframe()) # 输入文章内容 self.input_text(self.publish_page.find_input_kw(), content) # 切回到默认首页 self.driver.switch_to.default_content() # 选择封面 def choice_cover(self): self.publish_page.find_cover().click() # 选择频道 def choice_channel(self, channel): choice_channel(self.driver, self.publish_page.find_channel_choice(), channel) # 点击发布按钮 def click_publish_btn(self): self.publish_page.find_publish_btn().click()
定义业务层:
继承操作层,调用操作层的方法
# 定义业务层class PublishProxy: def __init__(self): self.publish_handle = PublishHandle() # 发布文章 def publish_article(self, title, content, channel): # 输入文章标题 self.publish_handle.input_title(title) self.publish_handle.input_content(content) self.publish_handle.choice_cover() self.publish_handle.choice_channel(channel) self.publish_handle.click_publish_btn()
script - Test测试类:
定义测试用例脚本:
实例化业务层 - setup_class,退出驱动 - teardown_class
# 定义测试类from page.mp.home_page import HomeProxyfrom page.mp.login_page import LoginProxyfrom page.mp.publish_page import PublishProxyfrom utils import UtilsDriver, is_existclass TestPublishArticle: # 定义类级别的fixture初始化操作方法 def setup_class(self): self.login_proxy = LoginProxy() self.home_proxy = HomeProxy() self.publish_proxy = PublishProxy() # 定义类级别的fixture销毁操作方法 def teardown_class(self): UtilsDriver.quit_mp_driver()
定义用例:调用业务层里面的方法,断言
# 定义登录的测试用例 def test_login(self): self.login_proxy.login("13012345678", "246810") # 登录 username = self.home_proxy.get_username_msg() # 获取登录后的用户名信息 assert "test123" == username # 根据获取到的用户名进行断言 # 定义测试方法 def test_publish_article(self): self.home_proxy.go_publish_page() # 跳转到发布文章页面 self.publish_proxy.publish_article("发布文章_0710_14", "发布文章_0710_14发布文章_0710_14", "数据库") assert is_exist(UtilsDriver.get_mp_driver(), "新增文章成功")
pytest配置文件
pytest的配置文件有固定的三个名称:(三选一即可)pytest.ini
tox.ini
setup.cfg
这三个配置文件是放在项目的根目录下
配置内容如下:
[pytest] # 标识当前配置文件是pytest的配置文件addopts = -s -v # 标识pytest执行时增加的参数testpaths = ./scripts # 匹配搜索的目录python_files = test_*.py # 匹配测试文件python_classes = Test* # 匹配测试类python_functions = test_* # 匹配测试方法
配置文件弄好了就可以在 命令行输入pytest就可以执行
pytest测试报告
安装
pip install pytest-html
pytest配置文件addopts加上--html=report/report.html
addopts = -s -v --html=report/report.html
allure-pytest测试报告
安装
pip install allure-pytest
添加测试步骤使用的方法:
@allure.step(title="步骤名称")title名称必须要带上。
# 定义操作层class LoginHandle(BaseHandle): def __init__(self): self.login_page = LoginPage() # 输入手机号码 @allure.step(title="输入手机号码") def input_mobile(self, mobile): self.input_text(self.login_page.find_mobile(),mobile) # 输入验证码 @allure.step(title="输入验证码") def input_code(self, code): self.input_text(self.login_page.find_code(),code)
生成allure测试报告数据
要在pytest.ini配置文件的addopts项中,增加一项:
addopts = -s -v --alluredir reportreport表示的是生成报告数据存放的目录
将allure测试报告数据生成HTML测试报告
allure转换工具安装解压allure-2.7.0工具包
将解压的目录放到不经常移动的路径下面
找到解压目录的bin目录,并将bin目录的路径添加到环境变量当中
在命令行当中输入 allure,如果有以下提示信息,就说明是成功的
使用的命令:
allure generate report -o report/html --cleanallure generate 表示的是生成测试报告report 表示的是测试报告的数据目录-o report/html 表示输出html测试报告的目录为 report/html--clean 表示的是清除之前 report/html里面的报告文件
拓展:参数化
形式:
case_data = get_case_data(BaseDir + "/data/mp/test_login_data.json")# 定义登录的测试用例 @pytest.mark.parametrize("username, code, expect", case_data) @allure.severity(allure.severity_level.CRITICAL) def test_login(self, username, code, expect):
实际用法:
# 定义测试类import loggingimport allureimport pytestfrom config import BaseDirfrom page.mp.home_page import HomeProxyfrom page.mp.login_page import LoginProxyfrom page.mp.publish_page import PublishProxyfrom utils import UtilsDriver, is_exist, get_case_datacase_data = get_case_data(BaseDir + "/data/mp/test_login_data.json")@pytest.mark.run(order=1)class TestPublishArticle: # 定义类级别的fixture初始化操作方法 def setup_class(self): self.login_proxy = LoginProxy() self.home_proxy = HomeProxy() self.publish_proxy = PublishProxy() # 定义类级别的fixture销毁操作方法 def teardown_class(self): UtilsDriver.quit_mp_driver() # 定义登录的测试用例 @pytest.mark.parametrize("username, code, expect", case_data) @allure.severity(allure.severity_level.CRITICAL) def test_login(self, username, code, expect): logging.info("用例的数据如下:用户名:{}, 验证码:{}, 预期结果:{}".format(username, code, expect)) print(username, code) self.login_proxy.login(username, code) # 登录 allure.attach(UtilsDriver.get_mp_driver().get_screenshot_as_png(), "登录截图", allure.attachment_type.PNG) username = self.home_proxy.get_username_msg() # 获取登录后的用户名信息 assert expect == username # 根据获取到的用户名进行断言