重点模块流程图
环境切换
环境配置
断言模块
使用的一些库
yaml文件处理(yaml包)
safe_load方法
python
# 先open方法,打开文件,再将文件加载为python对象,
# 如果原yaml文件里是字典模式,那返回结果就是字典;如果原来是列表模式,那就返回列表
def read_yaml(self, file):
with open(file, 'r', encoding='utf-8') as file:
data = yaml.safe_load(file)
return data
excel文件处理(openpyxl)
使用的地方:读取excel文件中的测试数据,并以字典格式返回
openpyxl库中常用的类
Workbook:表示一个Excel工作簿,是操作Excel文件的基础类。通过它,可以创建新的工作簿,打开已有的工作簿,以及保存工作簿等。 Worksheet:表示一个Excel工作表,是对单个表格的操作类。通过它,可以读取和修改单元格的值,设置单元格的格式等。 Cell:表示一个Excel单元格,是对单个单元格的操作类。通过它,可以读取和修改单元格的值,获取单元格的行和列等信息。 Chart:表示一个Excel图表,是对图表的操作类。通过它,可以创建图表,设置图表的类型、数据源、位置等。 Style:表示一种单元格格式,是对单元格格式的操作类。通过它,可以设置单元格的字体、颜色、对齐方式等格式。
load_workbook方法
读取excel表格,并返回一个workbook对象,
python
openpyxl.load_workbook(file_name)
load_workbook参数解析
python
openpyxl.load_workbook
(filename, read_only=False, keep_vba=False, data_only=False, keep_links=True, **kwargs)
-
filename: 必需。要加载的Excel文件的路径或文件名。可以是相对路径或绝对路径。
-
read_only: 可选。布尔值,默认为 False。如果设置为 True,则以只读模式打开文件,不允许进行修改。如果设置为 False,则以读写模式打开文件,可以读取和修改数据。
-
keep_vba: 可选。布尔值,默认为 False。如果设置为 True,则在加载文件时保留 VBA 代码。如果设置为 False,则在加载文件时删除 VBA 代码。
-
data_only: 可选。布尔值,默认为 False。如果设置为 True,则只加载数据,不加载样式、公式等信息。如果设置为 False,则加载所有数据和样式。
-
keep_links: 可选。布尔值,默认为 True。如果设置为 True,则在加载文件时保留链接。如果设置为 False,则在加载文件时删除链接。
-
**kwargs: 可选。用于传递其他额外的参数给 openpyxl.load_workbook() 方法。这些参数的具体用途和用法取决于 openpyxl 库的版本和功能。
workbook[sheetname]方法
根据传入的sheet页名称,返回sheet对象
python
#返回结果是个worksheet对象
sheet=workbook[sheetname]
workbook.sheetnames方法
python
#获取excel所有的sheet名称 , 返回结果是个list
sheetnames = workbook.sheetnames
sheet[1]
python
#获取表格中的列名,获取第一行的所有cell对象
cell_row_first = sheet[1]
获取Excel表的列名
python
field_names = [cell.value for cell in sheet[1]]
获取表格中元素值
python
values_rows = sheet.iter_rows(min_row=2, values_only=True)
遍历每一行元素值,进行处理
python
for row_data in values_rows:
# 将excel中的空白值(读出来之后是None类型)替换为空字符串
row = tuple('' if value is None else value for value in row_data)
HTTP请求处理
请求头header
请求头中的contentType有3种常见的类型,对应于post请求的3种body设置
- application/x-www-form-urlencoded
- application/json
- multipart/form-data
POST方法
python
def post(url, data=None, json=None, **kwargs):
r"""Sends a POST request.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) json data to send in the body of the :class:`Request`.
:param **kwargs: Optional arguments that ``request`` takes.
:return : :class:`Response <Response>` object
:rtype : requests.Response
"""
return request("post", url, data=data, json=json, **kwargs)
通常有以下3种传参
- Query(问号后面的参数)
- body
- application/x-www-form-urlencoded
- application/json
- multipart/form-data(可传文件)
data和json参数的区别
若不设置默认的Content-Type,则
- data传入参数是str,默认的type是?(看代码实际运行结果,无type)
python
#代码
url = 'http://httpbin.org/post'
# data = {'a_test': 112233, 'b_test': 223344}
data = '123'
print(type(data))
r = requests.post(url=url, data=data).json()
pprint(r)
#响应结果
<class 'str'>
{'args': {},
'data': '123',
'files': {},
'form': {},
'headers': {'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate',
'Content-Length': '3',
'Host': 'httpbin.org',
'User-Agent': 'python-requests/2.28.2',
'X-Amzn-Trace-Id': 'Root=1-6661be67-57fe55d32da413b669ddfda3'},
'json': 123,
'origin': '101.80.29.22',
'url': 'http://httpbin.org/post'}
- data传入dict,默认的type是application/x-www-form-urlencoded,而且参数进入了form。
python
#代码
url = 'http://httpbin.org/post'
data = {'a_test': 112233, 'b_test': 223344}
# data = '123'
print(type(data))
r = requests.post(url=url, data=data).json()
pprint(r)
# 响应结果
{'args': {},
'data': '',
'files': {},
'form': {'a_test': '112233', 'b_test': '223344'},
'headers': {'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate',
'Content-Length': '27',
'Content-Type': 'application/x-www-form-urlencoded',
'Host': 'httpbin.org',
'User-Agent': 'python-requests/2.28.2',
'X-Amzn-Trace-Id': 'Root=1-6661bdf6-38fe95c02238d1412414d5e8'},
'json': None,
'origin': '101.80.29.22',
'url': 'http://httpbin.org/post'}
- data传入任何类型的对象,只要header指定了Content-Type,则type是指定的type,如下
python
from pprint import pprint
import requests
url = 'http://httpbin.org/post'
# data = {'a_test': 112233, 'b_test': 223344}
header = {}
header['Content-Type'] = 'applicaltion/json'
data = '123'
print(type(data))
r = requests.post(url=url, data=data, headers=header).json()
pprint(r)
# 打印的响应结果
{'args': {},
'data': '123',
'files': {},
'form': {},
'headers': {'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate',
'Content-Length': '3',
'Content-Type': 'applicaltion/json',
'Host': 'httpbin.org',
'User-Agent': 'python-requests/2.28.2',
'X-Amzn-Trace-Id': 'Root=1-66669dbb-450def7e76597530703d0a65'},
'json': 123,
'origin': '101.80.29.21',
'url': 'http://httpbin.org/post'}
- json传入任何类型的对象,或者即使header传入了指定的content-Type,实际传输的也是application/json
POST方法实践
python
# post请求仅传query参数的时候:
if query_params != "{}":
query_dict = json.loads(query_params)
query_string = urlencode(query_dict)
url = url + "?" + query_string
response = requests.post(url, headers=header)
else: # post请求传body参数,要转为json格式,
response = requests.post(url, data=json.dumps(process_data), headers=header)
GET方法
python
response = requests.get(url=url, params=json.loads(query_params),
headers=header)
通常有以下2种传参方式
- Rest(pathvariables路径参数)
- Query(问号后面的参数)
python
if method.lower() == 'get':
response = requests.get(url=url, params=json.loads(query_params), headers=header)
elif method.lower() == 'post':
if query_params != "{}":
query_dict = json.loads(query_params)
query_string = urlencode(query_dict)
url = url + "?" + query_string
response = requests.post(url, headers=header)
else:
response = requests.post(url, data=json.dumps(process_data), headers=header)
elif method.lower() == 'put':
response = requests.put(url, data=json.dumps(process_data), headers=header)
参考文章
连接MongoDB(pymongo库)
初始化Mongo连接对象
python
# 初始化Mongo连接对象
if self.mongodbinfo.get("password") is None:
url = "mongodb://%(host)s:%(port)s" % {
"host": self.mongodbinfo['host'],
"port": self.mongodbinfo['port'],
}
else:
# 扩展有密码时连接的配置信息;
url = "mongodb://%(user)s:%(password)s@%(host)s:%(port)s/?authSource=%(database)s" % {
"user": self.mongodbinfo["user"],
"password": self.mongodbinfo["password"],
"host": self.mongodbinfo["host"],
"port": self.mongodbinfo["port"],
"database": self.mongodbinfo["database"]
}
# print(url)
# 将线程安全的连接池封装到对象中;
self.connect_client = pymongo.MongoClient(url)
获取指定Mongo库中的所有集合(表)
python
result = self.connect_client[self.mongodbinfo['database']]
.list_collection_names()
筛选满足条件的一条记录
python
def fetch_one(self, collection_name: str, filters: dict = None) -> dict:
"""
查询一条符合条件的数据信息
:param collection_name: 集合的名称;
:param filters: dict; 过滤条件;
:return: dict; 筛选结果,字典信息;
example:
filters = {"name": "python入门"}
v = mongo_helper.fetch_one("test", filters)
print(v)
"""
conn = self.connect_client[self.mongodbinfo['database']][collection_name]
result = conn.find_one(filters)
# self.close_connect()
return result
调用上述方法的代码
python
collection_name = 'tcollector'
mongodb = ConnectMongo().connect_mongodb(collection_name)
filters = {"taskid": taskid.lower()}
db_records = mongodb.fetch_one(collection_name, filters)
删除单条or多条记录
python
def delete_one(self, collection_name: str, filters: dict) -> int:
"""
删除单条的数据信息;
:param collection_name:
:param filters:
:return: int; 删除数据的条数;
example:
filters = {"name": "批量修改回来"}
v = mongo_helper.delete_one("test", filters)
print(v, type(v))
"""
conn = self.connect_client[self.mongodbinfo['database']][collection_name]
result = conn.delete_one(filter=filters)
# self.close_connect()
return result.deleted_count
def delete_many(self, collection_name: str, filters: dict) -> int:
"""
删除多条的数据信息;
:param collection_name: 集合的名称;
:param filters: dict; 过滤条件;
:return: int; 返回删除的条数;
example:
filters = {"name": "批量修改回来"}
v = mongo_helper.delete_many("test", filters)
print(v, type(v))
"""
conn = self.connect_client[self.mongodbinfo['database']][collection_name]
result = conn.delete_many(filter=filters)
# self.close_connect()
return result.deleted_count
连接MySql
初始化mysql连接对象
python
def __init__(self, dbInfo):
self.host = dbInfo['host']
self.port = dbInfo['port']
self.db = dbInfo['db']
self.user = dbInfo['user']
self.password = dbInfo['password']
self.charset = 'utf8'
def connect(self, restype='dict'):
self.close()
self.conn = pymysql.connect(host=self.host,
port=self.port,
database=self.db,
user=self.user,
password=self.password,
charset=self.charset,
autocommit=True)
if restype == 'dict':
self.cursor = self.conn.cursor(cursor=pymysql.cursors.DictCursor)
else:
self.cursor = self.conn.cursor()
查询指定sql并返回结果
python
def query(self, sql):
self.cursor.execute(sql)
data = self.cursor.fetchall()
return data
执行增删改语句
python
def exec(self, *args, **kwargs):
self.cursor.execute(*args, **kwargs)
调用上述方法
python
# 调用query方法
sql = "SELECT top 3 * FROM Form"
db = ConnectDB().connect_db('Form')
res = db.query(sql)
print(res)
# 调用exec方法
for tablename in tablenames:
dbs = ConnectDB().connect_db(tablename)
# 构造数据库的删除语句
if len(self.condition) != 0:
query_sql = " Delete FROM " + tablename + self.condition
if order_by is not None:
query_sql += self.order_by
# 兼容相同表名对应多个库的情况(有分库)
if isinstance(dbs, list):
for db in dbs:
# 删除用exec
db.exec(query_sql)
else:
dbs.exec(query_sql)
Json库
json.loads()方法
将字符串转成Python对象(目前接口自动化项目中常用的是将json格式的字符串转为dict)
json.dumps()方法
将传入的Python对象转成字符串(项目中常用的是将dict对象转为json格式)
参考文章
读取文件(open)
常用方法
python
def modify_evn_conf(self, file_path, env):
with open(file_path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
data['env'] = env
with open(file_path, 'w') as f:
yaml_str = yaml.dump(data)
yaml_str = yaml_str.rstrip('\n')
# 修改文件内容
f.write(yaml_str)
常用的一些数据结构
字典
定义字典
python
person = {'name': 'Bob', 'age': 26, 'gender': 'Male'}
# 定义一个空字典
dict_a = {}
字典的遍历
添加元素到字典中 person['city'] = 'Beijing' print(person) 输出 {'name': 'Bob', 'age': 27, 'gender': 'Male', 'city': 'Beijing'}
删除字典中的元素 del person['gender'] print(person) 输出 {'name': 'Bob', 'age': 27, 'city': 'Beijing'}
字典的遍历
- 同时遍历字典的key和value
python
# expect_record是一个字典。items方法会返回一个包含键和值的元组
for key_expect, value_expect in expect_record.items():
- 遍历所有的key
python
# 创建一个字典
my_dict = {'a': 1, 'b': 2, 'c': 3}
# 遍历字典的键
for key in my_dict:
print(f"键: {key}, 值: {my_dict[key]}")
# 使用 keys() 方法遍历键
for key in my_dict.keys():
print(f"键: {key}")
- 遍历所有的value
python
# 创建一个字典
my_dict = {'a': 1, 'b': 2, 'c': 3}
# 使用 values() 方法遍历值
for value in my_dict.values():
print(f"值: {value}")
根据key获取内容
python
# 有2种方式可以获取内容
person.get('age')
person['name']
更新指定key对应的值
python
person['age'] = 27
添加元素到字典中
python
# key为'city'如果不存在,则会新增一个元素
person['city'] = 'Beijing'
遍历字典并修改
ini
# 创建一个字典
my_dict = {'a': 1, 'b': 2, 'c': 3}
# 遍历字典并修改
for key in list(my_dict.keys()): # 使用 list 来避免在遍历中修改字典
if my_dict[key] < 2:
del my_dict[key]
print(my_dict)
合并两个字典
update方法
列表
创建列表
python
list1 = [1, 2, 3, 4, 5.5, 6]
list2 = ['python', 'Python自学网', '后端学习']
list3 = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
['python', 'java']
]
增加元素
- append方法
python
li = ['xzc',[1,2,3],'123']
li.append('abc')
li.append(1)
print(li)
#输出['xzc',[1,2,3],'123','abc',1]
- insert方法
python
li = ['xzc',[1,2,3],'123']
li1 = li.insert(2,'ooo')
#在索引为2的'123'之前插入'ooo'
- extend方法
以最小元素追加,可迭代对象:字符串类型、列表等,Int类型不能迭代添加
python
li = ['xzc',[1,2,3],'123']
li2 = li.extend('哈喽')
print(li2)
#结果['xzc',[1,2,3],'123','哈','喽']
li3 = li.extend([1,2,3])
print(li3)
#结果['xzc',[1,2,3],'123',1,2,3]
修改元素
- 单个修改
python
li = ['xzc',[1,2,3],'123']
li[0] = 'sun' #把xzc改成sun
#利用replace()方法
li[0] = li[0].capitalize() #sun的首字母大写,再放回原处
li[0] = li[0].replace('x','a') #把'xzc'找出来,然后把x换成a
- 切片修改
python
li = ['xzc',[1,2,3],'123']
li[0:2] = '你好啊'
print(li) #输出['你','好','啊','123']
删除元素
- pop()方法:按照索引删除,并返回删除的元素
python
li = ['xzc',[1,2,3],'123']
name = li.pop(1) #删除[1,2,3]
print(name,li)#输出[1,2,3] ['xzc','123']
- remove()方法:按照元素删除,无返回值
python
li = ['xzc',[1,2,3],'123']
li.remove('xzc')#删除xzc
- clear()方法:清空列表
python
li = ['xzc',[1,2,3],'123']
li.clear() #清空
print(li) #输出[]
- del:直接删除列表
python
li = ['xzc',[1,2,3],'123']
del li
print(li)#此时输出列表会报错,因为已经被删除,列表不存在
- 切片删除
python
li = ['xzc',[1,2,3],'123']
# 左闭右开
del li[0:2] #删除'xzc',[1,2,3]
其他常用方法
python
li = ['xzc',[1,2,3],'123]
# 输出列表的长度
print(len(i))
# 指定元素出现的次数
li.count('xzc')
# 寻找指定元素的索引
li.index('xzc')
# 排序(默认从小到大)
li = [1,5,6,9,8,7]
li.sort()
# 逆向排序(从大到小)
li.sort(reverse=True)
# 列表反转
li.reverse()
元组
参考:Python 元组
字符串-split方法
pytest相关
fixture的执行顺序
【pytest官方文档】解读fixtures - 11. fixture的执行顺序,3要素详解(长文预警) - 把苹果咬哭的测试笔记 - 博客园
pytest_addoption
pytest_addoption 是 Pytest 测试框架提供的一个钩子函数。在编写测试用例时,可以使用该钩子函数来定义自定义的命令行选项。
通常情况下,Pytest 框架会自动解析 -h 或 --help 选项,并显示帮助信息。但是,如果我们需要添加一些额外的命令行选项来配置测试环境或传递参数给测试用例,就可以使用 pytest_addoption 函数。
通过在测试模块中定义 pytest_addoption 函数,我们可以通过调用 parser.addoption 方法来定义自定义选项。这些自定义选项将可用于在运行测试时从命令行传递参数。
以下是一个示例:
python
content of conftest.py
def pytest_addoption(parser):
parser.addoption("--url", action="store", default="http://example.com",
help="Specify the URL for the tests")
parser.addoption("--env", action="store", default="qa",
choices=["qa", "staging", "prod"],
help="Specify the environment for the tests")
在这个例子中,我们定义了两个自定义选项 --url 和 --env。在运行测试时,可以使用 --url 选项来指定测试的 URL,使用 --env 选项来指定测试的环境。 例如:
bash
pytest --url=http://myapp.com --env=staging
然后,在测试代码中,可以通过解析命令行选项来获取这些参数:
ini
content of test_example.py
def test_something(request):
url = request.config.getoption("--url")
env = request.config.getoption("--env")
# 使用 url 和 env 进行测试
通过解析 request 对象的 config 属性,我们可以使用 getoption 方法获取命令行选项的值,并将其用于测试逻辑中。
总结来说,pytest_addoption 函数允许我们在 Pytest 测试框架中定义自定义的命令行选项,以方便地配置和定制测试环境。
Pytest 框架会自动识别和执行 conftest.py 文件中的钩子函数,包括 pytest_addoption 函数。
当 Pytest 执行测试时,会按照一定的顺序搜索当前目录及其父级目录下的所有 conftest.py 文件。一旦找到了一个 conftest.py 文件,它就会加载其中的钩子函数和其他配置。
在加载 conftest.py 文件时,Pytest 会检查文件中是否存在定义了特定名称的钩子函数。如果发现了 pytest_addoption 函数定义,Pytest 就会调用该函数并将一个 parser 对象作为参数传递给它。
parser 对象是 Pytest 内部的一个配置解析器,它允许我们使用 addoption 方法来定义自己的命令行选项。
因此,只要将 pytest_addoption 函数定义在 conftest.py 文件中,并确保 conftest.py 文件与需要使用这些选项的测试模块在同一目录或其父级目录中,Pytest 就能够自动识别和执行 pytest_addoption 函数。这样,在运行测试时就能够使用自定义的命令行选项。