1. 安装Pycharm,并选择编译器为目前计算机上安装的python3
链接:https://pan.baidu.com/s/1R-dJlysywpT4tRiVAJJeSA
提取码:y07n
复制这段内容后打开百度网盘手机App,操作更方便哦
File-Settings-Project: untitled8-Project Interpreter,选择python编译器
2. 接口测试知识点及相关python包的介绍
针对系统间或者系统组件间的测试,检测数据交换、数据传递。相比于UI,接口测试效率更高,无需考虑页面内容。接口文档从开发那里拿,根据业务逻辑,做接口的功能测试。
接口测试要做:
- 业务功能测试,看业务逻辑,根据get/post方式,来设计不同的用例,传正常参数、异常参数等是否有正确的提醒
- 异常情况测试
- 边界测试,输入输出参数一定要测试
- 参数组合测试
- 安全测试,批量提交、批量抽奖等,加密传输
- 性能测试
- 接口异常测试:也要看第三方接口超时时也要模拟,查看被测试软件异常处理情况。
- 安全指标:SQL注入,特殊字符规避等,如果不做处理数据库会抛异常。
怎么做接口测试:因为目前大部分项目前后端主要是基于http协议的接口,所以测试时主要通过调用工具或代码模拟http请求的发送与接收,以达到接口测试目的。
常见工具:Postman、jmeter、soupUI、java+httpclient、robotframework+httplibrary
常用Python请求库:Urllib、urllib2、requests等,前面两个爬虫的时候用的多。接口测试常用包Requests。可以通过pip install requests来安装该包。如果需要安装指定版本:pip install requests==2.20.0更新pip:python -m pip install --upgrade pip
备注:Urllib和Urllib2是python2中提供的一个用于操作url的模块,但是提供了不同的功能。在python3中,urllib2被合并到urllib库中,在我们爬取网页的时候,经常用到这个库。
操作表格的包openpyxl:pip install openpyxl=2.5.12
json:Python的字典类型和json串是不一样的,因为服务型需要的是json数据类型,所以做接口测试的时候需要将字典类型转换为json串类型。Json本身是存储和交换文本信息的语法,类似XML。Json串本身是字符串类型,但是有特定的格式,Json值可以是:
- 数字,整数或浮点数
- 字符串,在双引号中
- 逻辑值,true或者false
- 数组,在中括号中
- 对象,在大括号中
- null
3. 接口框架的搭建
新建工程-文件-文件功能描述:
- 新建工程并新建文件interface_auto_test.py文件(工程存放的路径最好都是中文的,也最好不要在C盘)
interface_auto_test.py:主程序。
- 新建目录directory名为TestData,目录打开新建表格inter_test_data.xlsx,表格设计测试用例,4个sheet。为了将数据和程序分离。
TestData路径下的inter_test_data.xlsx设计:
表格设计结构:这个包含测试数据的表格,有多个sheet,首先要有一个主sheet,然后其他sheet作为测试用例,给sheet起适当的名字做标识。
主sheet表设计结构:记录了所有的接口,包括接口请求地址、接口名字、请求方式、传参方式(form、data)、接口的测试用例具体在哪个sheet、是否需要执行(接口数量大只测试一部分或者已经执行过了可以在这里标识)。
用例sheet的设计结构:用例编号、请求参数的数据、headers、依赖数据(一个接口需要依赖其他接口的请求参数或者响应参数)、响应code(接口的响应code,如果不是200肯定不是正常的响应body,如果是200,就去校验响应body)、响应数据(接口返回的业务内容即响应body)、数据存储(如注册接口,会用到用户名密码,登录接口需要用到用户名密码,所以注册后需要存储相应的用户名密码,存成字典类型。还要明确是存请求参数值还是存响应参数值)、checkpoint(对用例的监测点,比如业务的响应的code是不是符合预期,这里要区别接口的响应code和业务的响应code)、是否需要执行(y、n)、状态(执行成功还是失败,如果接口的响应code不是200这里是失败,如果checkpoint没有成功这里也是失败,)、错误信息(没有日志的情况下把错误写到这里方便排查错误原因,后期这列可以加可不加,不加的话就查日志比较耗时)。
- 新建工具包Python Package名为utils,在包中新建ParseExcel.py文件
utils包下的ParseExcel.py:操作表格的文件。
- 新建配置包Python Package名为config,包的话就自动包含了__init__文件,在包中新建public_data.py文件,设置绝对路径等公共数据大家都可以调用。
config包下的public_data.py:设置绝对路径、映射。
4. 调试步骤
- 在新建好测试数据的表格后,在utils包下的ParseExcel.py中通过if __name__ == '__main__':方式下加载表格并打印表格相关信息,如获取sheet名称,某一个单元格的内容等,如果没有问题证明ParseExcel.py文件是通的。
- 在config包下的public_data.py文件中,配置文件绝对路径,方便主程序调用。
import os
# 整个项目的根目录绝对路径 # os.path.dirname(__file__)返回脚本的路径 baseDir = os.path.dirname((os.path.dirname(__file__))) print("baseDir:",baseDir) # 设置数据文件的绝对路径 file_path = baseDir + "\TestData\inter_test_data.xlsx" print("file_path:",file_path) 结果: baseDir: C:/Users/c1/PycharmProjects/untitled8 file_path: C:/Users/c1/PycharmProjects/untitled8\TestData\inter_test_data.xlsx
- 在interface_auto_test.py文件,引入工具包和配置包中文件下的变量,引入方式如下,引入后也可针对特定的包调试一下是否是通的。
# ----coding:utf-8 ---- import requests import json from utils.ParseExcel import ParaseExcel from config.public_data import * from utils.HttpClient import HttpClient import sys from importlib import reload reload(sys) def main(): parseE = ParaseExcel() parseE.loadWorkBook(file_path) sheetObj = parseE.getSheetByName(u"API") print(sheetObj) activeList = parseE.getColumn(sheetObj, 7) # 第7列取是否需要执行 print(activeList) for i in activeList: print(i.value)
if __name__ == '__main__': main() 结果: <Worksheet "API"> (<Cell 'API'.G1>, <Cell 'API'.G2>, <Cell 'API'.G3>, <Cell 'API'.G4>) Active y y n
- 在第三步上可以得到哪些用例需要执行,是y的就是需要执行的,继续更新代码:
def main(): parseE = ParaseExcel() parseE.loadWorkBook(file_path) sheetObj = parseE.getSheetByName(u"API") activeList = parseE.getColumn(sheetObj, 7) for idx,cell in enumerate(activeList[1:]): print(idx) if cell.value == "y": #需要执行的接口所在行的行对象 rowObj = parseE.getRow(sheetObj, idx + 2) for j in rowObj: print(j.value) else: print("接口设置忽略执行") 结果: 0 1 用户注册 http://xx.xxx.xx.xx:xxxx/register/ post form 注册接口用例 y 1 2 用户登录 http://xx.xxx.xx.xx:xxxx/login/ post form 登录接口用例 y 2 接口设置忽略执行
- 因为枚举类型的下标是从0开始的,如果想避免从0开始,上述代码可以优化为如下,将枚举的下标改成指定起点,即从第二行开始读取
def main(): parseE = ParaseExcel() parseE.loadWorkBook(file_path) sheetObj = parseE.getSheetByName(u"API") activeList = parseE.getColumn(sheetObj, 7) for idx,cell in enumerate(activeList[1:], 2): print(idx) if cell.value == "y": #需要执行的接口所在行的行对象 rowObj = parseE.getRow(sheetObj, idx) for j in rowObj: print(j.value) else: print("接口设置忽略执行") 结果: 2 1 用户注册
http://xx.xxx.xx.xx:xxxx/register/post form 注册接口用例 y 3 2 用户登录
http://xx.xxx.xx.xx:xxxx/login/post form 登录接口用例 y 4 接口设置忽略执行
- 通过print(type(rowObj))得知其类型为tuple,所以可以通过key取得value值,得到每行的每列内容
def main(): parseE = ParaseExcel() parseE.loadWorkBook(file_path) sheetObj = parseE.getSheetByName(u"API") activeList = parseE.getColumn(sheetObj, 7) for idx, cell in enumerate(activeList[1:], 2): print(idx) if cell.value == "y": # 需要执行的接口所在行的行对象 rowObj = parseE.getRow(sheetObj, idx) print(type(rowObj)) # 可以得出结果是tuple类型,所以可以通过key、value取 apiName = rowObj[1].value requestUrl = rowObj[2].value requestMethod = rowObj[3].value paramsType = rowObj[4].value apiTestCaseFileName = rowObj[5].value print(apiName, requestUrl, requestMethod, paramsType, apiTestCaseFileName) else: print("接口设置忽略执行") 结果: 2 <class 'tuple'> 用户注册 http://xx.xxx.xx.xx:xxxx/register/ post form 注册接口用例 3 <class 'tuple'> 用户登录 http://xx.xxx.xx.xx:xxxx/login/ post form 登录接口用例 4 接口设置忽略执行
- 第六步中对excel列的操作都是通过写死的数字去做的,更简便的方式是将列映射成对应的数字,并放到公共文件中,需要的时候就去读取。这样的好处是,下次列增加或者减少可以通过改公共文件就可以了,其他的文件不需要改动。在配置包config下的public_data.py文件中新增映射如下(新增下划线部分):
# ----coding:utf-8 ---- import os # 整个项目的根目录绝对路径 # os.path.dirname(__file__)返回脚本的路径 baseDir = os.path.dirname((os.path.dirname(__file__))) # print("baseDir:",baseDir) # 设置数据文件的绝对路径 file_path = baseDir + "\TestData\inter_test_data.xlsx" # print("file_path:",file_path) #API表映射关系,表格列索引从0开始 API_APIName = 2 #接口名称 API_RequestUrl = 3 #请求URL API_RequestMethod = 4 #请求方法 API_paramsType = 5 #参数类型 API_APITestCase = 6 #测试用例名称 API_Active = 7 #用例是否执行标识 #测试用例表映射 CASE_RequestData = 1 #请求的数据 CASE_RelyData = 2 #请求依赖的上游响应数据 CASE_ResponseCode = 3 #请求返回的状态码 CASE_ResponseData = 4 #请求响应内容 CASE_DataStore = 5 #数据存储 CASE_CheckPoint = 6 #请求返回内容检查点 CASE_Active = 7 #用例是否执行标识 CASE_Status = 8 #执行结果状态 CASE_ErrorIinfo = 9 #执行结果错误信息
- 映射设置好后将interface_auto_test.py文件中的相关字段更新到对应映射字段(下划线为更新处)
def main(): parseE = ParaseExcel() parseE.loadWorkBook(file_path) sheetObj = parseE.getSheetByName(u"API") activeList = parseE.getColumn(sheetObj, API_Active) for idx, cell in enumerate(activeList[1:], 2): print(idx) if cell.value == "y": # 需要执行的接口所在行的行对象 rowObj = parseE.getRow(sheetObj, idx) apiName = rowObj[API_APIName - 1].value requestUrl = rowObj[API_RequestUrl - 1].value requestMethod = rowObj[API_RequestMethod - 1].value paramsType = rowObj[API_paramsType - 1].value apiTestCaseFileName = rowObj[API_APITestCase - 1].value print(apiName,requestUrl,requestMethod,paramsType,apiTestCaseFileName)
- interface_auto_test.py中读用例sheet表,准备执行测试用例
def main(): parseE = ParaseExcel() parseE.loadWorkBook(file_path) sheetObj = parseE.getSheetByName(u"API") activeList = parseE.getColumn(sheetObj, API_Active) for idx, cell in enumerate(activeList[1:], 2): print(idx) if cell.value == "y": # 需要执行的接口所在行的行对象 rowObj = parseE.getRow(sheetObj, idx) apiName = rowObj[API_APIName - 1].value requestUrl = rowObj[API_RequestUrl - 1].value requestMethod = rowObj[API_RequestMethod - 1].value paramsType = rowObj[API_paramsType - 1].value apiTestCaseFileName = rowObj[API_APITestCase - 1].value # print(apiName,requestUrl,requestMethod,paramsType,apiTestCaseFileName) # 下一步读用例sheet表,准备执行测试用例 caseSheetObj = parseE.getSheetByName(apiTestCaseFileName) caseActiveObj = parseE.getColumn(caseSheetObj, CASE_Active) for c_idx, col in enumerate(caseActiveObj[1:], 2): if col.value == "y": # 说明此case行需要执行 caseRowObj = parseE.getRow(caseSheetObj, c_idx) # 先写这两个字段,请求参数和依赖数据 requestData = caseRowObj[CASE_RequestData - 1].value relyData = caseRowObj[CASE_RelyData - 1].value print(requestData, relyData) else: print("用例被忽略执行") else: print("接口被设置忽略执行")
- 下一步interface_auto_test.py需要拼接接口请求参数,发送接口请求。首先要在工具包Utiles包中新建HttpClient.py文件,这里是一个发送http请求功能的文件,在这里封装了HttpClient类,这个类包含两种请求get和post请求,都是私有类型的方法,外界不能直接调用这两个方法,只能通过request方法来调用。封装的好处是让其方便调用,也使主程序简洁。
# ----coding:utf-8 ---- import requests import json class HttpClient(object): # 继承object基础类 def __init__(self): pass def request(self, requestMethod, requestUrl, paramsType, requestData=None, headers=None, cookies=None): if requestMethod.lower() == "post": if paramsType == "form": response = self.__post(url=requestUrl, data=json.dumps(eval(requestData)), headers=headers, cookies=cookies) return response elif paramsType == "json": response = self.__post(url=requestUrl, json=json.dumps(eval(requestData)), headers=headers, cookies=cookies) return response elif requestMethod == "get": if paramsType == "url": request_url = "%s%s" % (requestUrl, requestData) response = self.__get(url=requestUrl, headers=headers, cookies=cookies) return response elif paramsType == "params": response = self.__get(url=requestUrl, params=requestData, headers=headers, cookies=cookies) return response def __post(self, url, data=None, json=None, **kwargs): response = requests.post(url=url, data=data, json=json) return response def __get(self, url, params=None, **kwargs): response = requests.get(url=url, params=params) return response
可以在该文件中调试一下该程序是否是通的: if __name__ == '__main__': hc = HttpClient() res = hc.request("post", "http://xx.xxx.xx.xx:xxxx/register/", "form",'{"username":"caaaaaaaaa99","password":"abc123456","email":"[email protected]"}') print(res.json()) 结果: {'code': '00', 'userid': 25414} 备注: JSON是post请求要用,params是get请求要用。小实例说一下字符串拼接,如果要拼接的多是汉字,最好用%s方式。
data=json.dumps(eval(requestData))代码中这句话的意思是,我们从表格中读取出来的内容都是Unicode字符串,不能通过key、value的形式去操作,需要操作的是字典类型,所以需要用eval
- 在interface_auto_test.py中通过from utils.HttpClient import HttpClient导入第十步新建的类,来编写拼接接口请求参数,发送接口请求的代码,如只测试注册接口,代码如下划线部分(ctrl+?快速注释/快速取消注释)
# ----coding:utf-8 ---- import requests import json from utils.ParseExcel import ParaseExcel from config.public_data import * from utils.HttpClient import HttpClient import sys from importlib import reload reload(sys) def main(): parseE = ParaseExcel() parseE.loadWorkBook(file_path) sheetObj = parseE.getSheetByName(u"API") activeList = parseE.getColumn(sheetObj, API_Active) for idx, cell in enumerate(activeList[1:], 2): if cell.value == "y": # 需要执行的接口所在行的行对象 rowObj = parseE.getRow(sheetObj, idx) apiName = rowObj[API_APIName - 1].value requestUrl = rowObj[API_RequestUrl - 1].value requestMethod = rowObj[API_RequestMethod - 1].value paramsType = rowObj[API_paramsType - 1].value apiTestCaseFileName = rowObj[API_APITestCaseFileName - 1].value # print(apiName,requestUrl,requestMethod,paramsType,apiTestCaseFileName) # 下一步读用例sheet表,准备执行测试用例 caseSheetObj = parseE.getSheetByName(apiTestCaseFileName) caseActiveObj = parseE.getColumn(caseSheetObj, CASE_Active) for c_idx, col in enumerate(caseActiveObj[1:], 2): if col.value == "y": # 说明此case行需要执行 caseRowObj = parseE.getRow(caseSheetObj, c_idx) # 先写这两个字段,请求参数和依赖数据 requestData = caseRowObj[CASE_RequestData -1 ].value relyData = caseRowObj[CASE_RelyData - 1].value # 拼接接口请求参数,发送接口请求 httpC = HttpClient() print(requestMethod,requestUrl,paramsType,requestData) response = httpC.request(requestMethod=requestMethod, requestUrl=requestUrl, paramsType=paramsType, requestData=requestData) print(response.json()) else: print("用例被忽略执行") else: print("接口被设置忽略执行") if __name__ == '__main__': main() 结果:(注册用例表中共有三条用例,所有执行结果为三条,因为前两条用例中是正确的) post http://xx.xxx.xx.xx:xxxx/register/ form 请求数据(参数长度,包含的字符类型等),所以code码返回的都是0,第三条用例用户名和密码都不是正确合法的请求参数所以code码返回02{"username":"c666666666666","password":"c666666666666","email":"[email protected]"} {'code': '00', 'userid': 25419} post http://xx.xxx.xx.xx:xxxx/register/ form {"username":"c666666666667","password":"c666666666667","email":"[email protected]"} {'code': '00', 'userid': 25420} post http://xx.xxx.xx.xx:xxxx/register/ form {"username":"c2","password":"c2","email":"[email protected]"} {'username': 'c2', 'password': 'c2', 'code': '02', 'email': '[email protected]'}
- 截至上面,已经将不需要依赖数据的接口做了测试,接下来对需要依赖数据的接口做测试,首先依赖数据来自两方面,可能来自其他接口的请求参数,也可能来自其他接口的响应参数。比如用户注册后的响应参数会返回userid,在查询用户博文接口时会将userid作为请求参数传给后端。 RelyData的格式设置如下: {"request":{"username":"用户注册->1->username","password":"用户注册->1->password"},"response":{"userid":"用户注册->1->userid"}},解析如下:请求参数里取username值,而username对应的是用户注册接口下的第一条case下的username。响应参数里面取userid,而userid对应的是用户注册接口下的第一条case下的userid。 需要把请求的数据和响应的数据存起来,因为如果接口运行到当前时是无法拿上一个接口的请求或响应的内容的,目前代码是将这些数据放到了内存中(没有放到数据库中), 首先要明确存储依赖数据的格式,请求依赖数据示例如下:
响应依赖数据示例如下:{"用户注册":{"1":{"username":"cdongliangde12","password":"cdongliangde12"}},"用户登录":{"1":{"username":cdongliangde12}}}这个格式的好处是通过key来取,不需要遍历,效率高。
然后在config包下的public_data.py下加入如下代码,用作公用:{"用户注册":{"1":{"userid":1}},"用户登录":{"1":{"token":"dfdfffdkj43l545hj46"}}}
然后新建python package名为action,在action下新建python file名为get_relp.py,代码如下:# 新建两个字典对象 # 存储请求参数里面的依赖数据 REQUEST_DATA = {} # 存储响应对象里面的依赖数据 RESPONSE_DATA = {}
然后在interface_auto_test.py中增加如下划线内容# -*- coding:utf-8 -*- from config.public_data import REQUEST_DATA, RESPONSE_DATA class GetKey(object): def __init__(self): pass # dataSource相当于要做处理的数据源,在表格中对应登录接口用例的第一列RequestData # relyData相当于要获取的依赖数据源,在表格中对应登录接口用例的第二列RelyData # 在对依赖数据做处理前要将得到的依赖数据存下来,所以在config中的public_data下写了两个字典,存依赖数据,依赖数据的格式是: # {"用户注册": {"1": {"username": "cxcvs2cxcvs2", "password": "cxcvs2cxcvs2"}}} # 依赖规则的结构就是relyData的结构: {"request": {"username": "用户注册->1", "password": "用户注册->1"},"response":{"userid":"用户注册->1"}} @classmethod def get(cls, dataSource, relyData): data = dataSource.copy() # 通过拷贝,原dataSource数据不变,可以拷贝出来一个专门做数据操作 for key, value in relyData.items(): # 这里对依赖规则的结构中key value做一个判断 if key == "request": # 说明应该去字典REQUEST_DATA中获取值 for k, v in value.items(): interfaceName, case_id = v.split("->") data[k] = REQUEST_DATA[interfaceName][case_id][k] elif key == "response": # 说明应该去RESPONSE_DATA获取值 for k, v in value.items(): interfaceName, case_id = v.split("->") data[k] = RESPONSE_DATA[interfaceName][case_id][k] return data # 先假定REQUEST_DATA中是这样的依赖数据,调试一下代码是否能跑通 REQUEST_DATA = {"用户注册": {"1": {"username": "cxcvs2cxcvs2", "password": "cxcvs2cxcvs2"}}} 在该文件下调试: if __name__ == '__main__': s = {"username": "", "password": ""} rely = {"request": {"username": "用户注册->1", "password": "用户注册->1"}} print(GetKey.get(s, rely)) # 登录接口用例的第一列RequestData就会被更新为获取到的依赖数据,即s有原来的{"username": "", "password": ""}获取到了依赖数据变成了{'username': 'cxcvs2cxcvs2', 'password': 'cxcvs2cxcvs2'} 结果: {'username': 'cxcvs2cxcvs2', 'password': 'cxcvs2cxcvs2'}
# ----coding:utf-8 ---- import requests import json from utils.ParseExcel import ParaseExcel from config.public_data import * from utils.HttpClient import HttpClient from action.get_rely import GetKey import sys from importlib import reload reload(sys) def main(): parseE = ParaseExcel() parseE.loadWorkBook(file_path) sheetObj = parseE.getSheetByName(u"API") activeList = parseE.getColumn(sheetObj, API_Active) for idx, cell in enumerate(activeList[1:], 2): if cell.value == "y": # 需要执行的接口所在行的行对象 rowObj = parseE.getRow(sheetObj, idx) apiName = rowObj[API_APIName - 1].value requestUrl = rowObj[API_RequestUrl - 1].value requestMethod = rowObj[API_RequestMethod - 1].value paramsType = rowObj[API_paramsType - 1].value apiTestCaseFileName = rowObj[API_APITestCaseFileName - 1].value # 下一步读用例sheet表,准备执行测试用例 caseSheetObj = parseE.getSheetByName(apiTestCaseFileName) caseActiveObj = parseE.getColumn(caseSheetObj, CASE_Active) for c_idx, col in enumerate(caseActiveObj[1:], 2): if col.value == "y": # 说明此case行需要执行 caseRowObj = parseE.getRow(caseSheetObj, c_idx) # 先写这两个字段,请求参数和依赖数据 requestData = caseRowObj[CASE_RequestData -1 ].value relyData = caseRowObj[CASE_RelyData - 1].value if relyData: # 发送接口请求之前,先做依赖数据的处理 requestData = "%s" %GetKey.get(eval(requestData), eval(relyData)) # 拼接接口请求参数,发送接口请求 httpC = HttpClient() print(requestMethod, requestUrl, paramsType, requestData) response = httpC.request(requestMethod=requestMethod, requestUrl=requestUrl, paramsType=paramsType, requestData=requestData ) print(response.json()) else: print("用例被忽略执行") else: print("接口被设置忽略执行") if __name__ == '__main__': main()
- 在上面的例子中,实现了取依赖数据的内容,这一步要实现存储依赖数据。首先在action包中新建python文件data_store.py,用来实现数据存储的一些代码逻辑。在测试用例表中的DataStore列下有一个数据存储规则,如截图
# -*- coding:utf-8 -*- from config.public_data import REQUEST_DATA,RESPONSE_DATA class RelyDataStore(object): def __init__(self): pass @classmethod def do(cls, storePoint, apiName, caseId, request_source={}, response_source={}): # 为了避免依赖数据不从请求参数里或者响应body里取,将二者放到了参数最后给个默认值即可 # 测试用例表中DataStore,解析这个存储点{"request":["username","password"],"response":["userid"]} for key, value in storePoint.items(): if key == "request": # 说明存储的数据来自请求参数 # 举例如s = {"request": ["username", "password"]}中的 # ["username", "password"],第一次for循环用username当作i去执行 for i in value: # 举例如r = {"username": "CBASDFGTREWQ", "password": "123443211234", "email": "[email protected]"}中 # s中的username在r中的username中存在,所以对应if i in request_source if i in request_source: if apiName not in REQUEST_DATA: # 判断REQUEST_DATA中没有‘注册接口’这个key,因为刚开始字典是空的,所以对应REQUEST_DATA[apiName] = {str(caseId): {i: request_source[i]}} # 说明存储数据的结构还未生成,需要指明数据存储结构 # REQUEST_DATA['注册接口'] = {'1': {'username': 'CBASDFGTREWQ'}} REQUEST_DATA[apiName] = {str(caseId): {i: request_source[i]}} else: # 在第二次用passoword做for循环i的时候,因为apiName in REQUEST_DATA,所以直接来到else这步 # 说明存储数据结构中,最外层,结构完整 # 在这里因为caseId已经在第一次用username做for循环的时候写进去了,所以对应REQUEST_DATA[apiName][caseId] = request_source[i] if str(caseId) in REQUEST_DATA[apiName]: # REQUEST_DATA[apiName][str(caseId)][i] = request_source[i] else: REQUEST_DATA[apiName][str(caseId)] = {i: request_source[i]} else: print("请求参数中不存在字段", i) elif key == "response": # 说明存储的数据来自响应body for j in value: if j in response_source: if apiName not in RESPONSE_DATA: RESPONSE_DATA[apiName] = {str(caseId): {j: response_source[j]}} else: if str(caseId) in RESPONSE_DATA[apiName]: RESPONSE_DATA[apiName][str(caseId)][j] = response_source[j] else: RESPONSE_DATA[apiName][str(caseId)] = {j: response_source[j]} else: print("响应body中不存在字段", j) if __name__ == '__main__': # 准备一个请求数据源{"username":"changjinling688","password":"changjinling688","email":"[email protected]"} r = {"username": "CBASDFGTREWQ", "password": "123443211234", "email": "[email protected]"} # 准备一个存储点{"request":["username","password"]} s = {"request": ["username", "password"], "response": ["userid"]} # 准备一个响应body res = {"userid": 12, "code": "00"} RelyDataStore.do(s, "注册接口", 1, r, res) print("存储后的REQUEST_DATA:", REQUEST_DATA) print("存储后的RESPONSE_DATA:", RESPONSE_DATA) 调试结果: 存储后的REQUEST_DATA: {'注册接口': {'1': {'username': 'CBASDFGTREWQ', 'password': '123443211234'}}} 存储后的RESPONSE_DATA: {'注册接口': {'1': {'userid': 12}}}
- 在interface_auto_test.py中增加如下划线内容,实现存储依赖数据
# ----coding:utf-8 ---- # time: 2019/2/20--11:47 import requests import json from utils.ParseExcel import ParaseExcel from config.public_data import * from utils.HttpClient import HttpClient from action.get_rely import GetKey from action.data_store import RelyDataStore import sys from importlib import reload reload(sys) def main(): parseE = ParaseExcel() parseE.loadWorkBook(file_path) sheetObj = parseE.getSheetByName(u"API") activeList = parseE.getColumn(sheetObj, API_Active) for idx, cell in enumerate(activeList[1:], 2): if cell.value == "y": # 需要执行的接口所在行的行对象 rowObj = parseE.getRow(sheetObj, idx) apiName = rowObj[API_APIName - 1].value requestUrl = rowObj[API_RequestUrl - 1].value requestMethod = rowObj[API_RequestMethod - 1].value paramsType = rowObj[API_paramsType - 1].value apiTestCaseFileName = rowObj[API_APITestCaseFileName - 1].value # 下一步读用例sheet表,准备执行测试用例 caseSheetObj = parseE.getSheetByName(apiTestCaseFileName) caseActiveObj = parseE.getColumn(caseSheetObj, CASE_Active) for c_idx, col in enumerate(caseActiveObj[1:], 2): if col.value == "y": # 说明此case行需要执行 caseRowObj = parseE.getRow(caseSheetObj, c_idx) # 先写这两个字段,请求参数和依赖数据 requestData = caseRowObj[CASE_RequestData -1 ].value relyData = caseRowObj[CASE_RelyData - 1].value dataStore = caseRowObj[CASE_DataStore - 1].value if relyData: # 发送接口请求之前,先做依赖数据的处理 requestData = "%s" %GetKey.get(eval(requestData), eval(relyData)) # 拼接接口请求参数,发送接口请求 httpC = HttpClient() response = httpC.request(requestMethod=requestMethod, requestUrl=requestUrl, paramsType=paramsType, requestData=requestData ) # print(response.json()) responseData = response.json() # 这里拿出来的就是字典对象,所以不需要用eval转 # 存储依赖数据 # 存储依赖数据必须要判断一下用例表中DataStore如果不为空则需要存储 if dataStore: RelyDataStore.do(eval(dataStore), apiName, c_idx - 1, eval(requestData), responseData) print("REQUEST_DATA:", REQUEST_DATA) print("RESPONSE_DATA:", RESPONSE_DATA) else: print("用例被忽略执行") else: print("接口被设置忽略执行") if __name__ == '__main__': main() 调试结果: REQUEST_DATA: {'用户注册': {'1': {'username': 'zhangdongliang666', 'password': 'zhangdongliang666'}}} RESPONSE_DATA: {'用户注册': {'1': {'code': '00'}}} REQUEST_DATA: {'用户注册': {'1': {'username': 'zhangdongliang666', 'password': 'zhangdongliang666'}, '2': {'username': 'zhangdongliang667', 'password': 'zhangdongliang667'}}} RESPONSE_DATA: {'用户注册': {'1': {'code': '00'}, '2': {'code': '00'}}} REQUEST_DATA: {'用户注册': {'1': {'username': 'zhangdongliang666', 'password': 'zhangdongliang666'}, '2': {'username': 'zhangdongliang667', 'password': 'zhangdongliang667'}, '3': {'username': 'c13', 'password': 'c13'}}} RESPONSE_DATA: {'用户注册': {'1': {'code': '00'}, '2': {'code': '00'}, '3': {'code': '02'}}}
- 接下来做结果的比对,ResponseCode和CheckPoint,对于ResponseCode,可以直接在代码中做处理,在interface_auto_test.py中增加如下划线加一句就行。对于CheckPoint,请参考第16步
# ----coding:utf-8 ---- # author: Chang Jinling # time: 2019/2/20--11:47 import requests import json from utils.ParseExcel import ParaseExcel from config.public_data import * from utils.HttpClient import HttpClient from action.get_rely import GetKey from action.data_store import RelyDataStore import sys from importlib import reload reload(sys) def main(): parseE = ParaseExcel() parseE.loadWorkBook(file_path) sheetObj = parseE.getSheetByName(u"API") activeList = parseE.getColumn(sheetObj, API_Active) for idx, cell in enumerate(activeList[1:], 2): if cell.value == "y": # 需要执行的接口所在行的行对象 rowObj = parseE.getRow(sheetObj, idx) apiName = rowObj[API_APIName - 1].value requestUrl = rowObj[API_RequestUrl - 1].value requestMethod = rowObj[API_RequestMethod - 1].value paramsType = rowObj[API_paramsType - 1].value apiTestCaseFileName = rowObj[API_APITestCaseFileName - 1].value # 下一步读用例sheet表,准备执行测试用例 caseSheetObj = parseE.getSheetByName(apiTestCaseFileName) caseActiveObj = parseE.getColumn(caseSheetObj, CASE_Active) for c_idx, col in enumerate(caseActiveObj[1:], 2): if col.value == "y": # 说明此case行需要执行 caseRowObj = parseE.getRow(caseSheetObj, c_idx) # 先写这两个字段,请求参数和依赖数据 requestData = caseRowObj[CASE_RequestData -1 ].value relyData = caseRowObj[CASE_RelyData - 1].value dataStore = caseRowObj[CASE_DataStore - 1].value if relyData: # 发送接口请求之前,先做依赖数据的处理 requestData = "%s" %GetKey.get(eval(requestData), eval(relyData)) # 拼接接口请求参数,发送接口请求 httpC = HttpClient() response = httpC.request(requestMethod=requestMethod, requestUrl=requestUrl, paramsType=paramsType, requestData=requestData ) if response.status_code == 200: print(response.json()) responseData = response.json() # 这里拿出来的就是字典对象,所以不需要用eval转 # 存储依赖数据 # 存储依赖数据必须要判断一下用例表中DataStore如果不为空则需要存储 if dataStore: RelyDataStore.do(eval(dataStore), apiName, c_idx - 1, eval(requestData), responseData) print("REQUEST_DATA:", REQUEST_DATA) print("RESPONSE_DATA:", RESPONSE_DATA) else: print(response.status_code) else: print("用例被忽略执行") else: print("接口被设置忽略执行") if __name__ == '__main__': main()
- CheckPoint,对于CheckPoint要明确,对于响应body,比如是{"code":"00","userid":2}这类型的,这里的userid我们无法预测到一个用户注册后的userid是什么,所以可以只检测其数据类型是否是一个整数,检测的时候可以用正则去匹配。
首先需要在action包中新建python文件check_result.py,目前大致将设置校验规则为:等价、数据类型、正则# -*- coding:utf-8 -*- import re class CheckResult(object): def __init__(self): pass @classmethod def check(self, responseObj, checkPoint): # 需要把响应的结果传进来 # {"code":"00","userid":2} 这里的code是业务code,接口的响应code在interface_auto_test中已经加了 errorKey = {} # 存储检测过程中只要有一个不满足预期就加到这里来,以后可以放到ErrorInfo字段下 for key, value in checkPoint.items(): # 检验规则:等价、数据类型、正则 # {"code":"00","userid":{"value":"\w+"}} if isinstance(value, str): # 说明是等值校验 if responseObj[key] != value: errorKey[key] = responseObj[key] elif isinstance(value, dict): sourceData = responseObj[key] # 接口返回的真实值 if "value" in value: # 说明是通过正则校验 regStr = value["value"] rg = re.match(regStr, "%s" % sourceData) if not rg: errorKey[key] = sourceData elif "type" in value: # 说明是校验数据类型 typeS = value["type"] if typeS == "N": # 说明是整型 if not isinstance(sourceData, int): errorKey[key] = sourceData return errorKey if __name__ == '__main__': r = {"code": "01", "userid": 88.88, "id": "abc"} c = {"code": "00", "userid": {"type": "N"}, "id": {"value": "\d+"}} print(CheckResult.check(r, c)) 调试结果: {'code': '01', 'userid': 88.88, 'id': 'abc'}
- 在interface_auto_test.py中增加如下划线内容,将响应body和checkpoint做对比,如下划线:
# ----coding:utf-8 ---- # author: Chang Jinling # time: 2019/2/20--11:47 import requests import json from utils.ParseExcel import ParaseExcel from config.public_data import * from utils.HttpClient import HttpClient from action.get_rely import GetKey from action.data_store import RelyDataStore from action.check_result import CheckResult import sys from importlib import reload reload(sys) def main(): parseE = ParaseExcel() parseE.loadWorkBook(file_path) sheetObj = parseE.getSheetByName(u"API") activeList = parseE.getColumn(sheetObj, API_Active) for idx, cell in enumerate(activeList[1:], 2): if cell.value == "y": # 需要执行的接口所在行的行对象 rowObj = parseE.getRow(sheetObj, idx) apiName = rowObj[API_APIName - 1].value requestUrl = rowObj[API_RequestUrl - 1].value requestMethod = rowObj[API_RequestMethod - 1].value paramsType = rowObj[API_paramsType - 1].value apiTestCaseFileName = rowObj[API_APITestCaseFileName - 1].value # 下一步读用例sheet表,准备执行测试用例 caseSheetObj = parseE.getSheetByName(apiTestCaseFileName) caseActiveObj = parseE.getColumn(caseSheetObj, CASE_Active) for c_idx, col in enumerate(caseActiveObj[1:], 2): if col.value == "y": # 说明此case行需要执行 caseRowObj = parseE.getRow(caseSheetObj, c_idx) # 先写这两个字段,请求参数和依赖数据 requestData = caseRowObj[CASE_RequestData -1 ].value relyData = caseRowObj[CASE_RelyData - 1].value dataStore = caseRowObj[CASE_DataStore - 1].value checkPoint = caseRowObj[CASE_CheckPoint - 1].value if relyData: # 发送接口请求之前,先做依赖数据的处理 requestData = "%s" %GetKey.get(eval(requestData), eval(relyData)) # 拼接接口请求参数,发送接口请求 httpC = HttpClient() response = httpC.request(requestMethod=requestMethod, requestUrl=requestUrl, paramsType=paramsType, requestData=requestData ) if response.status_code == 200: print(response.json()) responseData = response.json() # 这里拿出来的就是字典对象,所以不需要用eval转 # 存储依赖数据 # 存储依赖数据必须要判断一下用例表中DataStore如果不为空则需要存储 if dataStore: RelyDataStore.do(eval(dataStore), apiName, c_idx - 1, eval(requestData), responseData) # print("REQUEST_DATA:", REQUEST_DATA) # print("RESPONSE_DATA:", RESPONSE_DATA) # 比对结果 errorKey = CheckResult.check(responseData, eval(checkPoint)) if not errorKey: print("校验结果通过") else: print("校验失败", errorKey) else: print(response.status_code) else: print("用例被忽略执行") else: print("接口被设置忽略执行") if __name__ == '__main__': main() 调试结果: {'code': '00', 'userid': 25437} 校验结果通过 {'code': '00', 'userid': 25438} 校验结果通过 {'username': 'c1313', 'password': 'c1313', 'code': '02', 'email': '[email protected]'} 校验结果通过 {'code': '03', 'params': {'username': 'zhangdongliang656', 'password': 'zhangdongliang656'}} 校验失败 {'code': '03'}
- 如上面调试结果所示,校验失败,是因为登录时要求密码是加密的,而且是用md5加密,首先在utils包下新建md5_encrypt.py文件,代码如下:
然后需要在action包的get_rely.py下,添加如下代码:# -*- coding:utf-8 -*- import hashlib # 第一次创建md5实例对象,会加载一些这个类的一些初始方法和信息, # 如果加密一个字符串后不进行md5实例初始化,再继续加密另一个字符串时, # md5会带一些刚开始的一些初始化信息对第二个字符串就行加密, # 和重新初始化md5后再进行加密的结果是不一样的 # 所以,每次做md5加密的时候都需要重新创建一个md5的实例对象,否则可能得不到准确的加密信息 def md5_encrypt(text): m5 = hashlib.md5() text = text.encode(encoding='utf-8') m5.update(text) value = m5.hexdigest() return value ''' # 由于MD5模块在python3中被移除 # 在python3中使用hashlib模块进行md5操作 import hashlib # 待加密信息 str = 'this is a md5 test.' # 创建md5对象 m = hashlib.md5() # Tips # 此处必须encode # 若写法为m.update(str) 报错为: Unicode-objects must be encoded before hashing # 因为python3里默认的str是unicode # 或者 b = bytes(str, encoding='utf-8'),作用相同,都是encode为bytes b = str.encode(encoding='utf-8') m.update(b) str_md5 = m.hexdigest() print('MD5加密前为 :' + str) print('MD5加密后为 :' + str_md5) # 另一种写法:b‘’前缀代表的就是bytes str_md5 = hashlib.md5(b'this is a md5 test.').hexdigest() print('MD5加密后为 :' + str_md5) '''
# -*- coding:utf-8 -*- from config.public_data import REQUEST_DATA, RESPONSE_DATA from utils import md5_encrypt class GetKey(object): def __init__(self): pass # dataSource相当于要做处理的数据源,在表格中对应登录接口用例的第一列RequestData # relyData相当于要获取的依赖数据源,在表格中对应登录接口用例的第二列RelyData # 在对依赖数据做处理前要将其存下来,所以在config中的public_data下写了两个字典,存依赖数据,依赖数据的格式是: # {"用户注册": {"1": {"username": "cxcvs2cxcvs2", "password": "cxcvs2cxcvs2"}}} # 依赖规则的结构就是relyData的结构: {"request": {"username": "用户注册->1", "password": "用户注册->1"},"response":{"userid":"用户注册->1"}} @classmethod def get(cls, dataSource, relyData): data = dataSource.copy() # 通过拷贝,原dataSource数据不变,可以拷贝出来一个专门做数据操作 for key, value in relyData.items(): # 这里对依赖规则的结构中key value做一个判断 if key == "request": # 说明应该去字典REQUEST_DATA中获取值 for k, v in value.items(): interfaceName, case_id = v.split("->") # 处理登录密码md5加密的问题 val = REQUEST_DATA[interfaceName][case_id][k] if k == "password": data[k] = md5_encrypt.md5_encrypt(val) else: data[k] = val elif key == "response": # 说明应该去RESPONSE_DATA获取值 for k, v in value.items(): interfaceName, case_id = v.split("->") data[k] = RESPONSE_DATA[interfaceName][case_id][k] return data # 先假定REQUEST_DATA中是这样的依赖数据 # REQUEST_DATA = {"用户注册": {"1": {"username": "zhangdongliang666", "password": "zhangdongliang666"}}} if __name__ == '__main__': s = {"username": "", "password": ""} rely = {"request": {"username": "用户注册->1", "password": "用户注册->1"}} print(GetKey.get(s, rely)) # 登录接口用例的第一列RequestData就会被更新为获取到的依赖数据,即s有原来的{"username": "", "password": ""}获取到了依赖数据变成了{'username': 'cxcvs2cxcvs2', 'password': 'cxcvs2cxcvs2'}
- 在interface_auto_test.py中增加如下划线内容,将响应body和checkpoint做对比,这里引入了md5加密的方法,所以对于登录用例就可以执行通过了:(一般公司不会用md5加密,因为md5加密后不可逆)
# ----coding:utf-8 ---- import requests import json from utils.ParseExcel import ParaseExcel from config.public_data import * from utils.HttpClient import HttpClient from action.get_rely import GetKey from action.data_store import RelyDataStore from action.check_result import CheckResult import sys from importlib import reload reload(sys) def main(): parseE = ParaseExcel() parseE.loadWorkBook(file_path) sheetObj = parseE.getSheetByName(u"API") activeList = parseE.getColumn(sheetObj, API_Active) for idx, cell in enumerate(activeList[1:], 2): if cell.value == "y": # 需要执行的接口所在行的行对象 rowObj = parseE.getRow(sheetObj, idx) apiName = rowObj[API_APIName - 1].value requestUrl = rowObj[API_RequestUrl - 1].value requestMethod = rowObj[API_RequestMethod - 1].value paramsType = rowObj[API_paramsType - 1].value apiTestCaseFileName = rowObj[API_APITestCaseFileName - 1].value # 下一步读用例sheet表,准备执行测试用例 caseSheetObj = parseE.getSheetByName(apiTestCaseFileName) caseActiveObj = parseE.getColumn(caseSheetObj, CASE_Active) for c_idx, col in enumerate(caseActiveObj[1:], 2): if col.value == "y": # 说明此case行需要执行 caseRowObj = parseE.getRow(caseSheetObj, c_idx) # 先写这两个字段,请求参数和依赖数据 requestData = caseRowObj[CASE_RequestData -1 ].value relyData = caseRowObj[CASE_RelyData - 1].value dataStore = caseRowObj[CASE_DataStore - 1].value checkPoint = caseRowObj[CASE_CheckPoint - 1].value if relyData: # 发送接口请求之前,先做依赖数据的处理 requestData = "%s" %GetKey.get(eval(requestData), eval(relyData)) # 拼接接口请求参数,发送接口请求 httpC = HttpClient() response = httpC.request(requestMethod=requestMethod, requestUrl=requestUrl, paramsType=paramsType, requestData=requestData ) if response.status_code == 200: print(response.json()) responseData = response.json() # 这里拿出来的就是字典对象,所以不需要用eval转 # 存储依赖数据 # 存储依赖数据必须要判断一下用例表中DataStore如果不为空则需要存储 if dataStore: RelyDataStore.do(eval(dataStore), apiName, c_idx - 1, eval(requestData), responseData) # print("REQUEST_DATA:", REQUEST_DATA) # print("RESPONSE_DATA:", RESPONSE_DATA) # 比对结果 errorKey = CheckResult.check(responseData, eval(checkPoint)) if not errorKey: print("校验结果通过") else: print("校验失败", errorKey) else: print(response.status_code) else: print("用例被忽略执行") else: print("接口被设置忽略执行") if __name__ == '__main__': main() 结果: {'code': '00', 'userid': 25439} 校验结果通过 {'code': '00', 'userid': 25440} 校验结果通过 {'username': 'c131313', 'password': 'c131313', 'code': '02', 'email': '[email protected]'} 校验结果通过 data[k]: 50e129ba7db82b7d6671388b8fccb036 {'token': 'a2cc7a159a603e6141f6ac0d7cb78a53', 'code': '00', 'userid': 25435, 'login_time': '2019-03-19 22:30:57'} 校验结果通过
- 在interface_auto_test.py开始做把校验结果写到excel中,前提需要在action包中新建write_test_result.py文件,需要将结果内容写到表格的ResponseData、Status、ErrorInfo字段中:
# -*- coding:utf-8 -*- from config.public_data import * def write_result(wbobj, sheetObj, responseData, errorKey, rowNum): # wbobj 相当于主程序中的parseE try: # 写响应body wbobj.writeCell(sheet=sheetObj, content="%s" %responseData, rowNo=rowNum, colsNo=CASE_ResponseData) # 写校验结果状态列及错误信息列 if errorKey: wbobj.writeCell(sheet=sheetObj, content="faild", rowNo=rowNum, colsNo=CASE_Status) wbobj.writeCell(sheet=sheetObj, content="%s" % errorKey, rowNo=rowNum, colsNo=CASE_ErrorInfo) else: wbobj.writeCell(sheet=sheetObj, content="pass", rowNo=rowNum, colsNo=CASE_Status) except Exception as e: raise e
- 然后再在interface_auto_test.py,加入下划线这一行:
# ----coding:utf-8 ---- # author: Chang Jinling # time: 2019/2/20--11:47 import requests import json from utils.ParseExcel import ParaseExcel from config.public_data import * from utils.HttpClient import HttpClient from action.get_rely import GetKey from action.data_store import RelyDataStore from action.check_result import CheckResult from action.write_test_result import write_result import sys from importlib import reload reload(sys) def main(): parseE = ParaseExcel() parseE.loadWorkBook(file_path) sheetObj = parseE.getSheetByName(u"API") activeList = parseE.getColumn(sheetObj, API_Active) for idx, cell in enumerate(activeList[1:], 2): if cell.value == "y": # 需要执行的接口所在行的行对象 rowObj = parseE.getRow(sheetObj, idx) apiName = rowObj[API_APIName - 1].value requestUrl = rowObj[API_RequestUrl - 1].value requestMethod = rowObj[API_RequestMethod - 1].value paramsType = rowObj[API_paramsType - 1].value apiTestCaseFileName = rowObj[API_APITestCaseFileName - 1].value # 下一步读用例sheet表,准备执行测试用例 caseSheetObj = parseE.getSheetByName(apiTestCaseFileName) caseActiveObj = parseE.getColumn(caseSheetObj, CASE_Active) for c_idx, col in enumerate(caseActiveObj[1:], 2): if col.value == "y": # 说明此case行需要执行 caseRowObj = parseE.getRow(caseSheetObj, c_idx) # 先写这两个字段,请求参数和依赖数据 requestData = caseRowObj[CASE_RequestData -1 ].value relyData = caseRowObj[CASE_RelyData - 1].value dataStore = caseRowObj[CASE_DataStore - 1].value checkPoint = caseRowObj[CASE_CheckPoint - 1].value if relyData: # 发送接口请求之前,先做依赖数据的处理 requestData = "%s" %GetKey.get(eval(requestData), eval(relyData)) # 拼接接口请求参数,发送接口请求 httpC = HttpClient() response = httpC.request(requestMethod=requestMethod, requestUrl=requestUrl, paramsType=paramsType, requestData=requestData ) if response.status_code == 200: print(response.json()) responseData = response.json() # 这里拿出来的就是字典对象,所以不需要用eval转 # 存储依赖数据 # 存储依赖数据必须要判断一下用例表中DataStore如果不为空则需要存储 if dataStore: RelyDataStore.do(eval(dataStore), apiName, c_idx - 1, eval(requestData), responseData) # print("REQUEST_DATA:", REQUEST_DATA) # print("RESPONSE_DATA:", RESPONSE_DATA) # 比对结果 errorKey = CheckResult.check(responseData, eval(checkPoint)) print("errorKey:", errorKey) write_result(parseE, caseSheetObj, responseData, errorKey, c_idx) # if not errorKey: # print("校验结果通过") # else: # print("校验失败", errorKey) else: print(response.status_code) else: print("用例被忽略执行") else: print("接口被设置忽略执行") if __name__ == '__main__': main() 打开测试用例表格就会看到ResponseData、Status、ErrorInfo这三个字段都已经被添加了内容。
- 路径之类的不要用中文,避免执行的时候出现错误。
- 感谢光荣之路测试开发培训,这是学习笔记的整理,后期会继续补充内容,2019-03-21。