requests实现接口自动化(一)

一种冗余的设计

比如我们有注册接口:http://localhost:8099/futureloan/mvc/api/member/register , 其请求方式支持GET/POST,其参数分别为:

变量 变量名 类型 说明 可否为空
手机号 mobilephone String 新会员的手机号
密码 pwd String 6到18位(最少6位,最长18位)
注册名 regname String 相当于昵称

根据接口的字段要求可以设计如下的用例:

用例id 描述 期望结果 实际结果
register_001 正常注册 注册成功
register_002 手机号为空字符串 提示参数不能为空
register_003 手机号号段不对 提示手机号格式不正确
... ... ...

用代码实现,要写很多调用的方法,重复性高,维护麻烦

import requests

def test_login(params):
    url = "http://localhost:8099/futureloan/mvc/api/member/register"
    req = requests.request(method="get", urr=url, params=params)
    print(req.status_code)
    print(req.text)



#用例1
test_login({"mobilePhone": "13523567801", "pwd": "123"})
#用例2
test_login({"mobilePhone": "", "pwd": "123"})
#用例3
test_login({"mobilePhone": "10086123456", "pwd": "123"})
...

存放测试数据

由于接口请求的参数比较整齐划一,一个接口需要url, method, params这些参数,因此可以考虑把数据放在Excel中,使用Excel的好处是:
1.数据和代码分离,易于维护修改
2.Excel比较直观,操作方便

使用Excel的设计形式如下:

id api_name 用例说明 url method request_data expect_data
register_01 register 注册成功—无昵称 url地址 get

读取测试数据的两种方式

使用python提供的openpyxl库可以方便的读取测试数据,但是测试数据需要以什么样的方式存储,这个值得考虑。上面说过接口的形式都是比较整齐的,我们后面将这些数据传递给测试函数的时候,如果写很多调用来实现,这样工作量无疑是巨大的。怎么样才能写一次调用,实现多次参数传递,参考for循环的实现,for循环可以对列表、元组、字符串、字典等进行循环,元组限制较多不合适,字符串也不合适,剩下的列表和字典,正是我们想要的。考虑到我们数据存放在Excel中,Excel的行: 列有点类似key: value,因此内层数据可以使用字典,外层数据统一用列表包装即可

以前的方式
def test_login():
	...
	
#用例1
test_login_normal():
	test_login()
	
#用例2
test_login_phoneIsEmpty():
	test_login()

...

现在的方式
def test_login():
	...
	
	
all_case_data = [{}, {}, {} ...]

for case_data in all_case_data:
	...
	#只调用一次测试函数
	test_login()  
	...

定义好了数据的存储格式,即列表[字典]的形式,下一步就要读取Excel了,我先写出第一种方式,看看有什么优缺点

from openpyxl import load_workbook

class DoExcel:

    def __init__(self, filePath, sheetName):
        self.wb = load_workbook(filePath)
        self.sh = self.wb[sheetName]


    #读取所有的测试数据
    def read_allCaseData(self):
        all_case_data = []
        for row in range(2, self.sh.max_row + 1):
            case_data = {}
            case_data["id"] = self.sh.cell(row, 1).value
            case_data["url"] = self.sh.cell(row, 4).value
            case_data["method"] = self.sh.cell(row, 5).value
            case_data["request_data"] = self.sh.cell(row, 6).value
            case_data["expect_data"] = self.sh.cell(row, 7).value
            all_case_data.append(case_data)
        print(all_case_data)
        return all_case_data


if __name__ == '__main__':
    DoExcel(r"E:\python_workshop\python_API\TestDatas\api_info.xlsx", "case_datas").read_allCaseData()
    
    
    
#运行结果
[{'id': 'register_01', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'get', 'request_data': '{"mobilephone":"13723456712", "pwd":"test123"}', 'expect_data': '{"status":1,"code":"10001","data":null,"msg":"注册成功"}'}, {'id': 'register_02', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'get', 'request_data': '{"mobilephone":"13723456713", "pwd":"test123", "regname":"xiaozhai"}', 'expect_data': '{"status":1,"code":"10001","data":null,"msg":"注册成功"}'}, {'id': 'register_03', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'post', 'request_data': '{"mobilephone":"", "pwd":"test123", "regname":"xiaozhai"}', 'expect_data': '{"status":0,"code":"20103","data":null,"msg":"手机号不能为空"}'}, {'id': 'register_04', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'get', 'request_data': '{"mobilephone":"13723456715", "pwd":"", "regname":"xiaozhai"}', 'expect_data': '{"status":0,"code":"20103","data":null,"msg":"密码不能为空"}'}, {'id': 'register_05', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'post', 'request_data': '{"mobilephone":"1372345671", "pwd":"test123", "regname":"xiaozhai"}', 'expect_data': '{"status":0,"code":"20109","data":null,"msg":"手机号码格式不正确"}'}, {'id': 'register_06', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'get', 'request_data': '{"mobilephone":"137234567156", "pwd":"test123", "regname":"xiaozhai"}', 'expect_data': '{"status":0,"code":"20109","data":null,"msg":"手机号码格式不正确"}'}, {'id': 'register_07', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'get', 'request_data': '{"mobilephone":"13723456715", "pwd":"test1", "regname":"xiaozhai"}', 'expect_data': '{"status":0,"code":"20108","data":null,"msg":"密码长度必须为6~18"}'}, {'id': 'register_08', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'get', 'request_data': '{"mobilephone":"13723456715", "pwd":"test123456789012345", "regname":"xiaozhai"}', 'expect_data': '{"status":0,"code":"20108","data":null,"msg":"密码长度必须为6~18"}'}, {'id': 'register_09', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'get', 'request_data': '{"mobilephone":"13723456713", "pwd":"test123"}', 'expect_data': '{"status":0,"code":"20110","data":null,"msg":"手机号码已被注册"}'}]

这种方式最大的好处是代码比较清晰,我们看到for循环从第2行开始循环读取测试数据,然后把列名作为key,指定行号和列号的单元格的值作为value,存储到字典中,每行循环完了,把该行对应的字典存储到列表中,下一行开始的时候,重新生成一个空字典。但是缺点也很明显,主要集中在这里:

 case_data["id"] = self.sh.cell(row, 1).value
 case_data["url"] = self.sh.cell(row, 4).value
 case_data["method"] = self.sh.cell(row, 5).value
 case_data["request_data"] = self.sh.cell(row, 6).value
 case_data["expect_data"] = self.sh.cell(row, 7).value

把列号写死,万一要在id和url之间加一列,url后面的列号都得变了,这样维护起来比较麻烦。刚好之前学过zip可以把两个可迭代对象打包成一个对象,然后使用dict()将其变成字典,再追加到列表中。第一个对象是第一行的列表组成的列表,第二个对象是每一行的单元格的值组成的列表(这里是全部读取)
有了这样的思路,第二种方式实现起来也比较简单:

from openpyxl import load_workbook

class DoExcel:

    def __init__(self, filePath, sheetName):
        self.wb = load_workbook(filePath)
        self.sh = self.wb[sheetName]




    #读取所有测试数据
    def read_allCaseData(self):
        max_row = self.sh.max_row
        max_column = self.sh.max_column
        all_case_data = []
        title = [self.sh.cell(1, column).value for column in range(1, max_column + 1)]
        for row in range(2, max_row + 1):
            case_data = [self.sh.cell(row, column).value for column in range(1, self.sh.max_column+1)]
            all_case_data.append(dict(zip(title, case_data)))
        print(all_case_data)
        return all_case_data
        


if __name__ == '__main__':
    DoExcel(r"E:\python_workshop\python_API\TestDatas\api_info.xlsx", "case_datas").read_allCaseData()
    
    
#运行结果
D:\program\Python37\python.exe E:/python_workshop/python_API/Common/DoExcel.py
[{'id': 'register_01', 'api_name': 'register', '用例说明': '注册成功—无昵称', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'get', 'request_data': '{"mobilephone":"13723456712", "pwd":"test123"}', 'expect_data': '{"status":1,"code":"10001","data":null,"msg":"注册成功"}'}, {'id': 'register_02', 'api_name': 'register', '用例说明': '注册成功—有昵称', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'get', 'request_data': '{"mobilephone":"13723456713", "pwd":"test123", "regname":"xiaozhai"}', 'expect_data': '{"status":1,"code":"10001","data":null,"msg":"注册成功"}'}, {'id': 'register_03', 'api_name': 'register', '用例说明': '手机号为空', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'post', 'request_data': '{"mobilephone":"", "pwd":"test123", "regname":"xiaozhai"}', 'expect_data': '{"status":0,"code":"20103","data":null,"msg":"手机号不能为空"}'}, {'id': 'register_04', 'api_name': 'register', '用例说明': '密码为空', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'get', 'request_data': '{"mobilephone":"13723456715", "pwd":"", "regname":"xiaozhai"}', 'expect_data': '{"status":0,"code":"20103","data":null,"msg":"密码不能为空"}'}, {'id': 'register_05', 'api_name': 'register', '用例说明': '手机号长度少于11位', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'post', 'request_data': '{"mobilephone":"1372345671", "pwd":"test123", "regname":"xiaozhai"}', 'expect_data': '{"status":0,"code":"20109","data":null,"msg":"手机号码格式不正确"}'}, {'id': 'register_06', 'api_name': 'register', '用例说明': '手机号长度多于11位', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'get', 'request_data': '{"mobilephone":"137234567156", "pwd":"test123", "regname":"xiaozhai"}', 'expect_data': '{"status":0,"code":"20109","data":null,"msg":"手机号码格式不正确"}'}, {'id': 'register_07', 'api_name': 'register', '用例说明': '密码长度少于8位', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'get', 'request_data': '{"mobilephone":"13723456715", "pwd":"test1", "regname":"xiaozhai"}', 'expect_data': '{"status":0,"code":"20108","data":null,"msg":"密码长度必须为6~18"}'}, {'id': 'register_08', 'api_name': 'register', '用例说明': '密码长度多于18位', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'get', 'request_data': '{"mobilephone":"13723456715", "pwd":"test123456789012345", "regname":"xiaozhai"}', 'expect_data': '{"status":0,"code":"20108","data":null,"msg":"密码长度必须为6~18"}'}, {'id': 'register_09', 'api_name': 'register', '用例说明': '重复注册', 'url': 'http://localhost:8099/futureloan/mvc/api/member/register', 'method': 'get', 'request_data': '{"mobilephone":"13723456713", "pwd":"test123"}', 'expect_data': '{"status":0,"code":"20110","data":null,"msg":"手机号码已被注册"}'}]

Process finished with exit code 0

这种方式有效的避免了第一种方式的缺点,可以任意向Excel中增加列、删除列,读取的将是全部的测试数据

分层设计思想

我们定义了Excel,读取Excel的类,后面还要定义接口请求类、日志类等等,如果将这些东西全部放在一个目录下,看起来杂乱无章。实现数据和代码分离的功能就是为了分层,所以我们的目录最好体现分层思想。新的目录结构设计:

	Common(公共目录)
		--> 处理Excel.py
		--> 封装日志.py
		--> 发送请求.py
		--> 读取配置.py
		...
	Conf(配置目录)
		--> 数据库配置.cfg
		--> url配置.cfg
		...
	TestCases(测试方法目录)
		--> 测试发送请求.py
	TestDatas(测试数据目录)
		--> 测试数据.xlsx
	Logs(日志目录)
		--> 输出日志.log
		...
	HTMLReports(测试报告目录)
		--> 测试报告.log
		...
	main.py(框架入口方法)

封装请求方法的两种方式

先来说第一种,由于request方法会根据请求method区分请求数据的参数是params还是data,因此需要对method做判断

import requests


def send_request(method, url, request_data):
    if method.lower() == "get":
        result = requests.request(method, url, params=request_data).text
    elif method.lower() == "post":
        result = requests.request(method, url, data=request_data).text
    print(result)
    return result



if __name__ == '__main__':
    method = "post"
    url = "http://localhost:8099/futureloan/mvc/api/member/register"
    request_data = {"mobilephone":"13623456952", "pwd":"test123"}
    send_request(method, url, request_data)
    
    
#运行结果
D:\program\Python37\python.exe E:/python_workshop/python_API/Common/MyRequest.py
{"status":1,"code":"10001","data":null,"msg":"注册成功"}

Process finished with exit code 0

这种方法缺点是扩展性差,如果我要请求方式需要加上cookie,或者header,需要在方法中加上对应的字段

def send_request(method, url, request_data, cookie=None, header=None):
    if method.lower() == "get":
        result = requests.request(method, url, params=request_data).text
    elif method.lower() == "post":
        result = requests.request(method, url, data=request_data).text
    print(result)
    return result

也就是说,不同接口需要的请求信息是变化的,这就要求我们的代码要做到足够的灵活。采用关键字参数**kwargs可以解决这个问题,先来看一组demo

只有请求参数的情况:

#只有一个request_data
import requests


def send_request(method, url, **kwargs):
    print(kwargs)



if __name__ == '__main__':
    method = "post"
    url = "http://localhost:8099/futureloan/mvc/api/member/register"
    request_data = {"mobilephone":"13623456952", "pwd":"test123"}
    send_request(method, url, request_data=request_data)
    
    
#运行结果
D:\program\Python37\python.exe E:/python_workshop/python_API/Common/MyRequest.py
{'request_data': {'mobilephone': '13623456952', 'pwd': 'test123'}}

Process finished with exit code 0

除了请求参数还有cookie的情况:

#除了request_data,还有cookie
import requests


def send_request(method, url, **kwargs):
    print(kwargs)



if __name__ == '__main__':
    method = "post"
    url = "http://localhost:8099/futureloan/mvc/api/member/register"
    request_data = {"mobilephone":"13623456952", "pwd":"test123"}
    send_request(method, url, request_data=request_data, cookie="JSLKAJ27643538202")
    

#运行结果
D:\program\Python37\python.exe E:/python_workshop/python_API/Common/MyRequest.py
{'request_data': {'mobilephone': '13623456952', 'pwd': 'test123'}, 'cookie': 'JSLKAJ27643538202'}

Process finished with exit code 0

看到这里就明白**kwargs的意义了,关键字可以有效的包装封装的请求方法始终不变,只要改变不同的传参就可以实现不同的定制,接口需要什么传什么,只要遵循key = value的形式就行

按照关键字参数的思路,我们来重新设计下封装请求方法。先来看这种写法对不对

import requests


def send_request(method, url, **kwargs):
    print(type(kwargs))
    if method.lower() == "get":
        result = requests.request(method, url, params=kwargs).text
    elif method.lower() == "post":
        result = requests.request(method, url, data=kwargs).text
    print(result)
    return result



if __name__ == '__main__':
    method = "post"
    url = "http://localhost:8099/futureloan/mvc/api/member/register"
    request_data = {"mobilephone":"13623456962", "pwd":"test123"}
    send_request(method, url, request_data=request_data)

第一眼感觉没问题,但是怎么运行,它都只会得到一种结果

{"status":0,"code":"20103","data":null,"msg":"手机号不能为空"}

debug一下,就发现问题之所在了, data=kwargs中的kwargs = {'request_data': {'mobilephone': '13623456962', 'pwd': 'test123'}},而将data = kwargs传递给 requests.request的**kwargs,会重新组合为data = {'request_data': {'mobilephone': '13623456962', 'pwd': 'test123'}},也就是请求参数多了一个request_data的key,所以接口就找不到手机号了

image-20200323124704961

这相当于 key = value传递,我们在此基础上使得 key = { key: value },唯一的办法是少定义一个 key = value,那么就要把请求方法中控制get还是post的判断去掉,让params=request_data或者data=request_data再传递参数时判断即可。这样整体的代码也比较简洁了

import requests


def send_request(method, url, **kwargs):
    result = requests.request(method, url, **kwargs).text
    print(result)
    return result



if __name__ == '__main__':
    method = "post"
    url = "http://localhost:8099/futureloan/mvc/api/member/register"
    request_data = {"mobilephone":"13623456960", "pwd":"test123"}
    send_request(method, url, data=request_data)
 

#运行结果
D:\program\Python37\python.exe E:/python_workshop/python_API/Common/MyRequest.py
{"status":1,"code":"10001","data":null,"msg":"注册成功"}

Process finished with exit code 0
posted @ 2020-04-23 17:14  cnhkzyy  阅读(662)  评论(0编辑  收藏  举报