一、简介
本文从一个简单的登录接口测试入手,一步步调整优化接口调用姿势;然后简单讨论了一下接口测试框架的要点;最后介绍了一些通用的接口测试框架设计思路。期望读者可以通过本文对接口自动化测试有一个大致的了解。
二、引言
为什么要做接口自动化测试?
在当前互联网产品迭代频繁的背景下,回归测试的时间越来越少,很难在每个迭代都对所有功能做完整回归。但接口自动化测试因其实现简单、维护成本低,容易提高覆盖率等特点,越来越受重视。
本文介绍了基于 Python 语言进行接口自动化测试的完整流程。内容涵盖环境搭建、Flask 模拟接口编写、使用 Requests 和 Unittest 编写基础测试用例,并逐步演示了如何封装接口调用、利用 Session 对象管理 Cookie 状态以及添加日志输出。文章最后总结了项目化实践中的最佳方案,包括配置分离、CI/CD 集成及数据驱动测试模式,帮助读者构建可维护的自动化测试框架。

本文从一个简单的登录接口测试入手,一步步调整优化接口调用姿势;然后简单讨论了一下接口测试框架的要点;最后介绍了一些通用的接口测试框架设计思路。期望读者可以通过本文对接口自动化测试有一个大致的了解。
在当前互联网产品迭代频繁的背景下,回归测试的时间越来越少,很难在每个迭代都对所有功能做完整回归。但接口自动化测试因其实现简单、维护成本低,容易提高覆盖率等特点,越来越受重视。
使用 requests + unittest 很容易实现接口自动化测试,而且 requests 的 api 已经非常人性化,非常简单。但通过封装以后(特别是针对公司内特定接口),再加上对一些常用工具的封装,可以进一步提高业务脚本编写效率。
确保本机已安装 python 3.x 以上版本,然后安装如下库:
pip install flask
pip install requests
后面我们会使用 flask 写一个用来测试的接口,使用 requests 去测试。
下面使用 flask 实现两个 http 接口,一个登录,另外一个查询详情,但需要登录后才可以。新建一个 demo.py 文件,把下面代码 copy 进去,然后保存、关闭。
#!/usr/bin/python
# coding=utf-8
from 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)
最后执行如下命令:
python demo.py
响应如下:
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat...
大家可以看到服务已经起来了。
| 参数名称 | 参数类型 | 参数说明 | | :————: | :——-: | :——: | | username | String | 登录名称 | | password | String | 登录密码 |
响应信息: | 参数名称 | 参数类型 | 参数说明 | | :————: | :——-: | :——: | | code | Integer | 结果 code | | msg | String | 结果信息 |
请求 url: /info
请求方法: get
请求 cookies: session
响应信息: | 参数名称 | 参数类型 | 参数说明 | | :————: | :——-: | :——: | | code | Integer | 结果 code | | msg | String | 结果信息 | | data | String | 数据信息 |
#!/usr/bin/python
# coding=utf-8
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'
写完这个测试登录脚本,你或许会发现,在整个项目的测试过程,登录可能不止用到一次,如果每次都这么写,会不会太冗余了?对,确实太冗余了,下面做一下简单的封装,把登录接口的调用封装到一个方法里,把调用参数暴漏出来。
#!/usr/bin/python
# coding=utf-8
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'
OK,在这一个版本中,我们不但在把登录接口的调用封装成了一个实例方法,实现了复用,而且还把 host(self.base_url)提取了出来。
但问题又来了,登录之后,登录接口的 http 响应会把 session 以 cookie 的形式 set 到客户端,之后的接口都会使用此 session 去请求。还有,就是在接口调用过程中,希望可以把日志打印出来,以便调试或者出错时查看。
使用 requests 库里的同一个 Session 对象 (它也会在同一个 Session 实例发出的所有请求之间保持 cookie),即可解决上面的问题。
#!/usr/bin/python
# coding=utf-8
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(dict(self.session.cookies))
print(u'\n4、响应:')
pprint(response)
return response
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):
"""
测试获取详情信息
"""
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 信息),可不可以只需定义必须的信息,然后把其它共性的东西都封装起来呢,统一放到一个地方去管理?
在实际项目中,为了进一步提升可维护性,通常会将配置信息(如 Base URL、超时时间、重试策略等)抽取到配置文件(如 YAML 或 JSON)中,并通过环境变量进行区分(开发、测试、生产)。此外,结合 CI/CD 工具(如 Jenkins、GitLab CI)可以实现代码提交后自动触发接口测试,及时发现回归问题。
对于更复杂的场景,还可以引入断言库(如 pytest-assertion)、报告生成工具(如 Allure)以及数据驱动测试(Data-Driven Testing)模式,将测试数据与逻辑分离,从而构建更加健壮和高效的自动化测试体系。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online