Python接口自动化之数据驱动
以下主要介绍使用openpyxl模块操作excel及结合ddt实现数据驱动。
如果感觉文章很长,那就是文章很长。
在此之前,我们已经实现了用unittest框架编写测试用例,实现了请求接口的封装,这样虽然已经可以完成接口的自动化测试,但是其复用性并不高。
我们看到每个方法(测试用例)的代码几乎是一模一样的,试想一下,在我们的测试场景中,一个登录接口有可能会有十几条到几十条测试用例,如果每组数据都编写一个方法,这样将会有更多的重复项代码,不仅执行效率不高,也不好维护。
接下来将会对框架进行优化,采用数据驱动方式:
1.把测试数据用excel表格管理起来,代码做封装;
2.用ddt来驱动测试,两部分相互独立。
openpyxl模块
openpyxl模块介绍
openpyxl
是python第三方模块,运用openpyxl库可以进行excel的读和写。
在了解openpyxl模块之前,我们需要先熟悉excel的结构,才能更好理解openpyxl是如何操作excel。
从外到内,首先是一个excel
文件(名),打开excel之后,会看到底部有一个或多个sheet
(工作簿),每个sheet里有很多单元格,总体来说,主要分为三个层级。
在opnepyxl里面,一个Excel文件对应着一个Workbook
对象, 一个Sheet对应着一个Worksheet
对象,而一个单元格对应着一个Cell
对象。了解这些之后,对openpyxl是如何操作excel就比较清楚了。
openpyxl安装
pip install openpyxl
openpyxl简单使用
1 2 3 4 5 6 7 8 9 10 11 12 13 | import openpyxl if __name__ = = '__main__' : path = 'F:/case/test_case.xlsx' # 读取excel文件 workbook = openpyxl.load_workbook(path) # 读取所有sheet sheet = workbook.get_sheet_names() # 获取某个sheet sheet = workbook[sheet[ 0 ]] # 获取某个cell的值 cell_val = sheet.cell(row = 2 , column = 2 ).value print (cell_val) |
以上仅介绍openpyxl常用的语法,有兴趣了解更多内容可自行百度扩展。
Excel用例管理
在项目下,新建一个文件夹:data,文件夹下新建一个cases.xlsx文件,用来存放测试用例。
以下,是一个简单的登录测试用例设计模板:
既然有了用例模板,我们就开始从用openpyxl模块对excel读写数据。如下,在common文件夹下,新建excel_handle.py,用于封装操作excel的类。
excel_handle.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | import openpyxl class ExcelHandler: def __init__( self , file ): self . file = file def open_excel( self , sheet_name): """打开Excel、获取sheet""" wb = openpyxl.load_workbook( self . file ) # 获取sheet_name sheet = wb[sheet_name] return sheet def get_header( self , sheet_name): """获取header(表头)""" wb = self .open_excel(sheet_name) header = [] # 遍历第一行 for i in wb[ 1 ]: # 将遍历出来的表头字段加入列表 header.append(i.value) return header def read_excel( self , sheet_name): """读取所有数据""" sheet = self .open_excel(sheet_name) rows = list (sheet.rows) data = [] # 遍历从第二行开始的每一行数据 for row in rows[ 1 :]: row_data = [] # 遍历每一行的每个单元格 for cell in row: row_data.append(cell.value) # 通过zip函数将两个列表合并成字典 data_dict = dict ( zip ( self .get_header(sheet_name),row_data)) data.append(data_dict) return data @staticmethod def write_excel( file , sheet_name, row, cloumn,data): """Excel写入数据""" wb = openpyxl.load_workbook( file ) sheet = wb[sheet_name] sheet.cell(row, cloumn).value = data wb.save( file ) wb.close() if __name__ = = "__main__" : # 以下为测试代码 excel = ExcelHandler( '../data/cases.xlsx' ) data = excel.read_excel( 'login' ) |
接下来结合ddt实现数据驱动,先简单来介绍下ddt。
ddt介绍及使用
ddt介绍
- 名称:Data-Driven Tests,数据驱动测试
- 作用:由外部数据集合来驱动测试用例的执行
- 核心的思想:数据和测试代码分离
- 应用场景:一组外部数据来执行相同的操作
- 优点:当测试数据发生大量变化的情况下,测试代码可以保持不变
- 实际项目:excel存储测试数据,ddt读取测试数据到单元测试框架(测试用例中)
补充:
所谓数据驱动,就是数据的改变从而驱动自动化测试的执行,最终引起测试结果的改变。说的直白些,就是参数化的应用。
ddt安装
pip install ddt
ddt使用
要想知道ddt到底怎么使用,我们从ddt模块源码中提取出三个重要的函数ddt、unpack、data。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | def ddt( cls ): """ Class decorator for subclasses of ``unittest.TestCase``. Apply this decorator to the test case class, and then decorate test methods with ``@data``. For each method decorated with ``@data``, this will effectively create as many methods as data items are passed as parameters to ``@data``. The names of the test methods follow the pattern ``original_test_name_{ordinal}_{data}``. ``ordinal`` is the position of the data argument, starting with 1. For data we use a string representation of the data value converted into a valid python identifier. If ``data.__name__`` exists, we use that instead. For each method decorated with ``@file_data('test_data.json')``, the decorator will try to load the test_data.json file located relative to the python file containing the method that is decorated. It will, for each ``test_name`` key create as many methods in the list of values from the ``data`` key. """ for name, func in list ( cls .__dict__.items()): if hasattr (func, DATA_ATTR): for i, v in enumerate ( getattr (func, DATA_ATTR)): test_name = mk_test_name(name, getattr (v, "__name__" , v), i) test_data_docstring = _get_test_data_docstring(func, v) if hasattr (func, UNPACK_ATTR): if isinstance (v, tuple ) or isinstance (v, list ): add_test( cls , test_name, test_data_docstring, func, * v ) else : # unpack dictionary add_test( cls , test_name, test_data_docstring, func, * * v ) else : add_test( cls , test_name, test_data_docstring, func, v) delattr ( cls , name) elif hasattr (func, FILE_ATTR): file_attr = getattr (func, FILE_ATTR) process_file_data( cls , name, func, file_attr) delattr ( cls , name) return cls def unpack(func): """ Method decorator to add unpack feature. """ setattr (func, UNPACK_ATTR, True ) return func def data( * values): """ Method decorator to add to your test methods. Should be added to methods of instances of ``unittest.TestCase``. """ global index_len index_len = len ( str ( len (values))) return idata(values) |
ddt:
装饰类,也就是继承自TestCase的类。data:
装饰测试方法。参数是一系列的值。unpack:
传递的是复杂的数据结构时使用。比如使用元组或者列表,添加unpack之后,ddt会自动把元组或者列表对应到多个参数上,字典也可以这样处理;当没有加unpack时,方法的参数只能填一个。
知道了具体应用后,简单来个小例子加深理解。
test_ddt.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import unittest import ddt # 装饰类 @ddt .ddt class DdtDemo(unittest.TestCase): def setUp( self ): pass def tearDown( self ): pass # 装饰方法 @ddt .data(( "15312344578" , "12345678" ), ( "15387654321" , "12345678" )) @ddt .unpack def test_ddt( self , username,password): print (username,password) if __name__ = = '__main__' : unittest.main(verbosity = 2 ) |
运行结果为:
1 2 3 4 | Ran 2 tests in 0.001s OK 15312344578 12345678 15387654321 12345678 |
上面的例子是为了加深理解,接下来介绍excel结合ddt实现数据驱动,优化之前的test_login.py模块。
test_login.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | import unittest from common.requests_handler import RequestsHandler from common.excel_handler import ExcelHandler import ddt import json @ddt .ddt class TestLogin(unittest.TestCase): # 读取excel中的数据 excel = ExcelHandler( '../data/cases.xlsx' ) case_data = excel.read_excel( 'login' ) print (case_data) def setUp( self ): # 请求类实例化 self .req = RequestsHandler() def tearDown( self ): # 关闭session管理器 self .req.close_session() @ddt .data( * case_data) def test_login_success( self ,items): # 请求接口 res = self .req.visit(method = items[ 'method' ],url = items[ 'url' ],json = json.loads(items[ 'payload' ]), headers = json.loads(items[ 'headers' ])) try : # 断言:预期结果与实际结果对比 self .assertEqual(res[ 'code' ], items[ 'expected_result' ]) result = 'Pass' except AssertionError as e: result = 'Fail' raise e finally : # 将响应的状态码,写到excel的第9列,即写入返回的状态码 TestLogin.excel.write_excel( "../data/cases.xlsx" , 'login' , items[ 'case_id' ] + 1 , 9 , res[ 'code' ]) # 如果断言成功,则在第10行(测试结果)写入Pass,否则,写入Fail TestLogin.excel.write_excel( "../data/cases.xlsx" , 'login' , items[ 'case_id' ] + 1 , 10 , result) if __name__ = = '__main__' : unittest.main() |
整体流程如下图:
总结:本文主要介绍openpyxl操作excel,结合ddt实现数据驱动。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
2017-11-23 获取字符串实际长度(包含中英文)