返回测试开发学习路线测试开发的工作闭环
一条回归数据从文件到报告
Test Development / Tutorial 01
测试开发编程基础教程
从测试工程师的视角出发,掌握把业务规则写成可运行脚本的编程能力:数据处理、HTTP 请求、数据库校验、日志留痕,最终串成一个订单服务回归脚本。
8 个章节贯穿案例:订单服务回归Python + requests + SQL
01
为什么测试工程师需要编程能力
从点到线发现问题用例执行或线上反馈
设计验证把场景翻译成逻辑
编码脚本与数据
自动执行一键回归
持续反馈日志与报告
普通测试与测试开发的区别
| 环节 | 普通测试 | 测试开发 |
|---|---|---|
| 发现问题 | 执行用例,记录缺陷 | 执行用例,记录缺陷 |
| 定位原因 | 把现象写进 Bug 单 | 判断是数据、接口还是环境问题 |
| 设计验证 | 等待修复后手工重测 | 把场景翻译成可重复执行的验证逻辑 |
| 执行回归 | 手工重跑,依赖人的记忆 | 脚本自动执行,一键回归 |
| 持续反馈 | Bug 单闭环 | 日志 + 报告,失败可定位 |
回到贯穿案例:订单服务回归
接口越来越多,每次发版都要手工点一遍“下单、查询、取消”。订单服务回归脚本把这件事变成:自动读取一批订单数据 → 调用下单接口 → 校验响应 → 核对数据库 → 输出报告。人只负责看结论,机器负责重复劳动。
会写代码不是目的,把一条业务规则变成“今天能跑、下个月还能跑、失败了能定位”的自动回归,才是测试开发要解决的工程问题。
02
测试代码与业务代码的区别
读者不同业务代码与测试代码的关注点
| 维度 | 业务代码 | 测试代码 |
|---|---|---|
| 关注点 | 功能、性能、稳定性 | 可读性、可维护性、可扩展性、可复用性 |
| 主要读者 | 开发者和编译器 | 测试工程师自己和团队 |
| 运行频率 | 部署后长期运行 | 每次回归都运行,一次编写多次执行 |
| 失败含义 | 系统出了 Bug | 业务与预期不一致,要能快速定位 |
| 演进方式 | 随功能迭代修改 | 随用例集沉淀,持续扩充 |
为什么测试代码最看重可读性
业务代码要扛住高并发,测试代码要扛住“三个月后还有人维护”。下一轮回归跑你的脚本的,可能是另一位测试同事:他不需要知道你调了什么框架,但他必须能一眼看出——数据从哪来、接口调哪个、断言断什么、失败看哪里。
业务代码追求“把功能做对”,测试代码追求“把验证说清楚”。同样一段逻辑,写在测试里要优先让下一步骤的人三分钟读懂。
03
数据与文件处理:测试数据的来源
JSON / CSV / Exceltest_data/orders.json
[
{
"orderId": "SO-20240301-001",
"skuId": "SKU-1001",
"quantity": 2,
"expectedStatus": "PENDING_PAYMENT",
"payAmount": "199.80"
},
{
"orderId": "SO-20240301-002",
"skuId": "SKU-1002",
"quantity": 1,
"expectedStatus": "PAID",
"payAmount": "59.90"
}
]读取与断言
import json
import csv
def load_orders_from_json(path):
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def load_orders_from_csv(path):
with open(path, "r", encoding="utf-8") as f:
return list(csv.DictReader(f))
def assert_order_status(order, expected_status):
assert order["status"] == expected_status, (
"订单 %s 状态异常: 期望 %s, 实际 %s"
% (order["orderId"], expected_status, order["status"])
)测试数据的三条纪律
- 数据独立成文件,不写死在断言里。
- 金额用字符串或 Decimal 存储,避免浮点误差。
- 每条数据都要能说清它验证什么:正常、边界还是异常。
Excel 场景用 pandas 一行读取:pandas.read_excel(path, dtype=str) 再转成字典列表。注意把金额读成字符串而不是浮点数,否则 199.80 会变成 199.8。
04
HTTP 请求与响应:与接口对话
requests封装下单请求
import requests
BASE_URL = "https://api.example.com"
TOKEN = "test-token-xxx"
def create_order(payload):
response = requests.post(
BASE_URL + "/orders",
json=payload,
headers={
"Authorization": "Bearer " + TOKEN,
"Content-Type": "application/json",
},
timeout=10,
)
return response
def assert_business_success(response):
assert response.status_code == 200, "HTTP 状态异常: %s" % response.status_code
body = response.json()
assert body["code"] == "SUCCESS", "业务码异常: %s" % body
assert body["data"]["orderId"], "响应缺少订单号"三层检查,缺一不可
| 检查项 | 含义 | 例子 |
|---|---|---|
| HTTP 状态码 | 网络与服务器处理结果 | 200 / 500 |
| 业务 code | 业务规则是否通过 | SUCCESS / ORDER_CLOSED |
| 响应体 data | 真正的业务数据 | orderId、payAmount、status |
HTTP 200 只说明“服务器处理了这个请求”,不代表下单成功。断言必须分层:先看 HTTP 状态,再看业务 code,最后取 data 里的业务字段。
05
数据库操作:用另一只眼看订单
交叉校验查询订单表
import sqlite3
DB_PATH = "orders.db"
def fetch_order(order_id):
conn = sqlite3.connect(DB_PATH)
try:
cur = conn.cursor()
cur.execute(
"SELECT order_id, status, pay_amount FROM orders WHERE order_id = ?",
(order_id,),
)
row = cur.fetchone()
if row is None:
return None
return {"orderId": row[0], "status": row[1], "payAmount": str(row[2])}
finally:
conn.close()接口返回与数据库交叉校验
def cross_check(api_order, db_order):
assert db_order is not None, "订单未写入数据库: %s" % api_order["orderId"]
assert db_order["status"] == api_order["status"], (
"状态不一致: 接口 %s vs 数据库 %s"
% (api_order["status"], db_order["status"])
)
assert db_order["payAmount"] == api_order["payAmount"], (
"金额不一致: 接口 %s vs 数据库 %s"
% (api_order["payAmount"], db_order["payAmount"])
)为什么要交叉校验
接口层的返回是系统自己“说”的,数据库落库才是业务真正发生的证据。接口说下单成功、数据库却没有订单——这种问题只测接口永远发现不了,交叉校验能把它抓出来。
测试环境连库只做只读查询;如果确实要写数据,优先在事务里回滚。参数化 SQL 一律用占位符 ?,永远不要字符串拼接,防止注入也避免引号转义。
06
日志与异常:失败时要能三分钟定位
留痕统一日志配置
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s [%(name)s] %(message)s",
)
logger = logging.getLogger("order-regression")
def log_api_call(action, payload, response):
logger.info(
"action=%s payload=%s status=%s code=%s",
action,
payload,
response.status_code,
response.json().get("code"),
)异常上下文要带订单号
import requests
try:
response = create_order(payload)
assert_business_success(response)
except AssertionError:
logger.error(
"订单 %s 断言失败, 响应体=%s",
payload.get("orderId"),
response.text,
)
raise
except requests.RequestException as exc:
logger.error("订单 %s 请求异常: %s", payload.get("orderId"), exc)
raise一次失败至少留下这些
- 用例名与测试数据编号。
- 订单号、商品号等业务主键。
- 请求方法、URL 与请求体。
- HTTP 状态、业务 code 与响应体。
- 发生时间与环境标识;token 和密码绝不进日志。
日志不是写给别人看的装饰,是失败时唯一的“事故现场”。先设计好“失败时能看到什么”,再写断言,比事后补日志高效得多。
07
串成一个小工具:订单服务回归脚本
可运行读取数据orders.json
调接口POST /orders
校验响应HTTP + code
核对数据库交叉校验
输出报告PASS / FAIL
order_regression.py
import json
import logging
import sqlite3
import requests
BASE_URL = "https://api.example.com"
TOKEN = "test-token-xxx"
DB_PATH = "orders.db"
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s [%(name)s] %(message)s",
)
logger = logging.getLogger("order-regression")
def load_orders(path):
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def create_order(payload):
return requests.post(
BASE_URL + "/orders",
json=payload,
headers={
"Authorization": "Bearer " + TOKEN,
"Content-Type": "application/json",
},
timeout=10,
)
def fetch_order(order_id):
conn = sqlite3.connect(DB_PATH)
try:
cur = conn.cursor()
cur.execute(
"SELECT status, pay_amount FROM orders WHERE order_id = ?",
(order_id,),
)
row = cur.fetchone()
return {"status": row[0], "payAmount": str(row[1])} if row else None
finally:
conn.close()
def verify_order(payload, response):
assert response.status_code == 200, "HTTP 状态: %s" % response.status_code
body = response.json()
assert body["code"] == "SUCCESS", "业务码异常: %s" % body
order_id = body["data"]["orderId"]
db_order = fetch_order(order_id)
assert db_order is not None, "订单未写入数据库: %s" % order_id
assert db_order["status"] == body["data"]["status"], (
"状态不一致: 接口 %s vs 数据库 %s"
% (body["data"]["status"], db_order["status"])
)
return order_id
def run_regression(path):
total = 0
passed = 0
for payload in load_orders(path):
total += 1
try:
response = create_order(payload)
order_id = verify_order(payload, response)
logger.info("PASS order_id=%s", order_id)
passed += 1
except Exception:
logger.exception("FAIL payload=%s", payload)
logger.info("报告: 共 %s 条, 通过 %s 条", total, passed)
if __name__ == "__main__":
run_regression("test_data/orders.json")运行步骤
- 安装依赖:pip install requests。
- 准备 test_data/orders.json,放 3 到 5 条订单数据。
- 执行 python order_regression.py。
- 观察日志中的 PASS / FAIL 与最终统计。
- 故意把一条数据的状态改错,确认失败信息能定位到具体订单。
这就是“发现一条 Bug”到“持续回归”的最小闭环:读数据、调接口、做断言、留证据。脚本先跑通,后面可以换 pytest、加参数化、接 CI——骨架不变。
08
练习与检查
动手练习:完成订单服务回归脚本
- 准备 5 条订单测试数据(含 1 条异常状态),存成 JSON。
- 写函数读取数据并逐条断言期望状态。
- 用 requests 封装下单接口,正确处理 token。
- 把 HTTP 状态、业务 code、响应体 data 分开断言。
- 查询数据库,校验接口返回与落库数据一致。
- 每条用例的前后打印请求摘要与响应摘要。
- 故意制造一条失败,确认日志能定位到具体订单号。
- 把以上整理成 order_regression.py 一个脚本并跑通。
数据可靠
- 测试数据独立成文件
- 金额用字符串存储
- 正常与异常数据并存
- 每条数据说明验证意图
校验可信
- HTTP 状态与业务 code 分开断言
- 接口结果与数据库交叉校验
- 失败信息包含订单号
- 断言失败信息可读
工程可用
- 脚本可重复运行
- 日志可追溯定位
- token 不进日志
- 报告统计通过率
你已经能把一条业务规则写成可运行的回归脚本。下一步把脚本升级成真正的自动化测试工程。
回到测试开发学习路线