求乙醇和虫师的零成本实现python接口自动化视频教程,腾讯课堂的看不了……感激!!
发布网友
发布时间:2022-04-30 04:06
我来回答
共1个回答
热心网友
时间:2023-10-11 07:05
一、简介
本文从一个简单的登录接口测试入手,一步步调整优化接口调用姿势,然后简单讨论了一下接口测试框架的要点,最后介绍了一下我们目前正在使用的接口测试框架pithy。期望读者可以通过本文对接口自动化测试有一个大致的了解。
二、引言
为什么要做接口自动化测试?
在当前互联网产品迭代频繁的背景下,回归测试的时间越来越少,很难在每个迭代都对所有功能做完整回归。但接口自动化测试因其实现简单、维护成本低,容易提高覆盖率等特点,越来越受重视。
为什么要自己写框架呢?
使用requets + unittest很容易实现接口自动化测试,而且requests的api已经非常人性化,非常简单,但通过封装以后(特别是针对公司内特定接口),再加上对一些常用工具的封装,可以进一步提高业务脚本编写效率。
三、环境准备
确保本机已安装python2.7以上版本,然后安装如下库:
pip install flask
pip install requests12
后面我们会使用flask写一个用来测试的接口,使用requests去测试。
四、测试接口准备
下面使用flask实现两个http接口,一个登录,另外一个查询详情,但需要登录后才可以,新建一个demo.py文件(注意,不要使用windows记事本),把下面代码copy进去,然后保存、关闭。
接口代码
#!/usr/bin/python# coding=utf-8from flask import Flask, request, session, jsonify
USERNAME = 'admin'PASSWORD = '123456'app = Flask(__name__)
app.secret_key = 'pithy'@app.route('/login', methods=['GET', 'POST'])def login():
error = None
if request.method == 'POST': if request.form['username'] != USERNAME:
error = 'Invalid username'
elif request.form['password'] != PASSWORD:
error = 'Invalid password'
else:
session['logged_in'] = True
return jsonify({'code': 200, 'msg': 'success'}) return jsonify({'code': 401, 'msg': error}), 401@app.route('/info', methods=['get'])def info():
if not session.get('logged_in'): return jsonify({'code': 401, 'msg': 'please login !!'}) return jsonify({'code': 200, 'msg': 'success', 'data': 'info'})if __name__ == '__main__':
app.run(debug=True)12345678910111213141516171819202122232425262728293031323334
最后执行如下命令:
python demo.py1
响应如下:
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat12
大家可以看到服务已经起来了。
接口信息
登录接口
请求url
/login
请求方法
post
请求参数
参数名称
参数类型
参数说明
username String 登录名称
password String 登录密码
响应信息
参数名称
参数类型
参数说明
code Integer 结果code
msg String 结果信息
详情接口
请求url
/info
请求方法
get
请求cookies
参数名称
参数类型
参数说明
session String session
响应信息
参数名称
参数类型
参数说明
code Integer 结果code
msg String 结果信息
data String 数据信息
五、编写接口测试
测试思路
使用requests [使用链接] 库模拟发送HTTP请求。
使用python标准库里unittest写测试case。
脚本实现
#!/usr/bin/python# coding=utf-8import requestsimport unittestclass TestLogin(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.login_url = 'http://127.0.0.1:5000/login'
cls.info_url = 'http://127.0.0.1:5000/info'
cls.username = 'admin'
cls.password = '123456'
def test_login(self):
"""
测试登录
"""
data = { 'username': self.username, 'password': self.password
}
response = requests.post(self.login_url, data=data).json() assert response['code'] == 200
assert response['msg'] == 'success'
def test_info(self):
"""
测试info接口
"""
data = { 'username': self.username, 'password': self.password
}
response_cookies = requests.post(self.login_url, data=data).cookies
session = response_cookies.get('session') assert session
info_cookies = { 'session': session
}
response = requests.get(self.info_url, cookies=info_cookies).json() assert response['code'] == 200
assert response['msg'] == 'success'
assert response['data'] == 'info'1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
六、优化
封装接口调用
写完这个测试登录脚本,你或许会发现,在整个项目的测试过程,登录可能不止用到一次,如果每次都这么写,会不会太冗余了? 对,确实太冗余了,下面做一下简单的封装,把登录接口的调用封装到一个方法里,把调用参数暴漏出来,示例脚本如下:
#!/usr/bin/python# coding=utf-8import requestsimport unittesttry: from urlparse import urljoinexcept ImportError: from urllib.parse import urljoinclass DemoApi(object):
def __init__(self, base_url):
self.base_url = base_url def login(self, username, password):
"""
登录接口
:param username: 用户名
:param password: 密码
"""
url = urljoin(self.base_url, 'login')
data = { 'username': username, 'password': password
} return requests.post(url, data=data).json() def get_cookies(self, username, password):
"""
获取登录cookies
"""
url = urljoin(self.base_url, 'login')
data = { 'username': username, 'password': password
} return requests.post(url, data=data).cookies def info(self, cookies):
"""
详情接口
"""
url = urljoin(self.base_url, 'info') return requests.get(url, cookies=cookies).json()class TestLogin(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.base_url = 'http://127.0.0.1:5000'
cls.username = 'admin'
cls.password = '123456'
cls.app = DemoApi(cls.base_url) def test_login(self):
"""
测试登录
"""
response = self.app.login(self.username, self.password) assert response['code'] == 200
assert response['msg'] == 'success'
def test_info(self):
"""
测试获取详情信息
"""
cookies = self.app.get_cookies(self.username, self.password)
response = self.app.info(cookies) assert response['code'] == 200
assert response['msg'] == 'success'
assert response['data'] == 'info'12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
OK,在这一个版本中,我们不但在把登录接口的调用封装成了一个实例方法,实现了复用,而且还把host(self.base_url)提取了出来,但问题又来了,登录之后,登录接口的http响应会把session以 cookie的形式set到客户端,之后的接口都会使用此session去请求,还有,就是在接口调用过程中,希望可以把日志打印出来,以便调试或者出错时查看。
好吧,我们再来改一版。
保持cookies&增加log信息
使用requests库里的同一个Session对象(它也会在同一个Session 实例发出的所有请求之间保持 cookie),即可解决上面的问题,示例代码如下:
#!/usr/bin/python# coding=utf-8import unittestfrom pprint import pprintfrom requests.sessions import Sessiontry: from urlparse import urljoinexcept ImportError: from urllib.parse import urljoinclass DemoApi(object):
def __init__(self, base_url):
self.base_url = base_url # 创建session实例
self.session = Session() def login(self, username, password):
"""
登录接口
:param username: 用户名
:param password: 密码
"""
url = urljoin(self.base_url, 'login')
data = { 'username': username, 'password': password
}
response = self.session.post(url, data=data).json()
print('\n*****************************************')
print(u'\n1、请求url: \n%s' % url)
print(u'\n2、请求头信息:')
pprint(self.session.headers)
print(u'\n3、请求参数:')
pprint(data)
print(u'\n4、响应:')
pprint(response) return response def info(self):
"""
详情接口
"""
url = urljoin(self.base_url, 'info')
response = self.session.get(url).json()
print('\n*****************************************')
print(u'\n1、请求url: \n%s' % url)
print(u'\n2、请求头信息:')
pprint(self.session.headers)
print(u'\n3、请求cookies:')
pprint(dict(self.session.cookies))
print(u'\n4、响应:')
pprint(response) return responseclass TestLogin(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.base_url = 'http://127.0.0.1:5000'
cls.username = 'admin'
cls.password = '123456'
cls.app = DemoApi(cls.base_url) def test_login(self):
"""
测试登录
"""
response = self.app.login(self.username, self.password) assert response['code'] == 200
assert response['msg'] == 'success'
def test_info(self):
"""
测试获取详情信息
"""
self.app.login(self.username, self.password)
response = self.app.info() assert response['code'] == 200
assert response['msg'] == 'success'
assert response['data'] == 'info'123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
大功告成,我们把多个相关接口调用封装到一个类中,使用同一个requests Session实例来保持cookies,并且在调用过程中打印出了日志,我们所有目标都实现了,但再看下脚本,又会感觉不太舒服,在每个方法里,都要写一遍print 1、2、3… 要拼url、还要很多细节等等,但其实我们真正需要做的只是拼出关键的参数(url参数、body参数或者传入headers信息),可不可以只需定义必须的信息,然后把其它共性的东西都封装起来呢,统一放到一个地方去管理?
封装重复操作
来,我们再整理一下我们的需求:
首先,不想去重复做拼接url的操作。
然后,不想每次都去手工打印日志。
不想和requests session打交道。
只想定义好参数就直接调用。
我们先看一下实现后,脚本可能是什么样:
class DemoApi(object):
def __init__(self, base_url):
self.base_url = base_url @request(url='login', method='post')
def login(self, username, password):
"""
登录接口
"""
data = { 'username': username, 'password': password
} return {'data': data} @request(url='info', method='get')
def info(self):
"""
详情接口
"""
pass1234567891011121314151617181920212223
调用登录接口的日志:
******************************************************1、接口描述
登录接口2、请求urlhttp://127.0.0.1:5000/login3、请求方法post4、请求headers
{ "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Connection": "keep-alive", "User-Agent": "python-requests/2.7.0 CPython/2.7.10 Darwin/16.4.0"}5、body参数
{ "password": "123456", "username": "admin"}6、响应结果
{ "code": 200, "msg": "success"}123456789101112131415161718192021222324252627282930
在这里,我们使用python的装饰器功能,把公共特性封装到装饰器中去实现。现在感觉好多了,没什么多余的东西了,我们可以专注于关键参数的构造,剩下的就是如何去实现这个装饰器了,我们先理一下思路:
获取装饰器参数
获取函数/方法参数
把装饰器和函数定义的参数合并
拼接url
处理requests session,有则使用,无则新生成一个
组装所有参数,发送http请求并打印日志
因篇幅*,源码不再列出,有兴趣的同学可以查看已经实现的源代码。
源代码查看地址:https://github.com/yuyu1987/pithy-test/blob/master/pithy/api.py
七、扩展
http接口请求的姿势我们定义好了,我们还可以做些什么呢?
[x] 非HTTP协议接口
[x] 测试用例编写
[x] 配置文件管理
[x] 测试数据管理
[x] 工具类编写
[x] 测试报告生成
[x] 持续集成
[x] 等等等等
需要做的还是挺多的,要做什么不要做什么,或者先做哪个,我觉得可以根据以下几点去判断:
是否有利于提高团队生产效率?