前言
本文介绍如何用Python实现接口测试。
1. Python语言基础
为什么要选用Python来进行自动化测试开发。我想大概可以这么来考虑:
1)Python是一种解释型、面向对象、动态数据类型的高级程序设计语言。只要你有一定的理工基础,Python基本上是最容易学的一门编程语言了。
2)Python可在命令行输入语句,立刻获得执行结果。(因此,可作为一个加强版的计算器来使用)
3)Python在科学计算和人工智能自然语言处理方面有着强大的优势
4)Python是黑客极其喜欢的一门语言,因为它可以轻易在从手机中到PC到服务器的多种设备上运行
5)一些知名网站的后台是用Python开发的,比如豆瓣 。。。
6)从科学计算、AI、到Web框架,到测试框架,Python提供许多成熟的 开发框架
7)由于历史的原因,Python版本有2.X 和 3.X 的差别,而这些差别在语法上还比较大,测试开发建议选用Python2.X 版本
2. Python 安装配置和简单运行
从Python官网下载 2.7.X 版本,然后安装。安装之后将 python 路径写入 PATH环境变量
1)Python可以从命令行运行,如下图所示
$ python
Python 2.7.10 (default, Oct 23 2015, 19:19:21)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.0.59.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> print "Hello, World!"
Hello, World!
>>> 1+3
4
>>> import math
>>> math.sqrt(9)
3.0
>>>
2)将代码写如文本文件,保存为 xxx.py 文件,然后从命令行运行,如下面例子中的 python app.py
#coding=utf-8
def isEven(num):
if num % 2 == 0:
return True
else:
return False
for i in range(1, 10):
#if i % 2 == 0:
if isEven(i):
print '%d 是偶数!' %i
else:
print '%d 是奇数!' %i
print 'finished!!'
然后在命令行键入 c:\> python xxx.py (MAC/Linux : $ python xxx.py)
$ python judge.py
1 是奇数!
2 是偶数!
3 是奇数!
4 是偶数!
5 是奇数!
6 是偶数!
7 是奇数!
8 是偶数!
9 是奇数!
finished!!
3. Python 编程简介
顺便简单说一下一些基本编程概念(有编程基础的童鞋请忽略...)。
3.1 应用程序概念
1)所有应用都可归纳为 I / O ,即给程序一些输入,程序给你一些输出
输入方式:文本键入、语音输入,手指点击,等等
输出方式:文本显示、语音播报,根据指令移动,等等
2)最简单:不管输入如何,输出总是统一的,比如显示一句话“你好,世界”
3)稍聪明:不对输入做处理,回显输入+特定输出,比如 “你好,张三”
4)再聪明:根据输入分三六五等,比如 “你好,张三” “滚犊子,王五”
5)更聪明:根据输入,做初步分析,继续要求输入,然后给出应答
比如:先输入性别,年龄,继续问做什么工作,挣多少钱,挣钱少的说“滚犊子”,挣钱多的,继续问“有没有闲钱”,。。。。
6)超聪明:
扫描站在面前的这个人,然后说“你女朋友今天又不高兴了吧”或者说 “你抽空洗个澡再来和我聊。。。”
>>> print "hello, world!"
hello, world!
2)稍聪明
>>> name = "Lee"
>>> print "Hello, " + name
Hello, Lee
#coding=utf-8
name = raw_input("请输入你的名字:")
if name == "leesi":
print '你好!' + name
else:
print '滚犊子!' + name
1)顺序语句和判断语句
上面例子中 从输入名字 到判断名字 到给出反应信息 是按顺序执行的,所以叫顺序语句。
上述例子中根据名字判断,if name == "leesi" 就是一个判断语句
2)循环语句+判断语句
for i in range(1, 10) -- 是一个循环语句,Python语法非常直观,很容易就猜到意思。
#coding=utf-8
def isEven(num):
if num % 2 == 0:
return True
else:
return False
for i in range(1, 10):
#if i % 2 == 0:
if isEven(i):
print '%d 是偶数!' %i
else:
print '%d 是奇数!' %i
print 'finished!!'
上述例子中将判断逻辑封装成一个函数。这里只是一个举例,真实编程中不会都这么简单,把一些处理逻辑加在一起才封装成一个函数。
def isEven(num):
if num % 2 == 0:
return True
else:
return False
4)再进一步,我们可以将常用逻辑以面向对象的思维封装成一个类,比如
#coding=utf-8
#类定义
class people:
#定义基本属性
name = ''
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s is speaking: I am %d years old" %(self.name,self.age))
p = people('tom',10,30)
p.speak()
这些库和框架以及开源代码和社区,就形成了一门开发语言的生态圈。
比如接口测试中经常就用到 urllib urllib2 等库,自动化测试经常用 selenium 等框架。
4 用Python+Flask实现一个简单接口服务
from flask import Flask, jsonify, abort, make_response, request, url_for
#from flask.ext.httpauth import HTTPBasicAuth
app = Flask(__name__)
tasks = [
{
'id': 1,
'title': u'Buy groceries',
'description': u'Milk, Cheese, Pizza, Fruit, Tylenol',
'done': False
},
{
'id': 2,
'title': u'Learn Python',
'description': u'Need to find a good Python tutorial on the web',
'done': False
}
]
token = ''
#auth = HTTPBasicAuth()
#@auth.get_password
#def get_password(username):
# if username == 'ok':
# return 'python'
# return None
#@auth.error_handler
#def unauthorized():
# return make_response(jsonify({'error': 'Unauthorized access'}), 401)
def make_public_task(task):
new_task = {}
for field in task:
if field == 'id':
new_task['uri'] = url_for('get_task', task_id=task['id'], _external=True)
else:
new_task[field] = task[field]
return new_task
def valid_token(req_token):
if req_token == 'JLKJADLF09480193':
return True
else:
return False
@app.route('/todo/api/v1.0/tasks', methods=['GET'])
def get_tasks():
req_token = request.args.get('token', u'')
if req_token != 'JLKJADLF09480193':
return jsonify({'error': 'invalid token!! Please re-login!!'})
else:
return jsonify({'tasks': map(make_public_task, tasks)})
@app.route('/todo/api/v1.0/tasks/', methods=['GET'])
def get_task(task_id):
req_token = request.args.get('token', u'')
if valid_token(req_token):
task = filter(lambda t: t['id'] == task_id, tasks)
if len(task) == 0:
abort(404)
return jsonify({'task': task[0]})
else:
return jsonify({'error': 'invalid token!! Please re-login!!'})
@app.errorhandler(404)
def not_found(error):
return make_response(jsonify({'error': 'Not found'}), 404)
@app.route('/todo/api/v1.0/get_token', methods=['POST'])
def get_token():
if not request.form.get('user'):
abort(400)
#user = request.form.get('user')
#pass = request.form.get('passwd')
if request.form.get('user') == 'ruby' and request.form.get('passwd') == 'cookie':
token = 'JLKJADLF09480193'
return jsonify({'token': token})
else:
return jsonify({'error': 'Invalid user or password!! Please re-login!!'})
@app.route('/todo/api/v1.0/login', methods=['POST'])
def login_verify():
if not request.json or not 'user' in request.json:
abort(400)
if request.json['user'] == 'ruby' and request.json['passwd'] == 'happy':
token = 'JLKJADLF09480193'
return jsonify({'token': token})
else:
return None
@app.route('/todo/api/v1.0/tasks', methods=['POST'])
def create_task():
if not request.json or not 'title' in request.json or not 'token' in request.json:
abort(400)
if not valid_token(request.json['token']):
return jsonify({'error': 'invalid token!! Please re-login!!'})
task = {
'id': tasks[-1]['id'] + 1,
'title': request.json['title'],
'description': request.json.get('description', ""),
'done': False
}
tasks.append(task)
return jsonify({'task': task}), 201
@app.route('/todo/api/v1.0/tasks/', methods=['PUT'])
def update_task(task_id):
task = filter(lambda t: t['id'] == task_id, tasks)
if len(task) == 0:
abort(404)
if not request.json:
abort(400)
if 'title' in request.json and type(request.json['title']) != unicode:
abort(400)
if 'description' in request.json and type(request.json['description']) is not unicode:
abort(400)
if 'done' in request.json and type(request.json['done']) is not bool:
abort(400)
task[0]['title'] = request.json.get('title', task[0]['title'])
task[0]['description'] = request.json.get('description', task[0]['description'])
task[0]['done'] = request.json.get('done', task[0]['done'])
return jsonify({'task': task[0]})
@app.route('/todo/api/v1.0/tasks/', methods=['DELETE'])
def delete_task(task_id):
task = filter(lambda t: t['id'] == task_id, tasks)
if len(task) == 0:
abort(404)
tasks.remove(task[0])
return jsonify({'result': True})
if __name__ == '__main__':
app.run(debug=True)
5、针对这个接口设计接口测试方案
接口说明如下-->
接口说明执行 |
请求 |
返回 |
1)输入用户名密码获取TOKEN JSON格式提交请求数据 |
POST http://localhost:5000/todo/api/v1.0/login {"user":"ruby","passwd":"happy"} |
{ "token": "JLKJADLF09480193"} -JSON |
2)输入用户名密码获取TOKEN 默认方式提交请求数据 |
POST http://localhost:5000/todo/api/v1.0/get_token user=ruby&passwd=cookie |
{ "token": "JLKJADLF09480193"} -JSON |
3)获取全部任务信息 |
GET http://localhost:5000/todo/api/v1.0/tasks?token=JLKJADLF09480193 |
{ “tasks”: [ {des …], [ ] } |
4)获取单个任务信息 |
GET http://localhost:5000/todo/api/v1.0/tasks/ |
{ "task": { "description”… |
5)创建任务信息 |
POST http://localhost:5000/todo/api/v1.0/tasksPOST data:{"title":"Learn interface testing", "description":"Need to train some guy", "token":"JLKJADLF09480193"} |
{ "task": { "description": "Need to train some guy", "done": false, "id": 3, "title": "Learn interface testing" }} |
6)更新任务信息 |
。。。。 |
测试用例设计如下
接口执行顺序 |
用例 |
预期返回 |
1)输入用户名密码获取TOKEN JSON格式提交请求数据 |
{"user":"ruby","passwd":"happy"} |
{ "token": "JLKJADLF09480193"} -JSON |
2)输入用户名密码获取TOKEN 默认方式提交请求数据 |
user=ruby&passwd=cookie |
{ "token": "JLKJADLF09480193"} -JSON |
3)获取全部任务信息 |
token=JLKJADLF09480193 |
{ “tasks”: [ {des …], [ ] } |
4)获取单个任务信息 |
Id=1 token=JLKJADLF09480193 |
{ "task": { "description”… |
5)创建任务信息 |
{title":"Learn interface testing", "description":"Need to train some guy", "token":"JLKJADLF09480193" } |
{ "task": { "description": "Need to train some guy", "done": false, "id": 3, "title": "Learn interface testing" }} |
6)错误用户名密码 |
User=“123”passwd=“err” |
{'error': 'Invalid user or password!! Please re-login!!'} |
7)错误TOKEN获取任务信息 |
token=1234 |
{'error': 'invalid token!! Please re-login!!'} |
8)正确TOKEN获取不存在任务 |
Token=… id=100 |
404 Not Found |
我们准备用python编写一端代码,读入EXCEL文件中的测试用例参数,比如用户名和信息,然后填入某个接口,并执行,
根据返回结果和EXCEL文件中的预期结果进行比对,通过的打印PASS,否则打印FAIL
import xlrd
import urllib,urllib2
serv_url = 'http://localhost:5000/todo/api/v1.0/get_token'
xls_file = r'/Users/XXXX/Documents/Test_Utils/account.xlsx'
get_url = 'http://localhost:5000/todo/api/v1.0/tasks'
token = {'token': 'JLKJADLF09480193'}
def do_post(url, data):
url_value = urllib.urlencode(data)
full_url = urllib2.Request(url, url_value)
resp = urllib2.urlopen(full_url)
content = resp.read()
#print(content)
if "token" in content:
return "token"
else:
return "error"
def do_get(url, data):
data = urllib.urlencode(data)
url2 = url + '?' + data
resp = urllib2.urlopen(url2)
content = resp.read()
print content
def file_to_test(xls_file):
book = xlrd.open_workbook(xls_file)
api_sheet = book.sheet_by_index(0)
nrows = api_sheet.nrows
for i in range(1, nrows):
user = api_sheet.cell(i, 0)
passwd = api_sheet.cell(i, 1)
ec_resp = api_sheet.cell(i,2)
uv = user.value
pv = passwd.value
data1 = {"user":uv, "passwd":pv}
ac_resp = do_post(serv_url, data1)
#print(ac_resp)
if ac_resp == ec_resp.value:
print "%d pass" % i
else:
print "###### %d fail #######" %i
if __name__ == "__main__":
file_to_test(xls_file)
do_get(get_url, token)
20)处理GET请求
21)对请求参数做URL编码
22)拼装完整请求URL
23)提交请求并获取Response
24)读取Response
25)打印出Response
26)空
27)定义从文件读取参数,并处理请求
28)打开EXCEL文件
29)读取EXCEL文件的第一个选项页
30)获取EXCEL文件的每行参数
31)循环处理
32-34)读参数1 user 参数2 密码 以及 预期结果
35-36)获取参数值用于正式URL拼接
37)组装参数体
38)调用do_post() 函数,正式处理请求
39)打印返回值
40)判断是否与预期相符
41-..)根据判断,打印PASS / Fail 结果
45-47)主函数顺序处理 login & get_tasks
修改代码中的EXCEL文件路径(根据你电脑上的存放),在EXCEL文件中的第一个选项页,输入一下内容
user | passwd | resp |
ruby | happy | error |
ruby | cookie | token |
candy | asdf | error |
james | jdflkdj | error |
tomcat | kjlkvljdkf | token |
先打开服务端 $ python app.py
$ python app.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger pin code: 301-460-587
$ python restcli.py
1 pass
2 pass
3 pass
4 pass
###### 5 fail #######
{
"tasks": [
{
"description": "Milk, Cheese, Pizza, Fruit, Tylenol",
"done": false,
"title": "Buy groceries",
"uri": "http://localhost:5000/todo/api/v1.0/tasks/1"
},
{
"description": "Need to find a good Python tutorial on the web",
"done": false,
"title": "Learn Python",
"uri": "http://localhost:5000/todo/api/v1.0/tasks/2"
}
]
}