今日内容
- 数据驱动
- 参数化
- 日志模块
- 基本应用
- 高级应用
1. JSON 文件定义
- JS 语言对象表示法. 是一种基于文本, 独立与语言, 轻量级的数据交换格式
- Web 项目数据交换格式多为: JSON 或 XML
- 移动端项目数据交换格式多为: JSON
2. JSON 文件语法规则
最外层可以是对象{}或者是数组[]
所有的数据必须是键值对(key-value)形式
- 所有的 key 必须是字符串类型
- 必须使用双引号
多条数据由逗点隔开
值的数据类型
- 字符串
- 数字类型
- 整数
- 浮点数
- 布尔类型(全小写)
- true
- false
- 空对象
- null
对象和数组可以相互嵌套
JSON演示
{ "name": "小明", "age": 18, "height": 1.85, "isMan": true, "school": null, "num": [1,2,3,4,5], "link": [ {"BaiDu": "http://www.baidu.com"}, {"黑马": "www.itheima.com"} ] }
3. JSON 文件的读写操作
无论是读取还是写入 JSON 文件, 都要使用 import json
读取 JSON(重点)
import json with open('./文件名.json', encoding='utf-8') as f: data = json.load(f) print('获取的 JSON 数据为:', data)- 读取的方法名:
load() - with open() 方法, 默认为 r 模式, 可以省略
- 读取的 JSON 文件内容会被自动转换为 Python 的字典数据(判断依据:布尔类型和空对象的语法格式)
- 读取的方法名:
写入 JSON 文件
import json dict_data = {"name": "小明", "isMan": False, "address": None} with open('./json_data.json', 'w', encoding='utf-8') as f: json.dump(dict_data, f)- 写入的方法名:
dump() - 在写入 JSON 文件时, with open() 方法, 需要指定为 w 模式
- 在写入 JSON 文件时, 中文会被自动转换为 unicode 编码(\u5c0f\u660e), 并不是乱码, 需要注意
- 写入的方法名:
4. 数据驱动案例一实现步骤
PO 对页面元素进行封装
- 代码演示1–公共方法类
""" 公共方法类 """ import time from selenium import webdriver class DriverUtil(object): """浏览器驱动工具类""" driver = None # 初始化状态 @classmethod def get_driver(cls): """获取驱动方法""" # 判断浏览器对象不存在时, 创建对象 if cls.driver is None: cls.driver = webdriver.Chrome() # 直接使用时需要修改此项(浏览器类型) cls.driver.get('http://cal.apple886.com/') # 直接使用时需要修改此项(项目地址) cls.driver.maximize_window() cls.driver.implicitly_wait(10) return cls.driver @classmethod def quit_driver(cls): """退出驱动方法""" # 判断浏览器对象存在时, 再执行退出 if cls.driver: cls.driver.quit() cls.driver = None if __name__ == '__main__': DriverUtil.get_driver() time.sleep(2) DriverUtil.quit_driver()- 代码演示2–计算器页面
""" 计算器页面 """ from selenium.webdriver.common.by import By from utils import DriverUtil class CalcPage(object): """计算器-对象库层""" def __init__(self): self.driver = DriverUtil.get_driver() # 获取浏览器驱动对象 # self.num_btn = (By.ID, 'simple0') # 数字键 self.num_btn = (By.ID, 'simple{}') # 数字键 self.add_btn = (By.ID, 'simpleAdd') # 加号键 self.equal_btn = (By.ID, 'simpleEqual') # 等号键 self.result = (By.ID, 'resultIpt') # 计算结果 def find_num_btn(self, num): """数字键定位方法""" # Python 规范要求: 单行字符数量不超过 79 个(提高代码的可阅读性) # location = (self.num_btn[0], self.num_btn[1].format(num)) # return self.driver.find_element(location[0], location[1]) return self.driver.find_element(self.num_btn[0], self.num_btn[1].format(num)) def find_add_btn(self): """加号键定位方法""" return self.driver.find_element(self.add_btn[0], self.add_btn[1]) def find_equal_btn(self): """等号键定位方法""" return self.driver.find_element(self.equal_btn[0], self.equal_btn[1]) def find_result(self): """计算结果定位方法""" return self.driver.find_element(self.result[0], self.result[1]) class CalcHandle(object): """计算器-操作层""" def __init__(self): self.calc_page = CalcPage() # 元素定位对象 def click_num_btn(self, num): """数字键点击方法""" self.calc_page.find_num_btn(num).click() def click_add_btn(self): """加号键点击方法""" self.calc_page.find_add_btn().click() def click_equal_btn(self): """等号键点击方法""" self.calc_page.find_equal_btn().click() def get_result(self): """计算结果获取方法""" # 如果目标元素没有对应文本信息在代码中显示, 应该使用目标元素的 value 属性的属性值 return self.calc_page.find_result().get_attribute('value') def input_number(self, num): """数字输入方法""" for i in str(num): self.click_num_btn(i) class CalcProxy(object): """计算器-业务层""" def __init__(self): self.calc_handle = CalcHandle() # 元素操作对象 def add_func(self, num1, num2): """加法运算方法""" # 通过循环遍历实现一次输入一个数字的效果 # 注意: 整数类型数据不能够被遍历, 因此需要强转成字符串类型数据 # for i in str(num1): # self.calc_handle.click_num_btn(i) self.calc_handle.input_number(num1) # 加数 self.calc_handle.click_add_btn() # 加号 # for j in str(num2): # self.calc_handle.click_num_btn(j) self.calc_handle.input_number(num2) # 被加数 self.calc_handle.click_equal_btn() # 等号 # 考虑到将来可能还需要增加减法/乘法/除法运算, 因此最好将获取计算结果方法抽取成单个方法 def get_result_func(self): """获取计算结果方法""" return self.calc_handle.get_result()编写对应的测试用例
- 代码演示–计算器加法测试用例
""" 计算器加法测试用例 """ import json import unittest from calc_page import CalcProxy from utils import DriverUtil from parameterized import parameterized def build_add_data(): with open('./add_data.json', encoding='utf-8') as f: data = json.load(f) print(data) return data class TestCalcAdd(unittest.TestCase): """加法测试类""" @classmethod def setUpClass(cls) -> None: cls.driver = DriverUtil.get_driver() # 获取浏览器对象 cls.calc_proxy = CalcProxy() # 计算器页面业务执行对象 @classmethod def tearDownClass(cls) -> None: DriverUtil.quit_driver() # 退出浏览器对象 @parameterized.expand(build_add_data()) def test_add(self, x, y, z): """加法测试方法""" self.calc_proxy.add_func(x, y) # 加法运算 result = self.calc_proxy.get_result_func() # 获取计算结果 # 由于计算结果返回数值为字符串类型, 因此预期结果也应该为字符串类型. self.assertEqual(str(z), result) # 断言判断 # def test_add(self): # """加法测试方法""" # self.calc_proxy.add_func(110, 119) # 加法运算 # result = self.calc_proxy.get_result_func() # 获取计算结果 # # 由于计算结果返回数值为字符串类型, 因此预期结果也应该为字符串类型. # self.assertEqual('229', result) # 断言判断 if __name__ == '__main__': unittest.main()准备数据源文件(JSON 文件)
- JSON文件
[ [1,2,3], [12,13,25], [110, 120, 230], [1111,2222,3333] ]编写解析数据源文件的方法
- 代码演示–解析数据源文件方法
import json def build_add_data(): with open('../add_data.json', encoding='utf-8') as f: data = json.load(f) print(data) return data利用参数化将数据源文件的中数据引入到测试用例的测试方法中
- 已引入上述测试用例中
5. 数据驱动案例二-实现步骤
公共方法类
- utils.py
- 代码演示
from selenium import webdriver def get_tip_message(): """获取弹窗信息方法""" msg = DriverUtil.get_driver().find_element_by_class_name('layui-layer-content').text print('msg:', msg) return msg class DriverUtil(object): """浏览器驱动对象工具类""" driver = None # 驱动对象初始化状态 @classmethod def get_driver(cls): """获取驱动对象方法""" # 判断浏览器对象不存在时再进行创建操作 if cls.driver is None: cls.driver = webdriver.Chrome() # 由于判断条件下的代码只会执行一次, 因此将打开和最大化和隐式等待的设置暂时放置到这里 cls.driver.get('http://127.0.0.1') cls.driver.maximize_window() cls.driver.implicitly_wait(10) return cls.driver @classmethod def quit_driver(cls): """退出驱动对象方法""" # 判断驱动对象存在时再执行退出操作 if cls.driver: cls.driver.quit() cls.driver = None if __name__ == '__main__': DriverUtil.get_driver() DriverUtil.quit_driver()PO 文件基类
- base_page.py
- 代码演示
""" PO 文件基类 """ from utils import DriverUtil class BasePage(object): """对象库层基类""" def __init__(self): self.driver = DriverUtil.get_driver() # 获取浏览器对象 def find_element_func(self, location): """元素定位方法""" return self.driver.find_element(location[0], location[1]) class BaseHandle(object): """操作层基类""" @staticmethod def input_text(element, text): """元素输入方法""" element.clear() # 清空 element.send_keys(text) # 输入对应页面的 PO 代码封装实现
- 代码演示–登录页面
""" 登录页面 """ from selenium.webdriver.common.by import By from base_page import BasePage, BaseHandle class LoginPage(BasePage): """对象库层(封装元素定位方法)""" def __init__(self): super().__init__() # 获取父类浏览器对象 self.username = (By.ID, 'username') # 用户名 self.password = (By.ID, 'password') # 密码 self.code = (By.ID, 'verify_code') # 验证码 self.login_btn = (By.NAME, 'sbtbutton') # 登录按钮 def find_username(self): """用户名定位方法""" return self.find_element_func(self.username) def find_password(self): """密码定位方法""" return self.find_element_func(self.password) def find_code(self): """验证码定位方法""" return self.find_element_func(self.code) def find_login_btn(self): """登录按钮定位方法""" return self.find_element_func(self.login_btn) class LoginHandle(BaseHandle): """操作层(封装元素操作方法)""" def __init__(self): self.login_page = LoginPage() # 元素对象定位类对象 def input_username(self, name): """用户名输入方法""" self.input_text(self.login_page.find_username(), name) def input_password(self, pwd): """密码输入方法""" self.input_text(self.login_page.find_password(), pwd) def input_code(self, code): """验证码输入方法""" self.input_text(self.login_page.find_code(), code) def click_login_btn(self): """登录按钮点击方法""" self.login_page.find_login_btn().click() class LoginProxy(object): """业务层(封装具体的测试执行步骤)""" def __init__(self): self.login_handle = LoginHandle() # 元素操作类对象 def login_func(self, name, pwd, code): """登录方法""" self.login_handle.input_username(name) # 输入用户名 self.login_handle.input_password(pwd) # 输入密码 self.login_handle.input_code(code) # 输入验证码 self.login_handle.click_login_btn() # 点击登录按钮测试用例的编写
- 代码演示–测试用例
""" 登录测试用例 """ import json import unittest import time import logging from utils import DriverUtil, get_tip_message from loign_page import LoginProxy from parameterized import parameterized logging.basicConfig(level=logging.DEBUG) def build_login_data(): """登录数据构造方法""" with open('./login_data.json', encoding='utf-8') as f: data = json.load(f) data_list = list() # Python 规范要求, 应该使用 list 类实例化一个空列表 for i in data: data_list.append((i.get('username'), i.get('password'), i.get('code'), i.get('is_success'), i.get('expect'))) print(data_list) logging.info(data_list) return data_list class TestTPShopLogin(unittest.TestCase): """登录测试类""" @classmethod def setUpClass(cls) -> None: cls.driver = DriverUtil.get_driver() # 获取浏览器对象 cls.login_proxy = LoginProxy() # 登录页面业务执行对象 @classmethod def tearDownClass(cls) -> None: time.sleep(3) DriverUtil.quit_driver() # 退出浏览器对象 def setUp(self) -> None: self.driver.get('http://127.0.0.1/') # 打开首页 # 点击首页的‘登录’链接,进入登录页面 self.driver.find_element_by_link_text('登录').click() @parameterized.expand(build_login_data()) def test_login_func(self, username, pwd, code, is_success, expect): """登录测试方法""" self.login_proxy.login_func(username, pwd, code) # 条件判断正向用例还是反向用例 if is_success: # 正向用例断言 time.sleep(2) title = self.driver.title # 获取页面标题 self.assertIn(expect, title) # 断言判断 else: # 反向用例断言 time.sleep(2) # 获取错误提示信息 msg = get_tip_message() # 设置断言判断测试结果 self.assertIn(expect, msg) # def test_account_does_not_exist(self): # """用户不存在""" # # self.login_proxy.login_func('13811110000', '123456', '8888') # # time.sleep(2) # # 获取错误提示信息 # msg = get_tip_message() # # # 设置断言判断测试结果 # self.assertIn('账号不存在', msg) # # def test_wrong_password(self): # """密码错误""" # # self.login_proxy.login_func('13800001111', 'error', '8888') # # time.sleep(2) # # 获取错误提示信息 # msg = get_tip_message() # # # 设置断言判断测试结果 # self.assertIn('密码错误', msg) if __name__ == '__main__': unittest.main()准备数据源文件(JSON 文件)
- JSON文件
[ { "desc": "用户名为空", "username": "", "password": "123456", "code": "8888", "is_success": false, "expect": "用户名不能为空" }, { "desc": "密码为空", "username": "13800001111", "password": "", "code": "8888", "is_success": false, "expect": "密码不能为空" }, { "desc": "密码错误", "username": "13800001111", "password": "error", "code": "8888", "is_success": false, "expect": "密码错误" }, { "desc": "登录成功", "username": "13800001111", "password": "123456", "code": "8888", "is_success": true, "expect": "我的账户" } ]编写解析数据源文件的方法
- 代码演示–解析数据源文件方法
import json def build_login_data(): """登录数据构造方法""" with open('./login_data.json', encoding='utf-8') as f: data = json.load(f) data_list = list() # Python 规范要求, 应该使用 list 类实例化一个空列表 for i in data: data_list.append((i.get('username'), i.get('password'), i.get('code'), i.get('is_success'), i.get('expect'))) print(data_list) return data_list利用参数化将数据源文件的中数据引入到测试用例的测试方法中
- 已引入上述测试用例中
数据驱动
定义:
在测试代码逻辑不变的前提下, 通过控制测试数据来决定测试结果的过程
特点:
- 不是工业级标准概念, 不同公司会有不同看法
- 有人认为属于一种模式, 还有人认为只是一种测试思想
- 数据驱动的实现要依赖参数化技术
数据来源:
- 直接写死在测试代码中
- 数据源文件
- JSON
- excel
- XML
- txt
- 数据库
- 能够获取数据接口
- 本地构造数据的方法
版权声明:本文为limy_liu原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。