1. 从手工到自动化:Python接口测试框架实战
作为一名从功能测试转型接口测试的工程师,我深刻理解手工测试的痛点——重复劳动多、效率低、容易遗漏边界条件。去年我们团队开始全面转向接口自动化测试,最初使用Postman和JMeter进行手工测试,后来有同事用Java搭建了自动化框架。但作为一个Python爱好者,我决定用Python+Requests打造更适合自己的接口自动化测试框架。
这个框架经过半年多的实际项目检验,已经稳定运行并大幅提升了我们的测试效率。今天我就把这个框架的设计思路和实现细节完整分享给大家,特别适合刚接触接口自动化测试的同行参考。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 框架整体设计与核心模块
2.1 框架目录结构解析
一个良好的目录结构是自动化框架的基础。经过多次迭代,我的框架最终形成了以下结构:
code复制├── common/ # 公共方法库
│ ├── Log.py # 日志模块
│ ├── common.py # 通用工具方法
│ └── configHttp.py # HTTP请求配置
├── config/ # 配置文件
│ └── config.ini # 全局配置文件
├── testCase/ # 测试用例
│ ├── test_login.py
│ └── test_order.py
├── testFile/ # 测试数据文件
│ ├── testCases.xlsx
│ └── SQL.xml
├── result/ # 测试结果(运行时生成)
│ └── 20230815_143000/
│ ├── output.log
│ └── report.html
├── caselist.txt # 用例执行清单
└── runAll.py # 主执行入口
这种结构实现了业务逻辑与测试数据的分离,修改测试用例或SQL语句时无需改动代码,大大提高了维护性。
2.2 配置文件设计技巧
在config.ini中,我将所有环境相关的配置集中管理:
ini复制[DATABASE]
host = 192.168.1.100
username = tester
password = Test@1234
port = 3306
database = test_db
[HTTP]
baseurl = http://api.example.com
port = 8080
timeout = 5.0
[EMAIL]
mail_host = smtp.163.com
mail_user = tester@163.com
mail_pass = Email@1234
mail_port = 25
sender = tester@163.com
receiver = dev1@example.com/dev2@example.com
on_off = 1 # 邮件开关
实际项目中建议将密码等敏感信息进行加密存储,这里为了演示使用明文
通过专门的readConfig.py读取配置:
python复制import configparser
class ReadConfig:
def __init__(self):
self.cf = configparser.ConfigParser()
self.cf.read('config/config.ini')
def get_db(self, name):
return self.cf.get("DATABASE", name)
def get_http(self, name):
return self.cf.get("HTTP", name)
def get_email(self, name):
return self.cf.get("EMAIL", name)
这种设计让环境切换变得非常简单,只需修改配置文件即可适配不同测试环境。
3. 核心模块实现细节
3.1 日志模块的线程安全实现
日志是排查问题的关键,我设计了一个线程安全的日志模块:
python复制import logging
from threading import Lock
class Log:
__instance = None
__lock = Lock()
@classmethod
def get_logger(cls):
if cls.__instance is None:
with cls.__lock:
if cls.__instance is None:
cls.__instance = cls.__create_logger()
return cls.__instance
@classmethod
def __create_logger(cls):
logger = logging.getLogger("api_test")
logger.setLevel(logging.INFO)
# 控制台输出
ch = logging.StreamHandler()
ch.setFormatter(logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
))
logger.addHandler(ch)
# 文件输出
fh = logging.FileHandler('output.log')
fh.setFormatter(logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
))
logger.addHandler(fh)
return logger
关键点:
- 使用双重检查锁实现线程安全的单例模式
- 同时输出到控制台和文件
- 统一的时间格式和日志格式
3.2 HTTP请求封装的艺术
在configHttp.py中,我对Requests进行了二次封装:
python复制import requests
from common.Log import Log
class HttpRequest:
def __init__(self):
self.log = Log.get_logger()
self.session = requests.Session()
self.timeout = 10
def request(self, method, url, **kwargs):
try:
resp = self.session.request(
method,
url,
timeout=self.timeout,
**kwargs
)
resp.raise_for_status()
return resp
except requests.exceptions.RequestException as e:
self.log.error(f"请求失败: {str(e)}")
raise
def get(self, url, params=None, **kwargs):
return self.request('GET', url, params=params, **kwargs)
def post(self, url, data=None, json=None, **kwargs):
return self.request('POST', url, data=data, json=json, **kwargs)
这个封装实现了:
- 自动会话管理
- 统一的超时设置
- 自动异常处理和日志记录
- 支持GET/POST等多种请求方式
3.3 测试数据管理方案
我采用Excel+XML管理测试数据:
Excel测试用例示例:
| case_name | method | url | params | expected |
|---|---|---|---|---|
| 登录成功 | POST | /login | code=200 | |
| 登录失败 | POST | /login | code=401 |
读取Excel的通用方法:
python复制from openpyxl import load_workbook
def get_testcases(file_path):
wb = load_workbook(file_path)
ws = wb.active
cases = []
for row in ws.iter_rows(min_row=2, values_only=True):
case = {
'name': row[0],
'method': row[1],
'url': row[2],
'params': eval(row[3]) if row[3] else None,
'expected': row[4]
}
cases.append(case)
return cases
SQL语句XML管理:
xml复制<database name="test_db">
<table name="user">
<sql id="get_user">SELECT * FROM user WHERE id=?</sql>
<sql id="delete_user">DELETE FROM user WHERE id=?</sql>
</table>
</database>
XML解析方法:
python复制from xml.etree import ElementTree
def get_sql(db_name, table_name, sql_id):
tree = ElementTree.parse('testFile/SQL.xml')
sql = tree.find(f".//database[@name='{db_name}']/table[@name='{table_name}']/sql[@id='{sql_id}']")
return sql.text if sql is not None else None
4. 测试用例设计与执行
4.1 测试用例编写规范
基于unittest的测试用例示例:
python复制import unittest
from common.HttpRequest import HttpRequest
from common.Log import Log
class TestLogin(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.http = HttpRequest()
cls.log = Log.get_logger()
def test_login_success(self):
"""测试登录成功场景"""
url = "/api/login"
data = {"username": "test", "password": "123456"}
resp = self.http.post(url, json=data)
self.assertEqual(200, resp.status_code)
self.assertIn("token", resp.json())
def test_login_failed(self):
"""测试密码错误场景"""
url = "/api/login"
data = {"username": "test", "password": "wrong"}
resp = self.http.post(url, json=data)
self.assertEqual(401, resp.status_code)
self.assertEqual("Invalid credentials", resp.json()["message"])
关键点:
- 每个测试类对应一个业务模块
- 每个测试方法对应一个测试场景
- 清晰的测试描述
- 完整的断言验证
4.2 测试执行与报告生成
主执行文件runAll.py的核心逻辑:
python复制import unittest
import HTMLTestRunner
from common.Log import Log
def run_tests():
logger = Log.get_logger()
# 发现所有测试用例
discover = unittest.defaultTestLoader.discover(
start_dir='testCase',
pattern='test_*.py'
)
# 生成HTML报告
with open('result/report.html', 'wb') as f:
runner = HTMLTestRunner.HTMLTestRunner(
stream=f,
title='接口测试报告',
description='测试结果详情'
)
result = runner.run(discover)
# 统计测试结果
logger.info(f"测试通过率: {result.success_rate:.2%}")
logger.info(f"通过用例数: {result.success_count}")
logger.info(f"失败用例数: {result.failure_count}")
return result.wasSuccessful()
生成的HTML报告包含:
- 用例执行概况
- 详细的通过/失败统计
- 失败用例的错误堆栈
- 执行时间等元信息
5. 常见问题与解决方案
5.1 接口依赖问题
在测试订单相关接口时,需要先获取用户token。我的解决方案是:
python复制class TestOrder(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.token = cls.get_token()
@classmethod
def get_token(cls):
resp = HttpRequest().post("/api/login", json={
"username": "test",
"password": "123456"
})
return resp.json()["token"]
def test_create_order(self):
headers = {"Authorization": f"Bearer {self.token}"}
resp = HttpRequest().post(
"/api/orders",
json={"product_id": 1, "quantity": 2},
headers=headers
)
self.assertEqual(201, resp.status_code)
5.2 测试数据清理
自动化测试会产生测试数据,需要在测试完成后清理:
python复制def tearDown(self):
# 删除测试创建的用户
db = MyDB()
db.execute("DELETE FROM users WHERE username = 'test_user'")
db.close()
5.3 接口性能问题
对于慢接口,可以增加超时控制和性能断言:
python复制def test_api_performance(self):
start_time = time.time()
resp = self.http.get("/api/products")
elapsed = time.time() - start_time
self.assertEqual(200, resp.status_code)
self.assertLess(elapsed, 1.0, "接口响应时间超过1秒")
6. 框架扩展与优化方向
6.1 增加Mock服务
对于依赖第三方接口的场景,可以使用unittest.mock:
python复制from unittest.mock import patch
class TestPayment(unittest.TestCase):
@patch('common.HttpRequest.HttpRequest.post')
def test_payment_success(self, mock_post):
# 模拟支付接口返回成功
mock_post.return_value = Mock(status_code=200, json=lambda: {
"status": "success",
"transaction_id": "12345"
})
resp = pay_order(1001)
self.assertTrue(resp["status"], "success")
6.2 集成持续集成
在Jenkins中配置自动化测试任务:
groovy复制pipeline {
agent any
stages {
stage('Test') {
steps {
sh 'python runAll.py'
}
post {
always {
junit 'result/*.xml'
archiveArtifacts 'result/*.html'
}
}
}
}
}
6.3 可视化监控
使用Prometheus+Grafana监控测试结果:
python复制from prometheus_client import Counter
TEST_CASES = Counter(
'test_cases_total',
'Total test cases run',
['status']
)
class TestResult(unittest.TestResult):
def addSuccess(self, test):
TEST_CASES.labels(status='success').inc()
super().addSuccess(test)
def addFailure(self, test, err):
TEST_CASES.labels(status='failure').inc()
super().addFailure(test, err)
这套框架经过多个项目的实战检验,将我们的接口测试效率提升了3倍以上。最大的收获不是技术本身,而是通过自动化测试建立的信心——每次发版前跑一遍全量用例,就能确保核心功能不受影响。
