Python接口测试


前言


本文介绍如何用Python实现接口测试。


1. Python语言基础

为什么要选用Python来进行自动化测试开发。我想大概可以这么来考虑:

1)Python是一种解释型、面向对象、动态数据类型的高级程序设计语言。只要你有一定的理工基础,Python基本上是最容易学的一门编程语言了。

2Python可在命令行输入语句,立刻获得执行结果。(因此,可作为一个加强版的计算器来使用)

3Python在科学计算和人工智能自然语言处理方面有着强大的优势

4Python是黑客极其喜欢的一门语言,因为它可以轻易在从手机中到PC到服务器的多种设备上运行

5)一些知名网站的后台是用Python开发的,比如豆瓣 。。。

6)从科学计算、AI、到Web框架,到测试框架,Python提供许多成熟的 开发框架

7)由于历史的原因,Python版本有2.X3.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)超聪明:
     扫描站在面前的这个人,然后说“你女朋友今天又不高兴了吧”或者说 “你抽空洗个澡再来和我聊。。。”

1) 永远的 Hello World   -- 最简单,不管你做啥,它就一句话:你好,世界!!

>>> print "hello, world!"
hello, world!
2)稍聪明

>>> name = "Lee"
>>> print "Hello, " + name
Hello, Lee

3)再聪明

#coding=utf-8

name = raw_input("请输入你的名字:")

if name == "leesi":
    print '你好!' + name
else:
    print '滚犊子!' + name

3.2 代码控制基本概念

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!!'

3)将常用处理逻辑封装成一个函数 

上述例子中将判断逻辑封装成一个函数。这里只是一个举例,真实编程中不会都这么简单,把一些处理逻辑加在一起才封装成一个函数。

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()

3.3 语言生态
 围绕着一门编程语言,可以将一些常用功能封装成函数库,或者更大一点级别的框架。

这些库和框架以及开源代码和社区,就形成了一门开发语言的生态圈。

比如接口测试中经常就用到 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)

Flask 是一个基于Python的微型WEB框架。在这里,我们用来搭建一个简单的RESTful风格接口。

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/?token=JLKJADLF09480193

{  "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


6、测试脚本设计

  我们准备用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)


代码说明如下:

1)导入 处理EXCEL文件库
2)导入处理URL请求库
3)
4)服务器请求URL
5)EXCEL文件存储路径
6)GET请求URL
7)请求参数
8)
9)定义函数来处理post请求
10)将请求参数做URL编码
11)拼装完整请求URL
12)打开请求,并获取Response
13)读取Response
14)打印出完整返回(已注释掉)
15)判断返体中是否存在特定数据(似断言)
16)如果有返回
17)如果没有
18)返回错误信息

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


7、运行测试脚本

先打开服务端 $ 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"
    }
  ]
}


以上只是一个简单的示例,没有将接口完全写完。各位童鞋可以据此进行修改。祝你成功!


你可能感兴趣的:(自动测试)