Pandas、Excel实现 Pytest 数据驱动
引言
前面分享的推文 自动化测试必会—数据驱动DDT 介绍过 unittest 框架中操作 JSON 和 YAML 文件实现数据驱动。那么在 pytest 中,又该如何实现呢?
Pytest 操作 JSON/YAML 文件实现数据驱动
首先按照使用 pytest 进行数据驱动的基本思路创建一个读取 JSON 文件和 YAML 文件的方法:
def test_read_data_from_json_yaml(data_file): return_value = [] data_file_path = os.path.abspath(data_file) print(data_file_path) _is_yaml_file = data_file_path.endswith((".yml", ".yaml")) with codecs.open(data_file_path, 'r', 'utf-8') as f: #从YAML或JSON文件中加载数据 if _is_yaml_file: data = yaml.safe_load(f) else: data = json.load(f) for i, elem in enumerate(data): if isinstance(data, dict): key, value = elem, data[elem] if isinstance(value, dict): case_data = [] for v in value.values(): case_data.append(v) return_value.append(tuple(case_data)) else: return_value.append((value,)) return return_value
test_read_data_from_json_yaml 这个函数方法,实现了自动读取 JSON 文件和 YAML 文件,并且把 JSON 文件和 YAML 文件中的数据提取出来,并按照 pytest.mark.parametrize 可接收的方式返回。
有了这个函数方法,JSON 或者 YAML 文件的数据通过此方法就可以转换成 pytest.mark.parametrize 认可的格式直接传入了。
下面实践一下,在 APITest 项目根目录下创建如下文件目录:
|--APITest |--tests_pytest_ddt |--test_baidu_ddt.py |--test_baidu_ddt.json |--test_baidu_ddt.yaml |--test_baidu_ddt.xlsx |--__init__.py |--conftest.py
其中,test_baidu_ddt.json 文件的内容如下:
{ "case1": { "search_string": "testing", "expect_string": "Testing" }, "case2": { "search_string": "hello_world.com", "expect_string": "Testing" } }
test_baidu_ddt.yaml 文件的内容如下:
"case1": "search_string": "testing" "expect_string": "Testing" "case2": "search_string": "hello_world.com" "expect_string": "Testing"
test_baidu_ddt.py 文件的代码如下:
import codecs import json import os import time import pytest import yaml def test_read_data_from_json_yaml(data_file): return_value = [] data_file_path = os.path.abspath(data_file) print(data_file_path) _is_yaml_file = data_file_path.endswith((".yml", ".yaml")) with codecs.open(data_file_path, 'r', 'utf-8') as f: #从YAML或JSON文件中加载数据 if _is_yaml_file: data = yaml.safe_load(f) else: data = json.load(f) for i, elem in enumerate(data): if isinstance(data, dict): key, value = elem, data[elem] if isinstance(value, dict): case_data = [] for v in value.values(): case_data.append(v) return_value.append(tuple(case_data)) else: return_value.append((value,)) return return_value @pytest.mark.baidu class TestBaidu: @pytest.mark.parametrize('search_string, expect_string', test_read_data_from_json_yaml('tests_pytest_ddt/test_baidu_ddt.yaml')) def test_baidu_search(self, login, search_string, expect_string): driver, s, base_url = login driver.get(base_url + "/") driver.find_element_by_id("kw").send_keys(search_string) driver.find_element_by_id("su").click() time.sleep(2) search_results = driver.find_element_by_xpath('//*[@id="1"]/h3/a').get_attribute('innerHTML') print(search_results) assert (expect_string in search_results) is True if __name__ == "__main__": pytest.main(['-s', '-v'])
此文件中的代码与 Pytest 测试框架——数据驱动 中讲解的代码几乎相同,唯一的改变在于增加了一个新方法 test_read_data_from_json_yaml,另外 @pytest.mark.parametrize 的参数,从直接提供参数变成了从文件提供参数。
(test_read_data_from_json_yaml('tests_pytest_ddt/test_baidu_ddt.yaml'))
在命令行中通过如下方式运行:
D:\Python_Test\APITest>pytest tests_pytest_ddt -s -v
运行结束后查看结果如下:
可以看到,两个测试用例都执行了,并且 YAML 文件中的数据被正确读取。
那么如果我们现在要执行 JSON 文件中的数据该如何操作呢?把上述代码中传入的 yaml 文件后缀换成 json 文件后缀,再次执行即可。
Pytest 操作 Excel 文件实现数据驱动
在实际应用中,也有很多公司使用 Excel 来做数据驱动。在 python 中,读写 Excel 的 library 很多,常见的有 xlrd、xlwt,以及openpyxl。由于 xlrd 和 xlwt 只能分别用作读和写,实现同样的读写操作,它的代码行数较多,故逐渐变得不再流行。所以下面将重点介绍 openpyxl 的使用。
openpyxl 安装
pip install openpyxl
openpyxl 使用
from openpyxl import load_workbook, Workbook if __name__ == "__main__": # 创建一个workbook file_name = r'c:\test.xlsx' wb = Workbook() # 创建一个sheet,名为Testing,把它插入到最前的位置 wb.create_sheet('Testing',0) # 创建一个sheet,名为TEST,把它插入index为1的位置 wb.create_sheet('TEST',1) # 保存表格 wb.save(file_name) # 读和写 # 初始化表格 wb2 = load_workbook(file_name) # 读,获取所有的sheet名称 print(wb2.sheetnames) # 获取sheet名为Testing的表格 s = wb2['Testing'] # 将A1行的值设置为Testing s['A1'] = 'Testing' # 将第2行,第一列的值设置为1 s.cell(row=2, column=1).value = 1 # 打印第2行第一列单元格的值 --方法1 print(s.cell(row=2,column=1).value) # 打印第2行第一列单元格的值 --方法2 print(s['A2'].value) # 保存表格 wb.save(file_name)
如上述代码块所示,简单介绍了 openpyxl 的用法,涉及创建表格、创建 sheet 名、读取单元格的值、设置单元格的值等部分。你可以看出使用 openpyxl 操作 excel 是相对比较简单的。
openpyxl 结合 pytest 实现数据驱动
文件 test_baidu_ddt.xlsx 的内容如下(sheet 名 Testing):
来写个读 Excel 的方法,代码如下:
def test_read_data_from_excel(excel_file, sheet_name): return_value = [] # 判断文件是否存在 if not os.path.exists(excel_file): raise ValueError("File not exists") # 打开指定的sheet wb = load_workbook(excel_file) # 按照pytest接受的格式输出数据 for s in wb.sheetnames: if s == sheet_name: sheet = wb[sheet_name] for row in sheet.rows: return_value.append([col.value for col in row]) # 第一行数据是标题,故skip掉 return return_value[1:]
更新 test_baidu_ddt.py 文件,把 test_read_data_from_excel 的方法加进去,更新后的代码如下:
import codecs import json import os import time import pytest import yaml from openpyxl import load_workbook def test_read_data_from_json_yaml(data_file): return_value = [] data_file_path = os.path.abspath(data_file) print(data_file_path) _is_yaml_file = data_file_path.endswith((".yml", ".yaml")) with codecs.open(data_file_path, 'r', 'utf-8') as f: # 从YAML或JSON文件中加载数据 if _is_yaml_file: data = yaml.safe_load(f) else: data = json.load(f) for i, elem in enumerate(data): if isinstance(data, dict): key, value = elem, data[elem] if isinstance(value, dict): case_data = [] for v in value.values(): case_data.append(v) return_value.append(tuple(case_data)) else: return_value.append((value,)) return return_value def test_read_data_from_excel(excel_file, sheet_name): return_value = [] if not os.path.exists(excel_file): raise ValueError("File not exists") wb = load_workbook(excel_file) for s in wb.sheetnames: if s == sheet_name: sheet = wb[sheet_name] for row in sheet.rows: return_value.append([col.value for col in row]) print(return_value) return return_value[1:] @pytest.mark.baidu class TestBaidu: # 注意,此处调用我换成了读Excel的方法 @pytest.mark.parametrize('search_string, expect_string', test_read_data_from_excel(r'D\Python_Test\APITest\tests_pytest_ddt\test_baidu_ddt.xlsx', 'Testing')) def test_baidu_search(self, login, search_string, expect_string): driver, s, base_url = login driver.get(base_url + "/") driver.find_element_by_id("kw").send_keys(search_string) driver.find_element_by_id("su").click() time.sleep(2) search_results = driver.find_element_by_xpath('//*[@id="1"]/h3/a').get_attribute('innerHTML') print(search_results) assert (expect_string in search_results) is True if __name__ == "__main__": pytest.main(['-s', '-v','tests_pytest_ddt'])
在命令行中通过如下方式再次运行即可:
D:\Python_Test\APITest>pytest tests_pytest_ddt -s -v
运行后查看结果,会发现测试被正确执行,测试数据是从 Excel 指定的 sheet 名中获取的。
Pandas 实现数据驱动
openpyxl 操作 Excel 非常简洁,但是相对于 Pandas 来说,还不够简洁,而且 openpyxl 运算效率不如 Pandas,特别是当表格行项目过多时,openpyxl 运算较慢。
Pandas 是一个强大的分析结构化数据的工具集,它的使用基础是 Numpy(提供高性能的矩阵运算);Pandas 用于数据挖掘和数据分析,同时也提供数据清洗功能。使用 Pandas 操作 Excel 数据,就会变得十分简单。
Pandas 安装
# pandas默认依赖xlrd库,故先安装xlrd pip install xlrd # 安装Pandas pip install Pandas
Pandas 语法
import Pandas as pd # 首先初始化,engine默认是xlrd s = pd.ExcelFile(path_or_buffer, engine=None) # 接着parse s.parse(sheet_name=0,header=0,names=None,index_col=None,usecols=None, squeeze=False,converters=None,true_values=None,false_values=None, skiprows=None,nrows=None,na_values=None,parse_dates=False, date_parser=None,thousands=None,comment=None,skipfooter=0, convert_float=True,mangle_dupe_cols=True,**kwds,)
Pandas 读取 Excel 文件非常简单,首先使用 Pandas 初始化 ExcelFile。其两个参数path_or_buffer 是我们要读取的文件路径。
Excel 文件名称建议使用英文路径及英文命名方式,不要使用中文。
import pandas as pd path_or_buffer = r'D:\Python_Test\APITest\tests_pytest_ddt\test_baidu_ddt.xlsx'
engine 是供 Pandas 使用的 engine,可选项为“xlrd”、“openpyxl”、“odf”、“pyxlsb”,如果不提供,默认使用 xlrd。
parse 函数的参数
初始化后,可以使用 s.parse() 函数。parse 函数有非常多的参数,在此只列出常用的几个。
sheet_name: Excel 的 sheet 名
sheet_name 可以是整型数字、列表名,或者上述两者组合。
# 通过整型数字读取。读取第一个sheet。Pandas sheet名下标以0开始 s = pd.ExcelFile(path_or_buffer, sheet_name = 0) # 通过列表名读取 data = s.parse(sheet_name = 'Testing') # 通过index读取。读取第一个sheet data = s.parse(sheet_name = 0) #组合读取。读取第4个sheet,名为Testing的sheet以及第7个sheet data = s.parse(sheet_name = [3, 'Testing', 'Sheet6'])
header:使用哪一行作为列名
header 默认值为 0,即第一行,也可以设置为 [0, x]。
(例如 [0,1] 意味着将前两行作为多重索引)
data = s.parse(sheet_name = 'Testing', header = 0)
需要注意:Pandas 默认使用第一行为 header,所以在 Excel 里,第一行必须是 title,如果第一行是数据,将会导致第一行数据被遗漏。如果不想要 header,可以参数传递 header=None。
usecols:待读取的列
usecols 接收整型,从 0 开始,例如 [0, 1, 2],也可以使用列名例如 “A:D, F”,表示读取 A 到 D 列,以及 F 列。
data = s.parse(sheet_name = 'Testing', usecols='A:D')
skiprows:读取时,跳过特定行
skiprows=n,跳过前 n 行;skiprows = [a, b, c],跳过第 a+1、b+1、c+1 行(索引从0开始)
data = s.parse(sheet_name = 'iTesting', skiprows = [1,2,3])
nrows:需要读取的行数
仅仅列出要读取的行数
data = s.parse(sheet_name = 'Testing', nrows = 3)
Pandas 结合 Pytest 实现数据驱动
了解了 Pandas 语法后,来看下如何使用 Pandas 读取 Excel 数据:
def test_read_data_from_pandas(excel_file, sheet_name): if not os.path.exists(excel_file): raise ValueError("File not exists") # 初始化 s = pd.ExcelFile(excel_file) # 解析Excel Sheet df = s.parse(sheet_name) # 以list格式返回数据 return df.values.tolist()
可以看到,使用 pandas 读取 Excel 数据更加简洁方便。
最后,来更新下 test_baidu_ddt.py 文件,更新后的代码如下:
import codecs import json import os import time import pytest import yaml from openpyxl import load_workbook import pandas as pd # 读取Yaml文件和Json文件 def test_read_data_from_json_yaml(data_file): return_value = [] data_file_path = os.path.abspath(data_file) print(data_file_path) _is_yaml_file = data_file_path.endswith((".yml", ".yaml")) with codecs.open(data_file_path, 'r', 'utf-8') as f: #从YAML或JSON文件中加载数据 if _is_yaml_file: data = yaml.safe_load(f) else: data = json.load(f) for i, elem in enumerate(data): if isinstance(data, dict): key, value = elem, data[elem] if isinstance(value, dict): case_data = [] for v in value.values(): case_data.append(v) return_value.append(tuple(case_data)) else: return_value.append((value,)) return return_value # 读取Excel 文件 -- openpyxl def test_read_data_from_excel(excel_file, sheet_name): return_value = [] if not os.path.exists(excel_file): raise ValueError("File not exists") wb = load_workbook(excel_file) for s in wb.sheetnames: if s == sheet_name: sheet = wb[sheet_name] for row in sheet.rows: return_value.append([col.value for col in row]) print(return_value) return return_value[1:] # 读取Excel文件 -- Pandas def test_read_data_from_pandas(excel_file, sheet_name): if not os.path.exists(excel_file): raise ValueError("File not exists") s = pd.ExcelFile(excel_file) df = s.parse(sheet_name) return df.values.tolist() @pytest.mark.baidu class TestBaidu: @pytest.mark.parametrize('search_string, expect_string', test_read_data_from_pandas(r'D:\Python_Test\APITest\tests_pytest_ddt\test_baidu_ddt.xlsx', 'Testing')) def test_baidu_search(self, login, search_string, expect_string): driver, s, base_url = login driver.get(base_url + "/") driver.find_element_by_id("kw").send_keys(search_string) driver.find_element_by_id("su").click() time.sleep(2) search_results = driver.find_element_by_xpath('//*[@id="1"]/h3/a').get_attribute('innerHTML') print(search_results) assert (expect_string in search_results) is True if __name__ == "__main__": pytest.main(['-s', '-v', 'tests_pytest_ddt'])
在命令行中通过如下方式再次运行即可:
D:\Python_Test\APITest>pytest tests_pytest_ddt -s -v
运行后查看结果,可以发现测试被正确执行,测试数据是通过 Pandas 从 Excel 指定的 sheet 名中获取的。
事实上,Pandas 不仅仅能读取 Excel 文件,还可以读取 HTML 文件、TXT 文件、JSON 文件、数据库文件 (.sql) 等。在数据分析领域,Pandas 使用非常广泛,更多具体的 Pandas 使用,请自行查阅。
欢迎关注【无量测试之道】公众号,回复【领取资源】
Python编程学习资源干货、
Python+Appium框架APP的UI自动化、
Python+Selenium框架Web的UI自动化、
Python+Unittest框架API自动化、
资源和代码 免费送啦~
文章下方有公众号二维码,可直接微信扫一扫关注即可。
备注:我的个人公众号已正式开通,致力于测试技术的分享,包含:大数据测试、功能测试,测试开发,API接口自动化、测试运维、UI自动化测试等,微信搜索公众号:“无量测试之道”,或扫描下方二维码:
添加关注,让我们一起共同成长!