为什么要用Python实现接口自动化?
使用requests+unittest很容易实现接口自动化测试,而且requests的api已经非常人性化,通过封装以后(特别是针对公司内特定接口),再加上对一些常用工具的封装,可以进一步提高业务脚本编写效率。
搭建环境
确保Python版本是2.7以上
pip install flask
pip install requests
上一篇文章写了可以用Flask框架写接口,这次我们就正好用上了。写好接口以后,就可以用requests去测试了。
接口代码
新建一个demo.py文件,用flask实现两个http接口,一个用于登录,一个用于登录后的查询。
from flask import Flask,request,session,jsonify
USERNAME='admin'
PASSWORK='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'})
@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)
启动的命令
python demo.py
从上面的代码中可以看出:
-
请求url为
/login
请求方法为post
-
详情接口URL为
/info
请求方法为get
编写接口测试代码
脚本实现为:
import requests
import unittest
class 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'
代码优化
封装接口调用,写完这个测试登录脚本,你或许会发现,在整个项目的测试过程,登录可能不止用到一次,如果每次都这样写,会不会太冗余了?下面做一个简单的封装,把登录接口的调用封装到一个方法里,把调用参数暴露出来,示例脚本如下:
import requests
import unittest
try:
from urlparse import urljoin
except ImportError:
from urllib.parse import urljoin
class 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'
在上面的版本中,我们不但把登录接口的调用封装成了一个实例方法,实现了复用,而且还把host(self.base_url)提取出来。
进一步改进:让登录之后,登录接口的http响应会把session以cookie的形式set到客户端,之后的接口都会使用此session去请求,还有就是在接口调用过程中,希望可以把日志打印出来。
解决方法:使用requests库里的同一个Session对象,可解决,示例代码如下:
import unittest
from pprint import pprint
from requests.sessions import Session
try:
from urlparse import urljoin
except ImportError:
from urllib.parse import urljoin
class 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(data)
print(u'\n4、响应:')
pprint(response)
return response
class TestLogin(unittest.TestCase):
@classsmethod
def setUpClass(cls):
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'
上面的代码,我们把多个相关接口调用封装到了一个类中,使用同一个requests Session实例来保持cookies,并且在调用过程中打印出了日志。但是代码看着还是不舒服,在每个方法里,我们都需要写一遍print 1、2、3要拼url、还要很多细节等等,但其实我们真正需要做的只是拼接出关键参数(url参数、body参数或者传入headers信息),可不可以只需要定义必须的信息,然后把其他共性的东西都封装起来,统一放到一个地方去管理?
在这里就需要使用Python的装饰器功能,把公共特性封装到装饰器中去实现。
示例代码
扩展
接下来我们还可以做什么来丰富呢?
- 非HTTP协议接口
- 测试用例编写
- 配置文件管理
- 测试数据管理
- 工具类编写
- 测试报告生成
- 持续集成
关于测试报告
目前python的主流单元测试框均有report插件
- pytest:推荐使用pytest-html和allure pytest。
- unittest:推荐使用HTMLTestRunner。
测试用例编写原则:
-
原子性:每个用例保持独立,彼此不耦合,以降低干扰。
-
专一性:一个用例应该专注于验证一件事情,而不是做很多事情,一个测试点不要重复验证。
-
稳定性:绝大多数用例应该是非常稳定的,也就是说不会经常因为除环境以外的因素挂掉,因为如果在一个测试项目中有很多不稳定的用例的话,测试结果就不能很好的反应项目质量。
-
分类清晰:有相关性的用例应写到一个模块或一个测试类里,这样做即方便维护,又提高了报告的可读性。
文章参考:
饿了么技术社区-GitChat(他们用的框架为pithy)
网友评论