一、PO模式入门到深入
学习路线:
V1:不使用任何设计模式和单元测试框架
V2:pytest管理测试用例
V3:使用方法封装的思想,对代码进行优化
V4:采用PO模式的分层思想对代码进行拆分,分离page
V5:对PO分层之后的代码继续优化,分离page中的元素和操作
V6:PO模式深入封装,把共同的操作提取封装
1、V1-无模式
案例需求:对电商网站对登录模块进行自动化测试
登录模块包含很多测试用例,如:账号不存在,密码错误,验证码错误,登录成功等等
选择测试用例
选择账号不存在和密码错误为例
账号不存在:
1、点击首页登录链接,进入登录页面
2、输入一个不存在的用户名
3、输入密码
4、输入验证码
5、点击登录按钮
6、获取错误提示信息
密码错误:
1、点击首页登录链接,进入登录页面
2、输入用户名
3、输入错误密码
4、输入验证码
5、点击登录按钮
6、获取错误提示信息
1.1 总体介绍
不使用任何设计模式和单元测试框架,每个文件对应编写一个测试用例,完全面向过程的编程方式
文件名:test_login_account_not_exist.py和test_login_password_error.py
账号不存在测试用例代码:
# 账号不存在
import time
from selenium import webdriver
# 实例化浏览器驱动
driver = webdriver.Chrome()
driver.maximize_window()
driver.implicitly_wait(10)
driver.get("http://localhost/")
# 1、点击首页登录链接,进入登录页面
driver.find_element_by_class_name("red").click()
# 2、输入一个不存在的用户名
driver.find_element_by_id("username").send_keys("18000021")
# 3、输入密码
driver.find_element_by_id("password").send_keys("123456")
# 4、输入验证码
driver.find_element_by_id("verify_code").send_keys("8888")
# 5、点击登录按钮
driver.find_element_by_name("sbtbutton").click()
# 6、获取错误提示信息
msg = driver.find_element_by_css_selector(".layui-layer-content").text
print(msg)
# 关闭浏览器
time.sleep(3)
driver.quit()
密码错误测试用例:
# 密码错误
import time
from selenium import webdriver
# 实例化浏览器驱动
driver = webdriver.Chrome()
driver.maximize_window()
driver.implicitly_wait(10)
driver.get("http://localhost/")
# 1、点击首页登录链接,进入登录页面
driver.find_element_by_class_name("red").click()
# 2、输入用户名
driver.find_element_by_id("username").send_keys("18800123")
# 3、输入错误的密码
driver.find_element_by_id("password").send_keys("error")
# 4、输入验证码
driver.find_element_by_id("verify_code").send_keys("8888")
# 5、点击登录按钮
driver.find_element_by_name("sbtbutton").click()
# 6、获取错误提示信息
msg = driver.find_element_by_css_selector(".layui-layer-content").text
print(msg)
# 关闭浏览器驱动
time.sleep(3)
driver.quit()
1.2 无模式存在的问题
- 一条测试用例对应一个文件,用例多的时候,不方便维护管理
- 代码高度冗余
2、V2-pytest单元测试框架管理测试用例
2.1 总体介绍
引入pytest框架管理测试用例,并断言执行结果,输出测试报告
好处:
- 方便组织和管理测试用例
- 提供了丰富的断言方法
- 方便生成测试报告
- 减少代码冗余
代码:
import time
from selenium import webdriver
# 定义测试类
class TestLogin:
def setup_method(self):
# 实例化浏览器驱动
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.implicitly_wait(10)
self.driver.get("http://localhost/")
def teardown_method(self):
# 关闭浏览器
time.sleep(3)
self.driver.quit()
# 定义用户不存在的测试方法
def test_login_account_not_exist(self):
# 1、点击首页登录链接,进入登录页面
self.driver.find_element_by_class_name("red").click()
# 2、输入一个不存在的用户名
self.driver.find_element_by_id("username").send_keys("18000021")
# 3、输入密码
self.driver.find_element_by_id("password").send_keys("123456")
# 4、输入验证码
self.driver.find_element_by_id("verify_code").send_keys("8888")
# 5、点击登录按钮
self.driver.find_element_by_name("sbtbutton").click()
# 6、获取错误提示信息
msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
print(msg)
# 断言
assert "账号不存在!" ==msg
# 定义密码错误的测试方法
def test_login_password_error(self):
# 1、点击首页登录链接,进入登录页面
self.driver.find_element_by_class_name("red").click()
# 2、输入用户名
self.driver.find_element_by_id("username").send_keys("18800123")
# 3、输入错误密码
self.driver.find_element_by_id("password").send_keys("error")
# 4、输入验证码
self.driver.find_element_by_id("verify_code").send_keys("8888")
# 5、点击登录按钮
self.driver.find_element_by_name("sbtbutton").click()
# 6、获取错误提示信息
msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
print(msg)
# 断言
assert "密码错误!" == msg
2.2 存在的问题
依然存在代码冗余,比如有些操作步骤/方法是不是可以封装起来
3、V3-方法封装
3.1 总体介绍
使用方法封装思想,对代码进行优化
封装的概念:
是将一些有共性的或者多次使用的代码提取到一个方法中,供其他地方调用
好处:
- 避免代码冗余
- 容易维护和调用
- 隐藏代码的实现细节
目的:
用最少的代码实现最多的功能
驱动工具类代码实现:
# 获取/关闭浏览器驱动的类
import time
from selenium import webdriver
class DriverUtils:
__driver = None # 变量加两个下划线,防止外部方法调用,避免与测试用例的驱动混淆
# 获取浏览器驱动
@classmethod # 可以通过类名来调用方法
def get_driver(cls):
# 实例化浏览器驱动
if cls.__driver is None:
cls.__driver = webdriver.Chrome()
cls.__driver.maximize_window()
cls.__driver.implicitly_wait(10)
return cls.__driver
# 获关闭浏览器驱动
@classmethod # 可以通过类名来调用方法
def close_driver(cls):
if cls.__driver is not None:
# 关闭浏览器
time.sleep(3)
cls.__driver.quit()
cls.__driver = None
测试脚本代码实现:
import time
from selenium import webdriver
from v3.driver_utils import DriverUtils
# 定义测试类
class TestLogin:
def setup_method(self):
# 实例化浏览器驱动
self.driver = DriverUtils.get_driver()
self.driver.get("http://localhost/")
def teardown_method(self):
# 关闭浏览器
time.sleep(3)
DriverUtils.close_driver()
# 定义用户不存在的测试方法
def test_login_account_not_exist(self):
# 1、点击首页登录链接,进入登录页面
self.driver.find_element_by_class_name("red").click()
# 2、输入一个不存在的用户名
self.driver.find_element_by_id("username").send_keys("18000021")
# 3、输入密码
self.driver.find_element_by_id("password").send_keys("123456")
# 4、输入验证码
self.driver.find_element_by_id("verify_code").send_keys("8888")
# 5、点击登录按钮
self.driver.find_element_by_name("sbtbutton").click()
# 6、获取错误提示信息
msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
print(msg)
# 断言
assert "账号不存在!" ==msg
# 定义密码错误的测试方法
def test_login_password_error(self):
# 1、点击首页登录链接,进入登录页面
self.driver.find_element_by_class_name("red").click()
# 2、输入用户名
self.driver.find_element_by_id("username").send_keys("18800123")
# 3、输入错误密码
self.driver.find_element_by_id("password").send_keys("error")
# 4、输入验证码
self.driver.find_element_by_id("verify_code").send_keys("8888")
# 5、点击登录按钮
self.driver.find_element_by_name("sbtbutton").click()
# 6、获取错误提示信息
msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
print(msg)
# 断言
assert "密码错误!" == msg
3.2 存在的问题
仍然存在代码冗余,如测试脚本操作步骤时,登录,输入用户名都是重复的
4、PO模式介绍
思考:
在做UI自动化时,定位元素特别依赖前端页面,一旦页面发生变更,就不得不跟着去修改定位元素的代码
存在的问题:
- 如果开发人员修改了元素的id,这时候不得不修改所有对应的代码
- 即便不修改,也存在大量冗余代码
4.1 PO模式总体介绍
概念:
PO时Page Object的缩写,PO模式是自动化测试项目开发实践的最佳设计模式之一
核心思想:
- 通过对页面元素对封装减少冗余代码,同时在后期维护中,若元素发生变化,只需要调整页面元素封装的代码即可,提高了测试用例的可维护性,可读性
- 页面和测试脚本分离
4.2 PO模式分层
分层机制,让不同层去做不同类型的事情,让代码结果清晰,增加复用性
主要分层方式:
两层
- 对象操作层:封装页面信息,包括元素及元素的操作
- 业务数据层:封装多种操作组合的业务以及测试数据
三层
- 对象库
- 操作层
- 业务数据层
四层
- 对象库
- 操作层
- 业务层
- 数据层
4.3 PO模式的优点
引入PO模式前:存在大量冗余代码;业务流程不清晰;后期维护成本大
引入PO模式后:减少冗余代码;业务代码和测试数据被分开,降低耦合性 ;维护成本低
5、V4-PO模式的实践
5.1 总体介绍
采用PO模式的分层思想对代码进行拆分
封装:
对登录页进行封装:类LoginPage
对测试用例封装:类TestLogin
结构:
utils包:driver_utils.py
page包:login_page.py
scripts包:test_login.py
pytest.ini
5.2 PO模式实战
5.2.1 代码结构
5.2.2 代码示例
login_page.py
class LoginPage:
# 将driver传入初始函数以调用
def __init__(self, driver):
self.driver = driver
# 点击首页登录链接,进入登录页面
def click_login_link(self):
return self.driver.find_element_by_class_name("red").click()
# 输入用户名
def input_username(self, username):
return self.driver.find_element_by_id("username").send_keys("username")
# 输入密码
def input_password(self, password):
return self.driver.find_element_by_id("password").send_keys("password")
# 输入验证码
def input_verifycode(self, code):
return self.driver.find_element_by_id("verify_code").send_keys("code")
# 点击登录按钮
def click_login_button(self):
return self.driver.find_element_by_name("sbtbutton").click()
# 获取错误提示信息
def get_error(self):
msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
return msg
test_login.py
import time
from selenium import webdriver
from v4.page.login_page import LoginPage
from v4.utils.driver_utils import DriverUtils
# 定义测试类
class TestLogin:
def setup(self):
# 实例化浏览器驱动
self.driver = DriverUtils.get_driver()
self.login_page = LoginPage(self.driver)
self.driver.get("http://localhost/")
def teardown(self):
# 关闭浏览器
time.sleep(3)
DriverUtils.close_driver()
# 定义用户不存在的测试方法
def test_login_account_not_exist(self):
# 点击首页登录链接,进入登录页面
self.login_page.click_login_link()
# 输入用户名
self.login_page.input_username("1800000000")
# 输入密码
self.login_page.input_password("123456")
# 输入验证码
self.login_page.input_verifycode("8888")
# 点击登录按钮
self.login_page.click_login_button()
# 断言
assert "账号不存在!" == self.login_page.get_error()
# 定义密码错误的测试方法
def test_login_password_error(self):
# 1、点击首页登录链接,进入登录页面
self.login_page.click_login_link()
# 2、输入用户名
self.login_page.input_username("1800000000")
# 3、输入错误密码
self.login_page.input_password("123456")
# 4、输入验证码
self.login_page.input_verifycode("8888")
# 5、点击登录按钮
self.login_page.click_login_button()
# 断言
assert "密码错误!" == self.login_page.get_error()
driver_utils.py
# 获取/关闭浏览器驱动的类
import time
from selenium import webdriver
class DriverUtils:
__driver = None # 变量加两个下划线,防止外部方法调用,避免与测试用例的驱动混淆
# 获取浏览器驱动
@classmethod # 可以通过类名来调用方法
def get_driver(cls):
# 实例化浏览器驱动
if cls.__driver is None:
cls.__driver = webdriver.Chrome()
cls.__driver.maximize_window()
cls.__driver.implicitly_wait(10)
return cls.__driver
# 获关闭浏览器驱动
@classmethod # 可以通过类名来调用方法
def close_driver(cls):
if cls.__driver is not None:
# 关闭浏览器
time.sleep(3)
cls.__driver.quit()
cls.__driver = None
pytest.ini
[pytest]
adopts = -s
test paths = ./scripts
python_files = test_*.py
python_classes = Test*
python_functions = test_*
6、V5-PO模式深入优化
6.1 总体介绍
对PO分层进一步优化,优化内容:
- 分离page页面中的元素和操作
- 优化元素的定位方式
6.2 代码示例
相较于V4版本的PO模式,只需要改动LoginPage页面即可,将元素定位分层,当页面元素改动时,以便更快地发现并维护自动化代码
from selenium.webdriver.common.by import By
class LoginPage:
# 登录链接 按钮
login_link_btn = By.CLASS_NAME, "red"
# 用户名 输入框
username_input = By.ID, "username"
# 密码 输入框
password_input = By.ID, "password"
# 验证码 输入框
verify_code_input = By.ID, "verify_code"
# 登录 按钮
login_btn = By.NAME, "sbtbutton"
# 提示信息
msg_info = By.CSS_SELECTOR, ".layui-layer-content"
# 将driver传入初始函数以调用
def __init__(self, driver):
self.driver = driver
# 将元组解包,将两个值分别赋值给两个参数
def find_el(self, feature):
return self.driver.find_element(*feature)
# return self.driver.find_element(feature[0], feature[1])
# 点击首页登录链接,进入登录页面
def click_login_link(self):
return self.driver.find_el(self.login_link_btn)
# return self.driver.find_element_by_class_name("red").click()
# 输入用户名
def input_username(self, username):
return self.driver.find_el(self.username_input)
# return self.driver.find_element_by_id("username").send_keys("username")
# 输入密码
def input_password(self, password):
return self.driver.find_el(self.password_input)
# return self.driver.find_element_by_id("password").send_keys("password")
# 输入验证码
def input_verifycode(self, code):
return self.driver.find_el(self.verify_code_input)
# return self.driver.find_element_by_id("verify_code").send_keys("code")
# 点击登录按钮
def click_login_button(self):
return self.driver.find_el(self.login_btn)
# return self.driver.find_element_by_name("sbtbutton").click()
# 获取错误提示信息
def get_error(self):
return self.driver.find_el(self.msg_info)
# msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
# return msg
7、V6-PO模式深入封装
7.1 总体介绍
把共同的方法进行封装
- 封装操作基类:封装查找元素的方法,封装对元素的操作方法:点击、清空、输入
- page继承操作基类
7.2 代码示例
base_action.py
class BaseAction:
def __init__(self, driver):
self.driver = driver
def find_el(self, feature):
return self.driver.find_element(*feature)
def find_els(self, feature):
return self.driver.find_elements(*feature)
def click(self, feature):
return self.find_els(feature).click()
def input(self, feature, content):
return self.find_els(feature).send_keys(content)
def clear(self, feature):
return self.find_els(feature).clear()
login_page.py
from selenium.webdriver.common.by import By
from v6.base.base_action import BaseAction
class LoginPage(BaseAction):
# 登录链接 按钮
login_link_btn = By.CLASS_NAME, "red"
# 用户名 输入框
username_input = By.ID, "username"
# 密码 输入框
password_input = By.ID, "password"
# 验证码 输入框
verify_code_input = By.ID, "verify_code"
# 登录 按钮
login_btn = By.NAME, "sbtbutton"
# 提示信息
msg_info = By.CSS_SELECTOR, ".layui-layer-content"
# 点击首页登录链接,进入登录页面
def click_login_link(self):
return self.click(self.login_link_btn)
# return self.driver.find_el(self.login_link_btn)
# 输入用户名
def input_username(self, username):
return self.input(self.username_input, "18000000")
# return self.driver.find_el(self.username_input)
# 输入密码
def input_password(self, password):
return self.input(self.password_input, "123456")
# return self.driver.find_el(self.password_input)
# 输入验证码
def input_verifycode(self, code):
return self.input(self.verify_code_input, "8888")
# return self.driver.find_el(self.verify_code_input)
# 点击登录按钮
def click_login_button(self):
return self.click(self.login_btn)
# return self.driver.find_el(self.login_btn)
# 获取错误提示信息
def get_error(self):
return self.driver.find_el(self.msg_info)
二、数据驱动
1、数据驱动介绍
1.1 概念
以数据来驱动整个测试用例的执行,即测试数据决定测试结果
1.2 特点
- 可以把数据驱动理解为一种模式或一种思想
- 数据驱动技术可以让用户把关注点放在对测试数据的构建和维护上,而不是直接维护测试脚本,可以用同样的业务流程,对不同的输入数据进行测试
- 数据驱动的实现要依赖参数化技术
1.3 数据来源
- 直接定义在脚本中:简单直观,但是测试方法和数据未实现真正的分离,不方便后期维护
- 从文件中读取数据:如txt、excel、xml、JSON等格式文件
- 从数据库中读取数据
2、JSON操作
2.1 JSON基本介绍
2.1.1 概念
JSON全称“Javascript Object Notation”,是Javascript对象表示法,它是一种基于文本,独立于语言的轻量级数据交换格式
2.2.2 特点
- 是纯文本
- 具有良好的自我描述性,便于阅读和书写
- 具有清晰的层级结构
- 有效提升网络传输效率 :
- 对比 XML可拓展标记语言,被设计用来传输和存储数据;
- 如果使用XML,需要读取XML文档,然后通过XML中的标签节点来遍历文档,并读取值,然后存储在变量中;
- 如果使用JSON,只需要读取JSON字符串
2.2.3 JSON语法规则
- 大括号保存对象
- 中括号保存数组
- 对象和数组可以相互嵌套
- 数据采用键值对表示
- 多个数据由逗号分隔
2.2.4 JSON值
- 数字(整数、浮点数)
- 字符串(在双引号中)
- 逻辑值(true、false)
- 数组(在中括号中)
- 对象(在大括号中)
- null:JSON中空值用null表示,对应python中的None
2.2 JSON操作
2.2.1 总体介绍
操作内容:
- Python中的字典与JSON之间的转换
- JSON文件读写
注意点:
在python中想要操作JSON,需要导入依赖包【import json】
2.2.2 字典与JSON转换
- 把python字典转换为json字符串 dumps
- 把json字符串转换为python字典 loads
import json
# 把python字典转换为json字符串
dict = {
"name" : "zhangsan",
"age" : 18,
"is_man" : True,
"school" : None
}
# 使用dumps方法,得到的结果是json字符串
json_str = json.dumps(dict)
print(json_str)
# 把json字符串转换为python字典
json_str1 = '{"name": "zhangsan", "age": 18, "is_man": true, "school": null}'
#使用loads方法,得到的结果是python字典
dict2 = json.loads(json_str1)
print(dict2)
json文件读写
import json
# 读取data.json文件
with open("data.json", "r", encoding="utf-8") as f:
data1 = json.load(f)
print(data1)
# 把字典写入json文件 “data2.json”
data2 = data1
with open("data2.json", "w", encoding="utf-8") as f:
json.dump(data2, f)
# 把字典写入json文件“data3.json”--------解决写入中文的问题
data3 = data1
with open("data3.json", "w", encoding="utf-8") as f:
json.dump(data2, f, ensure_ascii=False)
2.3 JSON实战一
2.3.1 案例介绍
对tpshop电商项目的登录模块进行自动化测试,需要使用数据驱动,对PO模式框架增加数据驱动
2.3.2 实现步骤
1、编写测试用例
2、编写测试代码 -
- 采用PO模式分层思想对页面进行封装
- 编写测试脚本
- 定义文件,实现参数化
2.3.3 字典数据参数化代码实战
使用参数化优化代码前:
import time
import pytest
from selenium import webdriver
from v7_ddt.page.login_page import LoginPage
from v7_ddt.utils.driver_utils import DriverUtils
# 定义测试类
class TestLogin:
def setup(self):
# 实例化浏览器驱动
self.driver = DriverUtils.get_driver()
self.login_page = LoginPage(self.driver)
self.driver.get("http://localhost/")
def teardown(self):
# 关闭浏览器
time.sleep(3)
DriverUtils.close_driver()
# 定义用户不存在的测试方法
def test_login_account_not_exist(self):
# 点击首页登录链接,进入登录页面
self.login_page.click_login_link()
# 输入用户名
self.login_page.input_username("1800000000")
# 输入密码
self.login_page.input_password("123456")
# 输入验证码
self.login_page.input_verifycode("8888")
# 点击登录按钮
self.login_page.click_login_button()
# 断言
assert "账号不存在!" == self.login_page.get_error()
# 定义密码错误的测试方法
def test_login_password_error(self):
# 1、点击首页登录链接,进入登录页面
self.login_page.click_login_link()
# 2、输入用户名
self.login_page.input_username("1800000000")
# 3、输入错误密码
self.login_page.input_password("123456")
# 4、输入验证码
self.login_page.input_verifycode("8888")
# 5、点击登录按钮
self.login_page.click_login_button()
# 断言
assert "密码错误!" == self.login_page.get_error()
使用参数化优化代码后:
import time
import pytest
from selenium import webdriver
from v7_ddt.page.login_page import LoginPage
from v7_ddt.utils.driver_utils import DriverUtils
# 定义测试类
class TestLogin:
def setup(self):
# 实例化浏览器驱动
self.driver = DriverUtils.get_driver()
self.login_page = LoginPage(self.driver)
self.driver.get("http://localhost/")
def teardown(self):
# 关闭浏览器
time.sleep(3)
DriverUtils.close_driver()
# 定义测试登录的方法,实现脚本参数化
dict1 = {"username": "1700000000", "password": "123456", "code": "8888", "msg": "账号不存在!"}
dict2 = {"username": "1800000000", "password": "123456", "code": "8888", "msg": "密码错误!"}
@pytest.mark.parametrize("params", [dict1, dict2])
def test_login(self, params):
# 1、点击首页登录链接,进入登录页面
self.login_page.click_login_link()
# 2、输入用户名
self.login_page.input_username(params["username"])
# 3、输入密码
self.login_page.input_password(params["password"])
# 4、输入验证码
self.login_page.input_verifycode(params["code"])
# 5、点击登录按钮
self.login_page.click_login_button()
# 6、断言
assert params["msg"] == self.login_page.get_error()
2.3.4 json文件参数化代码实战
目录及json文件
login_page.py
import json
import time
import pytest
from selenium import webdriver
from v7_ddt.page.login_page import LoginPage
from v7_ddt.utils.driver_utils import DriverUtils
def analyze_data():
with open("./data/login_data.json", "r", encoding="utf8") as s:
# json.load(s)中包含了键和值,但是测试不需要键,所以要遍历然后存到一个新列表中
# print(json.load(s))
list_data = []
dict_data = json.load(s)
for value in dict_data:
# print(value)
list_data.append(value)
return list_data
# 定义测试类
class TestLogin:
def setup(self):
# 实例化浏览器驱动
self.driver = DriverUtils.get_driver()
self.login_page = LoginPage(self.driver)
self.driver.get("http://localhost/")
def teardown(self):
# 关闭浏览器
time.sleep(3)
DriverUtils.close_driver()
# 定义测试登录的方法,实现脚本参数化
dict1 = {"username": "1700000000", "password": "123456", "code": "8888", "msg": "账号不存在!"}
dict2 = {"username": "1800000000", "password": "123456", "code": "8888", "msg": "密码错误!"}
# dict_data = [dict1,dict2] 这个就相当于json转换后的字典数据
@pytest.mark.parametrize("params", analyze_data())
def test_login(self, params):
# 1、点击首页登录链接,进入登录页面
self.login_page.click_login_link()
# 2、输入用户名
self.login_page.input_username(params["username"])
# 3、输入密码
self.login_page.input_password(params["password"])
# 4、输入验证码
self.login_page.input_verifycode(params["code"])
# 5、点击登录按钮
self.login_page.click_login_button()
# 6、断言
assert params["msg"] == self.login_page.get_error()
抽离解析函数-新建一个base_anlyze.py文件,用于存放解析方法
这样就可以更加方便地维护和调用函数
2.4 JSON实战二
2.4.1 案例介绍
- 网页计算机器,进行加法的测试操作
- 要求:读取数据文件中的数据来执行用例
- 网址 :计算器在线计算 (apple886.com)
2.4.2 实现步骤
1、编写测试用例
1+1
1+2
1+2+3
2、搭建自动化测试框架(可以参考JSON实战一的框架)
2.4.3 代码实战
项目目录
base_action.py
class BaseAction:
def __init__(self, driver):
self.driver = driver
def find_el(self, feature):
return self.driver.find_element(*feature)
def find_els(self, feature):
return self.driver.find_elements(*feature)
# 获取数字按钮
def find_el_num(self, feature, num):
return self.driver.find_element(feature[0], feature[1].format(str[num]))
# simple{}.format(str(num))
def click(self, feature):
return self.find_els(feature).click()
def input(self, feature, content):
return self.find_els(feature).send_keys(content)
def clear(self, feature):
return self.find_els(feature).clear()
base_anlyze.py
import json
def analyze_data(filename):
with open("./data/"+filename, "r", encoding="utf8") as s:
# json.load(s)中包含了键和值,但是测试不需要键,所以要遍历然后存到一个新列表中
# print(json.load(s))
list_data = []
dict_data = json.load(s)
for value in dict_data:
# print(value)
list_data.append(value)
return list_data
cal_data.json
{
"add_001":{
"data": [1,1],
"result": 2
},
"add_002":{
"data": [1,2],
"result,": 3
},
"add_003": {
"data": [1,2,3],
"result": 6
}
}
cal_page.py
from selenium.webdriver.common.by import By
from v7_ddt_caculator.base.base_action import BaseAction
class CalPage(BaseAction):
# 数字 按钮
# 数字按钮的元素定位都是simple前缀
digit_btn = By.ID, "simple{}"
# 加号 按钮
add_btn = By.ID, "simpleAdd"
# 等号 按钮
eq_btn = By.ID, "simpleEqual"
# 计算结果
result = By.ID, "resultIpt"
# 点击数字
def click_digit_btn(self, num):
return self.find_el_num(self.digit_btn, num).click()
# 点击加号
def click_add_btn(self):
return self.click(self.add_btn)
# 点击等号
def click_eq_btn(self):
return self.click(self.eq_btn)
# 获取计算结果
def get_result(self):
return self.find_el(self.result).get_attribute("value")
test_cal.py
import json
import time
import pytest
from selenium import webdriver
from v7_ddt_caculator.base.base_analyze import analyze_data
from v7_ddt_caculator.page.cal_page import CalPage
from v7_ddt_caculator.utils.driver_utils import DriverUtils
# 定义测试类
class TestCal:
def setup(self):
# 实例化浏览器驱动
self.driver = DriverUtils.get_driver()
self.cal_page = CalPage(self.driver)
self.driver.get("http://cal.apple886.com/")
def teardown(self):
# 关闭浏览器
time.sleep(3)
DriverUtils.close_driver()
@pytest.mark.parametrize("params", analyze_data("cal_data.json"))
def test_cal(self, params):
for i in params["data"]:
self.cal_page.digit_btn(i)
self.cal_page.add_btn()
# self.cal_page.digit_btn(params["data"][0])
# self.cal_page.add_btn()
# self.cal_page.digit_btn(params["data"][1])
self.cal_page.eq_btn()
assert str(params["result"]) == self.cal_page.get_result()
driver.utils
# 获取/关闭浏览器驱动的类
import time
from selenium import webdriver
class DriverUtils:
__driver = None # 变量加两个下划线,防止外部方法调用,避免与测试用例的驱动混淆
# 获取浏览器驱动
@classmethod # 可以通过类名来调用方法
def get_driver(cls):
# 实例化浏览器驱动
if cls.__driver is None:
cls.__driver = webdriver.Chrome()
cls.__driver.maximize_window()
cls.__driver.implicitly_wait(10)
return cls.__driver
# 获关闭浏览器驱动
@classmethod # 可以通过类名来调用方法
def close_driver(cls):
if cls.__driver is not None:
# 关闭浏览器
time.sleep(3)
cls.__driver.quit()
cls.__driver = None
三、日志收集
3.1 日志相关概念
概念:
日志就是用于记录系统运行时的信息,也称为Log
作用:
- 调试程序
- 了解系统运行的情况是否正常
- 程序运行故障分析和问题定位
- 用来做用户行为分析和数据统计
级别:
思考:日志记录的信息重要性都一样吗?
- 日志级别:日志信息的重要性
- 常见日志级别:(从严重程度排序)
- DEBUG 调试,打印非常详细的日志信息
- INFO 一般信息,打印一般的日志信息,突出强调程序运行过程
- WARNING 警告 ,打印警告日志信息,表面会出现潜在的错误,不影响程序运行
- ERROR 错误,打印错误异常信息,该级别的错误出现表示程序一些功能无法使用
- CRITICAL 严重错误级别,表示程序可能无法继续运行
当为程序指定一个日志级别后,程序会记录大于等于该级别的日志信息,而不仅仅是记录指定级别的日志
一般建议只使用DEBUG、INFO、WARNING、ERROR四个级别
3.2 日志基本用法
3.2.1 基本用法
Python中有一个标准库,logging模块可以直接记录日志
使用方法:导入logging包,输出日志
import logging
logging.debug("这是一条调试信息")
logging.info("这是一条一般信息")
logging.warning("这是一条警告信息")
logging.error("这是一条错误信息")
logging.critical("这是一条严重错误信息")
logging中默认日志级别是warning,程序中大于等于该级别的日志信息才会输出
3.2.2 设置日志级别
如何选择日志级别?
- 在开发和测试环境中,为了尽可能详细查看程序的运行状态,可以使用DEBUG或INFO级别的日志获取详细的日志信息,但非常消耗计算机的性能
- 在生产环境中,通常只记录程序的异常和错误信息,设置日志级别为WARING和ERROR级别即可,这样可以减少服务器的压力,也方便问题的排查
3.2.3 设置日志格式
默认格式:日志级别:Logger名称:日志内容
3.2.4 将日志信息输出到文件中
python的logging模块将日志打印到了标准输出(控制台)中
将日志信息输出到文件
logging.basicConfig(filename="xxx.log")
3.3 日志高级用法
3.3.1 总体介绍
思考
- 如何将日志信息同时输出到控制台和日志文件中?
- 如何将不同级别日志输出到不同日志文件中?
- 如何解决日志文件过大的问题?
3.3.2 logging日志模块四大组件
3.3.2.1 总体介绍
- 日志器:Logger,提供了程序使用日志的入口
- 处理器:Handler,将logger创建的日志记录发送到合适的输出
- 格式器:Fomatter,决定日志的输出格式
- 过滤器:Filter,提供更细粒度的控制工具,决定输出哪条日志,丢弃哪条日志
整个logging模块就是靠这些组件完成日志处理的
3.3.2.2 组件之间的关系
日志器(Logger)是入口,真正干活的是处理器(Handler),处理器还可以通过格式器(Formatter)和过滤器(Filter)对要输出的日志内容做格式化和过滤
- 日志器可以通过处理器将日志信息输出到目标位置
- 不同处理器可以输出到不同位置
- 日志器可以设置多个处理器将同一条日志记录输出到不同位置
- 每个处理器可以设置自己的格式器实现同一条日志不同格式输出
- 每个处理器都可以设置自己的格式器实现日志过滤
3.3.2.3 日志器Logger
创建Logger对象:
logger = logging.getLogger(name)
可选参数name:
如果不写name,日志器默认为root,如果写了name,如logger=logging.getLogger("mylogger"),则日志器名称为mylogger
打印日志:
logging.debug("这是一条调试信息") logging.info("这是一条一般信息") logging.warning("这是一条警告信息") logging.error("这是一条错误信息") logging.critical("这是一条严重错误信息")
设置日志级别:
logger.setLevel()
为logger对象添加一个handler对象:
logger.addHandler()
为logger对象添加一个filter对象
logger.addFilter
3.3.2.4 处理器Handler
创建一个Handler对象
在程序中不应该直接实例化和使用Handler实例,因为Handler是一个基类,它只定义了Handler应有的接口,应该使用Handler实现类创建对象
解释:Handler相当于工人,但是工人下面有很多工种,比如园丁、建筑工人等,直接实例化不具体,需要实现类创建对象
创建方式:
- 输出日志到控制台 logging.StreamHandler()
- 输出到文件,按时间切割 logging.handlers.TimeRotatingFileHandler()
- 输出到磁盘文件 默认文件大小会无限增长
- 输出到文件,按文件大小切割
- 将日志消息以get或post请求方式发送给http服务器
- 将日志消息发送给一个指定的email地址
常用方法
为handler设置格式器对象 handler.setFormatter()
3.3.2.5 格式器Formatter
作用:用于配置日志信息的格式
创建Formatter对象:
- logging.Formatter(fmt=None,datefmt=None)
- fmt:消息格式化字符串,如果不指定该参数默认使用message的原始值
- datefmt:日期格式化字符串,如果不指定则使用默认日期
3.4 日志案例分析
3.4.1 案例说明
可读性比较好的需要具备一些特征:
-
在控制台和文件都能输出
- 文件输出能够按时间切割
实现步骤:
1、导包
2、创建日志器对象,设置日志级别
3、创建处理器对象:输出到控制台+文件(按时间切割)
4、创建格式器对象
5、将格式器添加到处理器
6、将处理器添加到日志器
7、打印日志
示例代码:
# 1、导包
import logging
import logging.handlers
# 2、创建日志器对象,设置日志级别
# logger = logging.getLogger(不设置默认为root)
logger = logging.getLogger("jack")
logger.setLevel(level=logging.DEBUG)
# 3、创建处理器对象:输出到控制台+文件(按时间切割)
ls = logging.StreamHandler()
# filename表示输出日志文件路径,when表示切割时间单位,backCount表示备份
ln = logging.handlers.TimedRotatingFileHandler(filename="jack.log", when="s", backupCount=3)
# 4、创建格式器对象
fmt = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(funcName)s:%(lineno)d] --%(message)s"
formatter = logging.Formatter(fmt=fmt)
# 5、将格式器添加到处理器
ls.setFormatter(formatter)
ln.setFormatter(formatter)
# 6、将处理器添加到日志器
logger.addHandler(ls)
logger.addHandler(ln)
# 7、打印日志
while True:
logger.debug("这是一条日志消息")
运行结果:
如图所示,在控制台和文件中都打印了日志消息,并且备份了三个文件