一、接口测试理论

1. 定义

接口的本质
它其实就是一种契约,遵循这样一种形式:在开发前期,我们约定接口会接收什么数据;在处理完成后它又会返回什么数据。

什么是接口?
一个系统或两个不同的系统中中不同的两个功能,他们之间相互连接的部分就是接口

接口测试的定义
接口测试其实就是模拟客户端,通过接口通信来检测被测接口的正确性和兼容性
接口测试是测试系统组件间接口的一种测试,主要检测系统内部组件与系统或外部间的交互点

接口测试的基本步骤

  1. 准备测试数据
  2. 通过接口测试工具,发起对被测接口的请求
  3. 验证返回结果的响应

前后端分离
前端与后端协商好接口定义方式,由后端提供接口功能,前端调用接口功能,以实现预期的功能,优势如下:

  • 后端无需精通前端技术,只需专注API数据处理即可
  • 前端无需关心后端功能如何实现,只需专注页面设计
  • 可以扩大接口的应用范围,比如web接口应用到APP中

流程图
image.png

2. 接口测试的分类

按照使用场景划分:

  • 内部接口:系统自身的接口,如淘宝的登录,投资理财产品等
  • 外部接口:系统提供给外部使用的接口,如微信支付,支付宝支付等

3. 接口测试的目的

  • 符合尽早测试的原则,bug发现越早,修复成本越低
  • 可以覆盖更多测试场景(绕开了前端页面的限制)
  • 后期可以扩展成接口自动化、性能、安全等测试

4. 测试金字塔

06  |  Requests接口测试 - 图2

各层测试占比:

  • unit(70%: 单元测试
  • Api(20%):接口测试
  • UI(10%):用户界面测试

5. 接口术语

5.1 接口分类

  • HTTP接口:基于HTTP协议,常用方法get, post,常用数据格式是json
  • webservice接口:走soap协议,常用数据格式是xml

5.2 传输数据的格式

json

  1. {"name":"zs"}

xml

  1. <note>
  2. <to>George</to>
  3. <from>John</from>
  4. <heading>Reminder</heading>
  5. <body>Don't forget the meeting!</body>
  6. </note>

5.3 cookie

cookie是储存在客户端的一小段文本,可以跟随请求一起发送至服务器,目的为了保持客户端的状态(因为HTTP是一种无状态协议)

5.4 session

session是一种存放在服务器端的用来保存用户数据一种保持状态的方案

5.5 token

token其实就是一个 令牌,由服务器生成,返回给客户端,作为客户端请求的一个标识

6. HTTP接口的请求方法

get
一般用于向指定URL请求资源,比如说:查询订单,查询个人信息等
post
一般用于向指定URL提交数据,比如注册,登录,新增等等
put
一般用于向指定URL提交最新的数据,比如修改信息,修改密码等等
delete
一般用于删除特定资源,比如删除订单,删除评论等等

7. 接口测试的范围

  1. 功能测试(参数的长度,类型,是否必填,唯一性等等)
  2. 安全测试(是否能篡改数据,数据是否加密等等)
  3. 逻辑测试(验证先后顺序)
  4. 性能测试(cpu,内存,吞吐量,响应时间,并发数… …)
  5. 异常测试(请求超时,重复请求… …)

    8. 接口测试的流程

  6. 需求分析

  7. 熟悉接口文档
  8. 编写测试要点(非必须)
  9. 编写测试用例
  10. 执行测试用例
  11. 编写自动化脚本(如果做了接口自动化的情况下)
  12. 编写测试报告

    9. 接口测试的工具

  • jmeter:主流性能测试工具,也可以用来做接口测试
  • postman:界面美观,功能齐全的接口测试工具
  • requestspython中的一个用来做接口测试的模块,可以实现接口自动化测试
  • soapui:测试webservice接口的工具
  • robotframework:基于Python的一个关键字测试框架

    10. 接口测试用例设计

    06  |  Requests接口测试 - 图3

二、Requests

1. 安装

pip install requests

2. get请求

向目标URL发送get请求,接收响应
get方法参数说明如下:

  • url:必填,指定请求的目标网址
  • params:指定请求参数,字典类型

百度搜索实例:

import requests

# 发送get请求
r = requests.get('https://www.baidu.com', params={'wd':'Python'})
print(r.status_code)            # 查看响应码
print(r.text)                       # 查看响应文本
print(r.url)                        # 查看请求地址
print(r.cookies)              # 查看请求中的cookie
print(r.headers)            # 查看请求头部信息

# 将响应数据写入文件
with open('baidu.html', 'w', encoding='utf-8') as f:
    f.write(r.text)

r.encoding = r.apparent_encoding            # 自动解码响应文本(乱码时添加)

3. post请求

向目标URL发送post请求,接收响应
post请求参数说明如下:

  • url:指定请求的目标网址
  • data:指定请求的参数,字典类型
  • json:指定请求的参数,json类型

注意:

  • 如果请求头部中出现了content-typeform,则请求参数以data传入,即传入字典类型
  • 如果请求头部中出现了content-typejson,则请求参数以json传入,即传入json类型

ecshop登录实例:
06  |  Requests接口测试 - 图4

datas = {"username":"liyihang", "password":"tashi123", "act":"act_login"}
r = requests.post('http://localhost:82/ecshop/user.php', data=datas)
print(r.status_code)
print(r.text)

result = r.text
if result.find('欢迎您回来')!= -1:
    print('登录成功')
else:
    print('登录失败')

post传入字典:

data = {"name":"zs", "age":18}
r =requests.post('http://httpbin.org/post', data=data)   # 传入字典类型数据
{
  "form": { "age": "18", "name": "zs"},   # 字典数据传在这儿
  "json": null,                                          # json数据为空
}

post传入json:

r =requests.post('http://httpbin.org/post', json=json.dumps(data))  # 传入json数据

注意:

{
  "form": {},                                                     # form为空
  "json": "{\"name\": \"zs\", \"age\": 18}",   # json数据传在这儿
}

4. 保持会话

在项目中,很多操作都是需要先登录才能进行,所以要先发送登录请求并保持登录成功的状态,这样才能进行后续操作。在requests中,可以使用session来保持登录状态

s = requests.session()      # 定义一个session
r1 = s.post('http://localhost:82/ecshop/user.php', data=datas)  # 在session中发送登录请求

recharge = {"amount":100, "user_note":"20191502", "payment_id":2, "act":"act_account"}
r2 = s.post('http://localhost:82/ecshop/user.php', data=recharge)    # 在session中发送充值请求
result2 = r2.text

if result2.find('您的充值金额为')!= -1:        # 验证充值是否成功
    print('充值成功')
else:
    print('充值失败')

5. 自定义请求头

很多网站为了保证安全以及减轻服务器压力,都会设置反爬虫机制,所谓反爬虫机制就是指对于非浏览器的请求拒绝处理,在这种情况下,就需要自定义headers中的数据,伪装成浏览器即可正常访问。

以访问 知乎发现页 为例:

import requests

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36"
}    # 该参数值通过fiddler抓取获得
r = requests.get('https://www.zhihu.com/explore', headers=headers)
print(r.status_code)
print(r.text)

6. 关闭证书验证

在发送HTTPS请求时,如果开启了Fiddler,代码可能会出现requests.exceptions.SSLError: HTTPSConnectionPool错误,如果出现这个错误,可以关闭请求中的证书验证规避:

r = requests.get('https://www.zhihu.com/explore', headers=agent, verify=False)  # verify=False 关闭证书验证

关闭证书验证后,出现的用户警告,额可以通过urllib3disable_warnnings()关闭

import urllib3

urllib3.disable_warnings()   # 关闭警告

三、Postman

1. 下载安装

  • 官网:https://www.getpostman.com

    2. 新建目录

    06  |  Requests接口测试 - 图5

    3. 发送get请求

    06  |  Requests接口测试 - 图6

    4. 发送post请求

    06  |  Requests接口测试 - 图7

    5. 添加断言

    06  |  Requests接口测试 - 图8

    6. 关联token

    目的:fecshop项目在后台查询订单时需要先登录,将登录后token关联至查询订单请求中,可以使用如下设置公共变量的方法

  1. 点击postman右上角小齿轮添加一个环境
  2. 点击右上角下拉框切换到新建好的环境
  3. 在登录请求中的tests选项卡中增加提取token并设置变量的JS代码

    //将token提取出来并存到变量中
    var value = postman.getResponseHeader("Access-Token");
    //将上面的变量设置成环境变量
    pm.environment.set("Access-Token",value);
    
  4. 在查询订单请求中使用刚刚设置好的变量

06  |  Requests接口测试 - 图9

  1. 发送一次登录请求,点击右上角眼睛图标,查看环境变量是否已经获取到
  2. 发送查询订单请求,请求成功

四、项目实战

1. 新建项目结构

项    目
    |----case
        |----test_login.py
    |----report        
        |----xxx-report.html
    |----run.py

2. 编写测试脚本

写脚本要使用unittest单元测试框架

  • unittest单元测试框架可以方便用例的管理与维护
  • 同时可以为测试报告提供数据

登录 脚本:

import unittest
import requests

class Login(unittest.TestCase):
    def setUp(self):
        self.url = "http://appapi.fecshop.com/v1/account/login"
        self.login_data = {"username":"admin", "password":"admin123"}

    def tearDown(self):
        pass

    def test_01_login_success(self):
        """用户名密码正确,登录成功"""
        r = requests.post(self.url, data=self.login_data)  # 发送登录请求
        self.assertIn('success', r.text)  # 断言success是否在响应文本中

if __name__=='__main__':        # 程序入口
    unittest.main()

查询订单 脚本

import json
import unittest
import requests

class QueryOrderList(unittest.TestCase):  # 定义类继承unittest.TestCase
    def setUp(self):    # 初始化方法
        url = 'http://appapi.fecshop.com/v1/account/login'
        datas = {"username": "admin", "password": "admin123"}
        r = requests.post(url, data=datas)  # 发送登录请求
        result = json.loads(r.text)     # 保存响应数据并且转化为字典
        token = result["access-token"]  # 取出响应字典中token
        self.header = {'Access-Token':token}    # 构造一个包含token请求头

    def test_01_query_success(self):
        '''参数正确,查询订单列表成功'''
        url = 'http://appapi.fecshop.com/v1/article/list'
        # 发送查询订单列表请求,并且在该请求中带入构造好请求头
        r = requests.get(url, params={"page":1, "password":"admin123"}, headers=self.header)
        result = r.text     # 保存查询订单列表的响应数据
        message = result.find('success')    # 在响应数据中查找 success字符
        self.assertNotEqual(message, -1)    # 断言是否出现success

if __name__=="__main__":
    unittest.main()

3. 常见的断言方法

unittest框架常用的断言如下:

序号 断言方法 断言描述
1 ``assertEqual(a,b) 判断a是否与b相等,不相等则``fail
2 ``assertNotEqual(a,b) 判断a是否与b不相等,相等则``fail
3 ``assertIn(a,b) 判断a是否在b中,不在则``fail
4 ``assertNotIn(a,b) 判断a是否不在b中,在则``fail
5 ``assertTrue(a) 判断a是否为True,如False则fail
6 ``assertFalse(a) 判断a是否为False,如True则fail

4. 安装BeautifulReport

pip install beautifulreport

5. 编写一键执行脚本

import unittest
from BeautifulReport import BeautifulReport
import time

current_time = time.strftime("%Y-%m-%d-%H-%M-%S")  # 2020-09-27-17-35-10
file_name = current_time + "-report.html"  # 拼接报告文件名

if __name__ == '__main__':
    # 从case目录下去查找所有以test开头的py文件中的所有测试用例
    suite = unittest.defaultTestLoader.discover(".\case")
    # 实例化BR
    res = BeautifulReport(suite)
    # 调用类中的生成测试报告的方法
    res.report("Fecshop接口自动化测试", file_name, r".\report")

6.执行测试,生成报告

06  |  Requests接口测试 - 图10

7. 新建配置目录

项    目
    |----case
        |----test_login.py
    |----report
        |----xxx-report.html
    |----config                                      # 配置文件目录
        |----config.py                        # 配置文件
    |----run.py

config.py

# 定义一些常用配置
HOST = 'http://appapi.fecshop.com/v1'

8. 新增测试数据目录

项    目
    |----case
        |----test_login.py
    |----report
        |----xxx-report.html
    |----config
        |----config.py                        
    |----data                                 # 测试数据目录
        |----login_data.csv             # 测试数据文件
    |----run.py

login_data.csv
06  |  Requests接口测试 - 图11

9. 新增工具类目录

项    目
    |----case
        |----test_login.py
    |----report
        |----xxx-report.html
    |----config
        |----config.py                        
    |----data                                 
        |----login_data.csv   
   |----utils
        |----csv_reader.py            # 测试数据读取
        |----get_token.py            # 登录并 获取token
    |----run.py

csv_reader.py

import csv          # 导入csv模块

def get_data(filename):
    content = []        # 定义空列表
    with open(filename, 'r') as f:      # 打开文件
        reader = csv.DictReader(f)      # 实例化dictreader,得到一个reader对象
        for i in reader:                # 遍历reader
            content.append(i)           # 将读取的内容追加列表中
    # 返回列表
    return content

if __name__=='__main__':    # 测试本文件
    c = get_data('D:/201915/fecshop/data/login_data.csv')
    print(c)

get_token.py

import sys
sys.path.append('..')
import requests
import json
from config.config import *
from utils.csv_reader import get_data


def get_token_for_header():
    url = HOST + '/account/login'
    data = get_data(DATA_PATH+'/login_data.csv')
    r = requests.post(url, json.loads(data[0]['data']))
    result = json.loads(r.text)
    token = result['access-token']
    header = {'Access-Token': token}
    return header

10. 调试用例

test_login.py

import json
import os,sys
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
import unittest
import requests
from config.config import *
from utils.csv_reader import get_data

file_path = DATA_PATH + '/login_data.csv'        # 组织登录数据文件存放路径
login_data = get_data(file_path)                     # 拿到登录数据
class Login(unittest.TestCase):
    def setUp(self):            # 初始化方法,在每个用例执行前都会执行
        self.url = HOST + '/account/login'
        # self.datas ={"username":"admin", "password":"admin123"}

    def tearDown(self):         # 结束方法,在每个用例结束后都会执行
        pass

    def test_01_login_success(self):  # 用例名称必须以test开头,编号可以使用例按照编号顺序执行
        '''用户名密码正确,登录成功'''
        datas = login_data[0]['data']       # 从login_data中取出第一行数据中的data
        r = requests.post(self.url, data=json.loads(datas)) # 发送登录请求,注意将str转成dict
        result = r.text                         # 保存响应信息
        status = result.find(login_data[0]['result'])         # 在响应中查找success
        self.assertNotEqual(status, -1)        # assertNotEqual(a, b)断言a,b不相等

    def test_02_login_fail(self):
        '''用户名正确,密码错误,登录失败'''
        data = login_data[1]['data']
        print(data)

if __name__=='__main__':        # 程序入口
    unittest.main()

五、附注

接口文档地址

https://documenter.getpostman.com/view/4922773/SVn2QGHo