第5章
CHAPTER 5
Requests初级使用
本章开始讲解基于Python实现的HTTP接口测试。主要依赖第三方库Requests实现。现有接口测试工具可以理解为一种接口测试框架的可视化实现,使用Python实现接口测试及测试用例的维护,可以使测试工作更为灵活,通过实现自定义测试框架会更贴近具体接口测试项目的需求和管理。本章作为Requests基本接口测试的讲解,为后面测试框架的实现打下基础。
5.1Requests介绍
Requests是用Python语言编写的,基于urllib,采用Apache License 2.0开源协议的HTTP库。Requests是一个很实用的Python HTTP客户端库,当编写爬虫和测试服务器响应数据时经常会用到,是基础Python实现HTTP接口测试必不可少的第三方工具库。
Requests库有7种常用方法,以request()为基础构造方法,在此基础上实现6种常见请求方法,见表51。
表51Requests常见请求方法
序号方 法 名 称描述
1request()构造方法,构造对象中包括后面的6种基本HTTP请求方法
2get()获取HTML页面信息的主要方法,返回值通常是HTML页面
3head()获取HTML页面Header的主要方法,返回值通常是指定请求Headers值
4post()提交POST请求方法,可以携带ContentType指定的主体内容
5put()提交PUT请求方法,与POST请求方法类似,可以指定请求位置
6patch()提交PATCH请求方法,可以设置当前用户存储参数值
7delete()提交DELETE请求方法,可以删除指定服务器资源
5.1.1GET方法的使用
GET方法是Requests库中使用频率最高的一种方法,以百度搜索首页为例,使用PyCharm编写接口测试脚本实现对百度搜索首页的访问,请求代码如下:
//chapter05/api_get.py
#导入第三方包requests
import requests
#访问百度首页
url = 'https://www.baidu.com/'
#发送GET请求,将返回结果存入变量response
response = requests.get(url)
#输出结果,内容中包含中文,需UTF-8转码
print(response.content.decode('UTF-8'))
返回结果如图51所示。
图51GET请求返回结果
5.1.2POST方法的使用
当访问接口携带大量参数且一些敏感信息不方便直接跟随URL展现在浏览器网址栏时,可以使用POST方法来完成请求。以孔夫子旧书网用户登录接口为例,请求代码如下:
//chapter05/api_post.py
import requests
url = 'https://login.kongfz.com/Pc/Login/account'
#将请求Headers以字典的方式存入变量headers
headers = {
"Connection":"keep-alive",
"Content-Length":"150",
"sec-ch-ua":"\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"100\", \"Google Chrome\";v=\"100\"",
"sec-ch-ua-mobile":"?0",
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36",
"X-Tingyun-Id":"OHEPtRD8z8s;r=325222859",
"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8",
"Accept":"application/json, text/javascript, */*; q=0.01",
"X-Requested-With":"XMLHttpRequest",
"Sec-Fetch-Site":"same-origin",
"Sec-Fetch-Mode":"cors",
"Sec-Fetch-Dest":"empty",
"Host":"login.kongfz.com"
}
#将Body以字典的方式存入变量data
data = {
"loginName":"Thinkerbang", #此处为用户名参数,读者需要自行替换
"loginPass":"123456",
"captchaCode":"",
"autoLogin":"0",
"newUsername":"",
"returnUrl":"https://user.kongfz.com/index.html",
"captchaId":""
}
#发送POST请求,将返回结果存入变量response
response = requests.post(url=url,headers=headers,data=data)
#由于返回值类型是JSON字符串,因此使用json()方法进行输出
print(response.json())
返回结果如图52所示。
图52POST请求返回结果
5.1.3PUT方法的使用
PUT方法与POST方法的作用类似,它们都可以完成Body传参操作。区别在于PUT方法与GET、DELETE等方法类似,这些方法都是幂等的,而POST方法不是。一个简单的区分方式是可以看接口请求是否会有重复信息,例如一个发布信息接口,同样参数情况下提交接口请求,使用PUT方法提交时,无论提交几次都只会发布一次信息,PUT方法每次都会将之前同参信息覆盖掉。使用POST方法提交时,由于它的不幂等性,同参多次提交会出现重复信息。在实际使用过程中,POST方法的使用频率很高,这是因为二者使用上的差异在实际开发过程中可以通过其他方法解决。
以httpbin网站PUT接口为例,请求代码如下:
//chapter05/api_put.py
import requests
url = "http://httpbin.org/put"
data = {
"name":"Thinkerbang",
"age":"2016"
}
response = requests.put(url = url,data=data)
print(response.json())
返回结果如图53所示。
图53PUT请求返回结果
5.1.4HEAD方法的使用
HEAD请求可以看作没有返回参数的GET请求,这两种请求方法的本质是相同的。当浏览器向服务器发送页面访问请求时,使用GET方法发送接口请求,服务器会返回相应的请求资源; 当浏览器向服务器确认当前访问页面是否有动态更新时,使用HEAD方法发送接口请求,服务器会检查当前页面的更新状态,将结果返回浏览器,此时的返回并不包含当前页面资源数据。
HEAD请求通常应用在检查资源有效性、超链接的可访问性及页面内容最近是否有修改等场合。HEAD请求返回的协议状态码与GET请求相同。
以百度网站首页HEAD接口为例,请求代码如下:
//chapter05/api_head.py
#导入第三方包requests
import requests
#访问百度首页
url = 'https://www.baidu.com/'
#发送GET请求,将返回结果存入变量response
response = requests.head(url)
#实际输出结果为空
print("返回Body:", response.content.decode('UTF-8'))
#输出结果为响应Headers
print("返回Headers:",response.headers)
返回结果如图54所示。
图54HEAD请求返回结果
5.1.5PATCH方法的使用
在HTTP协议中,PATCH方法用于对资源进行部分修改。与POST方法类似,PATCH方法是非幂等的,这就意味着连续多个相同请求会产生不同的效果。在响应首部Allow或者AccessControlAllowMethods的方法列表中需要添加PATCH方法,这样基于PATCH方法的接口请求才会生效。在本节孔夫子旧书网首页接口请求中,响应Headers中的ControlAllowMethods字段明确支持GET、POST、OPTIONS共3种方法进行请求,如图55所示。
图55ControlAllowMethods字段示例
PATCH方法与PUT方法类似,它们都可以直接修改服务器端的资源内容,是作为HTTP请求的补丁方法存在的。当对已有资源进行操作时,PATCH方法常用于资源部分内容需要更新时使用,而PUT方法常用于更新目标资源完整内容时使用。当资源不存在时,PATCH方法可以创建一个新的资源,而PUT方法无法完成这样的操作。有兴趣的读者可以参照RFC5789查看详细释义。
以httpbin网站PATCH方法接口为例,请求代码如下:
//chapter05/api_head.py
#导入第三方包requests
import requests
#访问httpbin页面中的patch函数接口
url = 'http://httpbin.org/patch'
#发送patch请求,将返回结果存入变量response
response = requests.patch(url)
#输出结果
print(response.json())
返回结果如图56所示。
图56PATCH请求返回结果
5.2基于GET方法的接口测试
GET方法在实际接口请求时多用于静态URL资源请求,例如页面查看请求。当接口请求需要传参以获取动态展示页面时,也可使用GET方法来完成,例如关键字查询请求。GET请求也可以传输实体的主体,但一般不用GET方法进行传输。
5.2.1GET方法参数解析
GET方法请求以获取资源和查询资源为主要使用场景,因此在请求时传递参数并不复杂。在api.py文件中GET方法的构成如图57所示。
图57GET方法的构成
GET方法使用参数源于request方法,常用参数如下。
(1) url参数: Request对象为必填参数,用于指明接口请求目标地址。
(2) params参数: 此参数为可选参数,当GET方法发送查询请求时用来传递查询参数使用,通常为字节类型或字典类型。
(3) **kwargs参数: 此参数为可选参数,可以传递url和param参数之外的其他类型参数,例如cookies、headers、timeout等接口请求中所需的参数。具体所支持的参数可在api.py文件下的request方法注释下查看。
5.2.2基于GET方法的请求类型
1. 通过URL直接传参
当接口请求需要传递的参数较少且没有加密需求时,可以将参数直接通过URL进行传递。例如,通过中图网首页进行关键词“华软盛科技有限公司”搜索,请求代码如下:
//chapter05/url_get.py
#导入第三方包requests、urllib.parse、re
import requests
import urllib.parse
import re
#查询文本
text = '华软盛科技有限公司'
#将汉字文本转换为unicode编码
urltext = text.encode('unicode-escape').decode()
#输出源文本
print(text)
#输出转换后的文本
print(urltext)
#使用中图网进行关键字查询
url = 'http://www.bookschina.com/book_find2/?stp='+urltext+'&sCate=0'
#发送GET请求,将返回结果存入变量response
response = requests.get(url=url)
#输出结果,title标题中包含转码后的查询关键字
#使用正则表达式提取结果中的标题
pat = re.compile('
'+'(.*?)'+'',re.S)
result = pat.findall(response.text)
#由于提取结果中包含汉字乱码,所以需要对结果进行解码处理
unurltext = urllib.parse.unquote(result[0])
print('URL解码结果:'+unurltext)
#对提取结果查询关键字进行切片处理
sptext = unurltext.split(':')
#对切片后列表中的关键字unicode码进行解码处理
detext = sptext[1].encode().decode("unicode_escape")
#输出最终结果
print(detext)
返回结果如图58所示。
图58GET查询请求返回结果
2. 通过字典方式传参
当接口请求需要传递的参数较多且将参数附在URL中进行传递时,参数不够直观。可以将参数以字典的方式进行存储,当发送GET方法进行接口请求时,使用params参数进行传递。修改文件url_get.py中的代码,在中图网首页对关键词“全栈UI自动化测试”进行搜索,请求代码如下:
//chapter05/url_get_param.py
#导入第三方包requests、urllib.parse、re
import requests
import urllib.parse
import re
#查询文本
text = '全栈UI自动化测试实战'
#将汉字文本转换为unicode编码
urltext = text.encode('unicode-escape').decode()
#将传递参数存入字典
dict = {
'stp':urltext,
'sCate':'0'
}
#使用中图网进行关键字查询
url = 'http://www.bookschina.com/book_find2/'
#发送GET请求,通过params进行传参,将返回结果存入变量response
response = requests.get(url=url,params=dict)
#输出结果,title标题中包含转码后的查询关键字
#使用正则表达式提取结果中的标题
pat = re.compile(''+'(.*?)'+'',re.S)
result = pat.findall(response.text)
#由于提取结果中包含汉字乱码,所以需要对结果进行解码处理
unurltext = urllib.parse.unquote(result[0])
print('URL解码结果:'+unurltext)
#对提取结果查询关键字进行切片处理
sptext = unurltext.split(':')
#对切片后列表中的关键字unicode码进行解码处理
detext = sptext[1].encode().decode("unicode_escape")
#输出最终结果
print(detext)
返回结果如图59所示。
图59通过params传参请求返回结果
3. 其他常见传递参数
GET方法接口请求还可以传递URL、params参数之外的其他参数,例如Headers、timeout等参数。
网站基于访问安全考虑,通常会在非登录验证接口加入访问验证,以避免工具对网站进行恶意访问。通过浏览器访问网站时,浏览器会在访问接口中自动加入客户端信息。当服务器端未开启对访问客户端信息验证时,接口请求脚本仅需URL即可完成访问; 当服务器端开启了客户端信息验证时,在接口请求脚本Headers中需要加入UserAgent参数。有些网站也会在Headers中加入自定义参数,当使用脚本进行测试访问时,需带上自定义Headers参数。
访问中图网首页,发送GET方法接口请求需要带上客户端信息,请求代码如下:
//chapter05/url_get_header.py
#导入第三方包requests
import requests
#定义Headers中需要传递的参数
Header = {
'Host': 'www.bookschina.com',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Referer': 'http://www.bookschina.com/RegUser/login.aspx',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.9'
}
#访问中图网首页
url = 'http://www.bookschina.com/'
#发送GET请求,加入Headers参数,将返回结果存入变量response
response = requests.get(url=url,headers=Header)
#输出结果
print(response.text)
返回结果如图510所示。
图510中图网首页请求返回结果
5.2.3常见Requests响应参数
接口请求返回Requests响应对象,对象拥有部分属性及方法,用来满足请求目标。一些响应对象的常用属性及方法见表52。
表52Requests响应对象的常用属性及方法
参数数 据 类 型作用
apparent_encodingstr(字符串型)根据返回内容解析出来的字符编码,然后返回编码名称
close()method关闭与服务器的连接,仅对建立长链接的对象起作用
CookiesRequestsCookieJar获取返回Cookie值
contentbytes(字节型)以bytes型返回原始响应体
elapseddatetime.timedelta返回从发送请求到接收到响应所花费的时长
encodingstr(字符串型)返回用于解码 response.content 的编码方式,默认按照“ISO88591”进行解码
historylist(列表)访问历史记录(重定向记录)
headersdict(字典)返回HTTP响应头参数
is_permanent_redirectbool(布尔型)如果响应是永久重定向的URL,则返回值为True,否则返回值为False
is_redirectbool(布尔型)如果响应已重定向,则返回值为True,否则返回值为False
iter_content()method以字节方式对响应数据进行迭代,当可以进行解码时返回
iter_lines()method以行方式对响应数据进行迭代
linksdict(字典)返回标题链接
json()method返回转换成JSON格式的数据
next返回重定向链中下一个请求的 PreparedRequest 对象
okbool(布尔型)判断协议状态码是否小于400,如果小于400,则返回值为True,如果不小于400,则返回值为False
raise_for_status()method抛出状态异常错误
rawObject(对象)返回请求后得到此响应对象的原始响应体对象,urllib的HTTPResponse对象,通常使用 response.raw.read()进行读取
reasonstr(字符串型)返回HTTP响应状态码相对应的描述文本,例如OK、Not Found等
requestrequests.models.
PreparedRequest返回对应的请求对象
status_codeint(整型)返回HTTP请求常响应协议状态码,例如200、302、404、500等
textstr(字符串型)返回经过编码后的文本内容
urlstr(字符串型)返回请求的真实URL网址
Requests响应常用属性及方法的请求代码如下:
//chapter05/url_get_response.py
#导入第三方包requests
import requests
#访问百度首页
url = 'https://www.baidu.com/'
#发送GET请求,将返回结果存入变量response
response = requests.get(url)
#输出结果,此处仅列举几个常用属性,更多的响应属性及方法可参见表5-1进行练习
print('返回接口请求中的URL:',response.url)
print('返回协议状态码:',response.status_code)
print('返回响应对象编码:',response.apparent_encoding)
print('返回Cookie对象:',response.cookies)
print('返回响应Headers:',response.headers)
返回结果如图511所示。
图511Requests响应参数示例结果
5.3基于POST方法的接口测试
HTTP协议规定当POST请求提交数据时需要放在消息主体(Body)中发送。常见的消息主体数据格式有4种,在发送请求时,需要在Headers中的 ContentType 字段中说明消息主体所采用的编码方式,服务器端接收到请求后会采用相应的方式进行解码,以确保数据传输的正确性。
POST请求将所有数据放在消息主体中传输,无法在URL中看到传输数据,与GET方法传输参数相比,具有一定的数据隐藏效果,例如B/S架构软件中用户登录请求通常会使用POST方法进行登录账号和密码数据传输。从应用层数据传输角度看,这仍然是明文传输。当POST方法传输的参数有敏感信息时,需要将参数进行加密处理,以密文方式进行传输。
5.3.1POST方法参数解析
POST方法请求以传输数据为主要使用场景,因此在请求时传递参数会根据Headers中的ContentType字段确定消息主体的编码方式。在api.py文件中POST方法的构成如图512所示。
图512POST请求参数
POST方法使用的参数同样源于request方法,感兴趣的读者可以通过查看api.py文档中Request方法的传递参数集进行进一步了解。POST方法常用的传递参数如下。
(1) url参数: Request对象的必填参数,用于指明接口请求目标地址。
(2) data参数: 此参数为二选一参数,当POST方法发送非JSON编码主体参数时此项为必填参数。当请求中有大量数据需要传递时使用。
(3) json参数: 此参数为二选一参数,当POST方法发送JSON编码主体参数时此项为必填参数,请求中有大量JSON数据需要传递时使用。
(4) **kwargs参数: 此参数为可选参数,可以传递url和data/json参数之外的其他类型参数,例如cookies、headers、timeout等接口请求中所需的参数。
POST请求的4种消息主体见表53。
表53POST请求的4种消息主体
ContentType参 数 规 则示例
multipart/formdata数据以键值对形式存在,使用分隔符boundary处理成一条消息
既可以上传文件,也可以上传参数
boundary分隔线
boundary
ContentDisposition: formdata;
name="file";
filename="test.txt"
boundary分隔线
boundary
xwwwformurlencoded
数据以键值对形式存在,是默认的MIME内容编码类型
只能上传键值对,不能用于文件上传,参数之间以&符间隔
username=Thinkerbang&
password=1234
(raw)
application/text
application/json
application/xml
application/html
可以上传任意格式的文本,常见格式有text、json、xml、html
(application/json示例)
{
username: 'Thinkerbang',
password: '1234'
}
application/octetstream
只可以上传二进制数据,通常用来上传文件
没有键值对,一次只能上传一个文件
boundary分隔线
boundary
ContentDisposition: formdata;
name="file";
filename="test.gif"
ContentType:
application/octetstream
GIF89…二进制数据…
boundary分隔线
boundary
5.3.2消息主体: Data类型实例
B/S架构的软件在访问时,登录请求通常使用POST方法来完成。在软件操作过程中,涉及新增、修改等操作,通常使用POST方法。本节实例使用孔夫子旧书网进行演示,在5.1.2节api_post.py代码实现登录的基础上,对“个人中心”信息进行更新操作,请求代码如下:
//chapter05/api_post_data.py
import requests
#------系统登录模块start-------
url = 'https://login.kongfz.com/Pc/Login/account'
#将请求Headers以字典的方式存入变量headers
headers = {
"Connection":"keep-alive",
"Content-Length":"150",
"sec-ch-ua":"\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"100\", \"Google Chrome\";v=\"100\"",
"sec-ch-ua-mobile":"?0",
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36",
"X-Tingyun-Id":"OHEPtRD8z8s;r=325222859",
"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8",
"Accept":"application/json, text/javascript, */*; q=0.01",
"X-Requested-With":"XMLHttpRequest",
"Sec-Fetch-Site":"same-origin",
"Sec-Fetch-Mode":"cors",
"Sec-Fetch-Dest":"empty",
"Host":"login.kongfz.com"
}
#将Body以字典的方式存入变量data
data = {
"loginName":"Thinkerbang", #此处为用户名参数,读者需要自行替换
"loginPass":"123456",
"captchaCode":"",
"autoLogin":"0",
"newUsername":"",
"returnUrl":"https://user.kongfz.com/index.html",
"captchaId":""
}
#发送POST请求,将返回结果存入变量response
response = requests.post(url=url,headers=headers,data=data)
#返回值类型是JSON字符串,因此使用json()方法进行输出
print(response.json())
#------系统登录模块end-------
#------个人信息修订模块start-------
url_info = 'https://user.kongfz.com/User/perPro/update/'
#将请求Headers以字典的方式存入变量header_info
header_info = {
"Host": "user.kongfz.com",
"Connection": "keep-alive",
"Content-Length": "86",
"sec-ch-ua": "\"Not_A Brand\";v=\"99\", \"Google Chrome\";v=\"109\", \"Chromium\";v=\"109\"",
"Accept": "text/plain, */*; q=0.01",
"Content-Type": "application/x-www-form-urlencoded",
"X-Requested-With": "XMLHttpRequest",
"sec-ch-ua-mobile": "?0",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
"sec-ch-ua-platform": "\"Windows\"",
"Origin": "https://user.kongfz.com",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
"Referer": "https://user.kongfz.com/person/person_info.html",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9"
}
#将Body以字典的方式存入变量data_info
data_info = {
'pic':'8284%2F3108284.jpg',
'sex':'man',
'qqNum':'359407130',
'birthday':'',
'area':'',
'sign':'Thinkerbang2', #修改个人信息中的"个性签名"内容
'intro':''
}
#发送POST请求,将返回结果存入变量response_info,由于有登录操作,所以需要将登录后的
#Cookies传入新的请求
response_info = requests.post(url=url_info,data=data_info,headers=header_info,Cookies=response.cookies)
#返回值类型是JSON字符串,也可以使用text属性进行文本输出
print(response_info.text)
#------个人信息修订模块end-------
返回结果如图513所示。
图513修改个人信息请求返回结果
5.3.3消息主体: JSON类型实例
POST请求通过信息主体传递参数的第2种方法是使用JSON格式。当主体数据为字典格式时,使用Data和JSON参数都可以完成传递。使用Data参数时会将主体数据转换为JSON格式后进行传递,请求代码如下:
//chapter05//api_post_json.py
import requests
import json
url = 'http://httpbin.org/post'
#将请求Headers以字典的方式存入变量headers
#Content-Type属性指定application/json类型
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36",
"Content-Type":"application/json; charset=UTF-8",
"Accept":"application/json, text/javascript, */*; q=0.01",
}
#将Body以字典的方式存入变量data
data = {
"username":"Thinkerbang",
"password":"123456"
}
#将字典格式参数转换成JSON格式
json_param = json.dumps(data)
#发送POST请求,使用data进行参数传递
response = requests.post(url=url,headers=headers,data=json_param)
#发送POST请求,使用JSON进行参数传递,json参数自动将字典类型转换成JSON格式
response2 = requests.post(url=url,headers=headers,json=data)
#由于返回值类型是JSON字符串,因此使用json()方法进行输出
print(response.json())
print(response2.json())
返回结果如图514所示。
图514传递JSON参数请求返回结果
在api_post_json.py文件中引入了JSON数据处理包,当在接口请求中涉及JSON数据时用来对数据进行预处理,常用的处理方法见表54。
表54常用JSON数据处理方法
方 法 名 称作用示例
json.dumps()
用于将字典类型的数据转换成字符串类型import json
data = {'user':'Tom','sn':'1234' }
jsObj = json.dumps(data)
son.dumps()参数:
r. json(): JSON格式数据转换;
indent=True: JSON格式数据序列化;
ensure_ascii=False: JSON格式数据中文处理
import json
json.dumps(r.json(),
indent=True,
ensure_ascii=False)
json.dump()用于将字典类型的数据转换成字符串类型,并写入JSON文件中
import json
data = {'user':'Tom','sn':'1234' }
filename = 'data.json'
json.dump(data, open(filename, "w"))
json.loads()用于将字符串类型的数据转换成字典类型
import json
data= {'user':'Tom','sn':'1234' }
jsDumps = json.dumps(data)
jsLoads = json.loads(jsDumps)
json.load()用于从JSON文件中读取数据,读取数据为字符串类型
import json
filename = ('data.json')
jsObj = json.load(open(filename))
5.3.4消息主体: XML类型实例
XML扩展标记语言属于质量级数据交互格式。在JSON数据格式出现之前,XML由于格式统一、符合数据传输标准曾被广泛使用,但是当使用XML格式做数据传输时其缺点也比较明显,例如文本格式复杂、体量大、占带宽,以及需要使用大量代码进行解析等。作为POST方法信息主体格式,XML至今仍在部分项目接口中使用。请求代码如下:
//chapter05//api_post_xml.py
import requests
url = 'http://www.testingedu.com.cn:8081/inter/SOAP?wsdl'
#将请求Headers以字典的方式存入变量headers
#Content-Type属性指定text/xml类型
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36",
"Content-Type":"text/xml; charset=UTF-8",
"token":"33bd50b3eb55439f89e328693fc02997",
"cookie":"userid=12955; token=33bd50b3eb55439f89e328693fc02997; type=SOAP",
}
#将Body以字符串的方式存入变量data
data = "Will123456"
#发送POST请求,使用data进行参数传递
response = requests.post(url=url,headers=headers,data=data)
#输出响应结果
print(response.text)
print("返回协议状态码:",response.status_code)
返回结果如图515所示。
图515传递XML参数请求返回结果
5.3.5消息主体: File类型实例
POST方法可用于消息主体参数传递,File类型是一种比较特殊的存在。文件传递方式与文件类型、文件大小有关。本节使用《全栈UI自动化测试实战》一书7.3.1节文件上传实例进行演示。网址为http://sahitest.com/demo/php/fileUpload.php,请求代码如下:
//chapter05//api_post_file.py
import requests
url = 'http://sahitest.com/demo/php/fileUpload.php'
#将请求Headers以字典的方式存入变量headers
#将Content-Type属性注释掉
headers = {
'Host': 'sahitest.com',
'Connection': 'keep-alive',
'Content-Length': '397',
'Cache-Control': 'max-age=0',
'Upgrade-Insecure-Requests': '1',
'Origin': 'http://sahitest.com',
#'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundarylTdGTedZL9dWTj6q',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Referer': 'http://sahitest.com/demo/php/fileUpload.htm',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.9'
}
#将抓取到的消息主体转换成files与data参数
files={
"file": open("test.txt", "rb"),
"Content-Type": "text/plain",
"Content-Disposition": "form-data",
"filename": "test.txt"
}
data={
"multi": "false",
"submit":"Submit Single"
}
#发送POST请求,使用data、files分别进行参数传递
response = requests.post(url=url,headers=headers,data=data,files=files)
#输入响应结果
print(response.text)
返回结果如图516所示。
图516上传文件请求返回结果
示例代码中涉及抓取上传消息主体参数的处理,此处进行数据转换说明。
第1步,在浏览器中打开网址http://sahitest.com/demo/php/fileUpload.php,进行文件上传操作,使用Fiddler同步抓取接口信息,请求消息主体如图517所示。
图517上传文件抓包结果
第2步,Request请求区被切换为WebForms选项卡,以表单方式查看消息主体传递参数,如图518所示。
图518消息主体参数以表单显示结果
第3步,第1项中包含的4个参数是files参数的组成要素,将参数转换为字典格式,其中file子参数主体使用open()方法引入待传文件。第2、第3项中的参数是data参数的组成要素,将参数转换为字典格式,转换结果见api_post_file.py。
5.4接口测试常用方法
5.4.1Cookies的传递
5.3.2节中的api_post_data.py文件中的代码修订了个人信息接口,需要用户登录将返回的Cookies信息传递进新的请求,代码未做函数化处理,因此不涉及参数的获取与传递。将用户登录及更改个人信息接口代码转换为方法调用方式,请求代码如下:
//chapter05//api_Cookies.py
import requests
def login():
url='https://login.kongfz.com/Pc/Login/account'
header={
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'X-Tingyun-Id': 'OHEPtRD8z8s;r=500843061',
'Origin': 'https://login.kongfz.com',
'Referer': 'https://login.kongfz.com/'
}
body={
'loginName': 'thinkerbang', #此处为用户名参数,读者需要自行替换
'loginPass': '123456',
'captchaCode': '',
'autoLogin': '0',
'newUsername': '',
'returnUrl': '',
'captchaId': ''
}
r = requests.post(url=url,headers=header,data=body)
print(r.json())
return r.cookies
def mod_info(cook):
url_info = 'https://user.kongfz.com/User/perPro/update/'
#将请求Headers以字典的方式存入变量header_info
header_info = {
"Host": "user.kongfz.com",
"Connection": "keep-alive",
"Content-Length": "86",
"sec-ch-ua": "\"Not_A Brand\";v=\"99\", \"Google Chrome\";v=\"109\", \"Chromium\";v=\"109\"",
"Accept": "text/plain, */*; q=0.01",
"Content-Type": "application/x-www-form-urlencoded",
"X-Requested-With": "XMLHttpRequest",
"sec-ch-ua-mobile": "?0",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
"sec-ch-ua-platform": "\"Windows\"",
"Origin": "https://user.kongfz.com",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
"Referer": "https://user.kongfz.com/person/person_info.html",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9"
}
#将Body以字典的方式存入变量data_info
data_info = {
'pic': '8284%2F3108284.jpg',
'sex': 'man',
'qqNum': '359407130',
'birthday': '',
'area': '',
'sign': 'Thinkerbang2', #修改个人信息中的“个性签名”内容
'intro': ''
}
#发送POST请求,将返回结果存入变量response_info,Cookies信息通过形参cook传入
response_info = requests.post(url=url_info, data=data_info, headers=header_info, Cookies=cook)
#返回值类型是JSON字符串,也可以使用text属性进行文本输出
print(response_info.text)
#调用login()方法,将返回的Cookies信息存入cook变量
cook = login()
#调用修改个人信息方法
mod_info(cook)
返回结果见5.3.2节的执行结果,如图513所示。
在api_Cookies.py文件中两个接口方法之间存在耦合性,在基于测试框架维护测试脚本时,通常使用RequestsCookieJar对象进行Cookies参数传递,请求代码如下:
//chapter05//api_Cookies_obj.py
import requests
#声明全局变量
global cook
#将变量存入RequestsCookieJar对象容器,用来传递Cookies参数
cook = requests.cookies.RequestsCookieJar()
def login():
url='https://login.kongfz.com/Pc/Login/account'
header={
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'X-Tingyun-Id': 'OHEPtRD8z8s;r=500843061',
'Origin': 'https://login.kongfz.com',
'Referer': 'https://login.kongfz.com/'
}
body={
'loginName': 'thinkerbang', #此处为用户名参数,读者需要自行替换
'loginPass': '123456',
'captchaCode': '',
'autoLogin': '0',
'newUsername': '',
'returnUrl': '',
'captchaId': ''
}
r = requests.post(url=url,headers=header,data=body)
print(r.json())
#引入全局变量cook
global cook
#将用户登录后的Cookies信息存入变量
cook = r.cookies
def mod_info(cook):
url_info = 'https://user.kongfz.com/User/perPro/update/'
#将请求Headers以字典的方式存入变量header_info
header_info = {
"Host": "user.kongfz.com",
"Connection": "keep-alive",
"Content-Length": "86",
"sec-ch-ua": "\"Not_A Brand\";v=\"99\", \"Google Chrome\";v=\"109\", \"Chromium\";v=\"109\"",
"Accept": "text/plain, */*; q=0.01",
"Content-Type": "application/x-www-form-urlencoded",
"X-Requested-With": "XMLHttpRequest",
"sec-ch-ua-mobile": "?0",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
"sec-ch-ua-platform": "\"Windows\"",
"Origin": "https://user.kongfz.com",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
"Referer": "https://user.kongfz.com/person/person_info.html",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9"
}
#将Body以字典的方式存入变量data_info
data_info = {
'pic': '8284%2F3108284.jpg',
'sex': 'man',
'qqNum': '359407130',
'birthday': '',
'area': '',
'sign': 'Thinkerbang2', #修改个人信息中的"个性签名"内容
'intro': ''
}
#发送POST请求,将返回结果存入变量response_info,Cookies信息通过形参cook传入
response_info = requests.post(url=url_info, data=data_info, headers=header_info, Cookies=cook)
#返回值类型是JSON字符串,也可以使用text属性进行文本输出
print(response_info.text)
#调用login()方法,将返回的Cookies信息存入cook变量
login()
#调用修改个人信息方法
mod_info(cook)
返回结果见5.3.2节的执行结果,如图513所示。
5.4.2身份认证
HTTP有3种身份认证方式: 基础身份认证、netrc认证、摘要式身份认证。
基础身份认证是HTTP1.0提出的认证方式,客户端对于每个需要授权访问的请求,通过提供用户名和密码进行认证,示例代码如下:
//chapter05//api_auth.py
from requests.auth import HTTPBasicAuth
import requests
url = 'http://192.168.1.42/index.html'
header = {
'Authorization': 'Basic dXNlcjoxMjM0NTY='
}
#------------第1种基础身份认证传输-------------------
r = requests.get(url=url,headers=header,auth=HTTPBasicAuth('user','123456'))
#输出返回主体及状态码
print("第1种基础认证返回主体:",r.text)
print("第1种基础认证返回状态码:",r.status_code)
#------------第2种基础身份认证传输-------------------
url2 = 'http://user:123456@192.168.1.42/index.html'
r2 = requests.get(url=url2,headers=header)
#输出返回主体及状态码
print("第2种基础认证返回主体:",r2.text)
print("第2种基础认证返回状态码:",r2.status_code)
返回结果如图519所示。
图519基础身份认证返回结果
5.4.3生成测试执行报告
测试结果的输出通常以HTML页面或文档方式进行图文展示。在unittest测试框架部分会引入HTML页面以展示测试结果。本节引入文档,以便展示测试执行结果。
本节引入reportlab库,以便生成报告。reportlab是Python的一个标准库,可以生成图文形式的文档。
在命令行下输入pip install i https://pypi.tuna.tsinghua.edu.cn/simple pythonoffice U命令后便可安装reportlab,安装过程如图520所示。
安装完成后,在Python下输入import reportlab命令进行验证。
首先,准备生成测试执行报告所需的基础图表类文件,代码如下:
图520reportlab的安装
//chapter05//report_Graphs.py
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import Table, Paragraph
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib import colors
from reportlab.graphics.charts.barcharts import VerticalBarChart
from reportlab.graphics.charts.legends import Legend
from reportlab.graphics.shapes import Drawing
#注册字体,此处使用微软雅黑
pdfmetrics.registerFont(TTFont('MYH', 'MSYHL.TTC'))
class Graphs():
#绘制标题
@staticmethod
def draw_title(title: str):
#获取样式表
style = getSampleStyleSheet()
#标题样式
ct = style['Italic']
#单独设置样式相关属性
ct.fontName = 'MYH' #字体名
ct.fontSize = 18 #字体大小
ct.leading = 50 #行间距
ct.textColor = colors.black #字体颜色
ct.alignment = 1 #居中
ct.bold = True
#创建标题对应的段落,并且返回
return Paragraph(title, ct)
#绘制小标题
@staticmethod
def draw_little_title(title: str):
#获取样式表
style = getSampleStyleSheet()
#标题样式
ct = style['Normal']
#设置样式属性
ct.fontName = 'MYH' #字体名
ct.fontSize = 15 #字体大小
ct.leading = 30 #行间距
ct.textColor = colors.black #字体颜色
ct.alignment = 1 #对齐方式
#创建标题对应的段落,并且返回
return Paragraph(title, ct)
#绘制普通段落内容
@staticmethod
def draw_text(text: str):
#获取样式表
style = getSampleStyleSheet()
#获取普通样式
ct = style['Normal']
ct.fontName = 'MYH'
ct.fontSize = 12
ct.wordWrap = 'CJK' #设置自动换行
ct.alignment = 0 #居左对齐
ct.firstLineIndent = 32 #第1行开头空格
ct.leading = 25
return Paragraph(text, ct)
#绘制表格
@staticmethod
def draw_table(*args):
#列宽度
col_width = 80
style = [
('FONTNAME', (0, 0), (-1, -1), 'MYH'), #字体
('FONTSIZE', (0, 0), (-1, 0), 10), #第1行的字体大小
('FONTSIZE', (0, 1), (-1, -1), 10), #第2行到最后一行的字体大小
('BACKGROUND', (0, 0), (-1, 0), '#d5dae6'), #设置第1行的背景颜色
('ALIGN', (0, 0), (-1, -1), 'CENTER'), #第1行水平居中
('ALIGN', (0, 1), (-1, -1), 'CENTER'),
#第2行到最后一行横向左对齐
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), #所有表格纵向居中对齐
#设置表格内文字的颜色
('TEXTCOLOR', (0, 0), (-1, -1), colors.darkslategray),
#将表格框线设置为grey色,线宽为0.5
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
]
table = Table(args, colWidths=col_width, style=style)
return table
#创建图表
@staticmethod
def draw_bar(bar_data: list, ax: list, items: list):
drawing = Drawing(500, 250)
bc = VerticalBarChart()
bc.x = 45 #整个图表的x坐标
bc.y = 45 #整个图表的y坐标
bc.height = 200 #图表的高度
bc.width = 350 #图表的宽度
bc.data = bar_data
bc.strokeColor = colors.black #顶部和右边轴线的颜色
bc.valueAxis.valueMin = 0 #设置y坐标的最小值
bc.valueAxis.valueMax = 10 #设置y坐标的最大值
bc.valueAxis.valueStep = 1 #设置y坐标的步长
bc.categoryAxis.labels.dx = 1
bc.categoryAxis.labels.dy = -5
bc.categoryAxis.labels.angle = 0
bc.categoryAxis.categoryNames = ax
#图示
leg = Legend()
leg.fontName = 'MYH'
leg.alignment = 'right'
leg.boxAnchor = 'ne'
leg.x = 475
leg.y = 240
leg.dxTextSpace = 10
leg.columnMaximum = 3
leg.colorNamePairs = items
drawing.add(leg)
drawing.add(bc)
return drawing
执行生成测试报告代码,示例代码中测试用例执行数据是为了演示使用reportlab工具包生成PDF文件而设置的,在测试框架章节会对生成过程进行封装处理,代码如下:
//chapter05//api_report.py
from chapter05.report_Graphs import Graphs
from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from reportlab.platypus import SimpleDocTemplate
if __name__ == '__main__':
#创建内容对应的空列表
content = list()
#添加文档标题
content.append(Graphs.draw_title('接口测试结果统计'))
#添加文档说明文字
content.append(Graphs.draw_text('本段文字是接口测试用例执行说明内容。'))
#添加小标题
content.append(Graphs.draw_title(''))
content.append(Graphs.draw_little_title('接口测试结果统计表'))
#添加接口用例执行结果统计数据表
data = [
('接口名称', '执行情况', '执行总次数', '通过次数', '失败次数'),
('登录接口', '已执行', '8', '8', '0'),
('首页查询接口', '已执行', '8', '7', '1'),
('信息添加接口', '已执行', '8', '5', '3')
]
content.append(Graphs.draw_table(*data))
#生成图表
content.append(Graphs.draw_title(''))
content.append(Graphs.draw_little_title('接口用例执行结果图示'))
b_data = [(8, 7, 5), (0, 1, 3)]
ax_data = ['UserLogin', 'IndexSelect', 'AddInfo']
leg_items = [(colors.green, '执行通过'),(colors.red, '执行失败') ]
content.append(Graphs.draw_bar(b_data, ax_data, leg_items))
#生成PDF文件
doc = SimpleDocTemplate('report.pdf', pagesize=letter)
doc.build(content)
执行结果生成的PDF文件如图521所示。
图521生成测试报告
工具篇
在接口测试过程中,一款合适的接口测试工具可以让测试工作事半功倍。主流接口测试工具可以看作一些优秀的接口测试框架的界面化结果,其本身包含着良好的接口测试流程及接口测试用例的管理等要素。工具篇主要介绍了Postman、Apifox、JMeter等几款接口测试中使用频率很高的主流接口测试工具,也可以作为软件研发团队在接口测试环境的解决方案。