共计 2769 个字符,预计需要花费 7 分钟才能阅读完成。
背景痛点:新手常踩的坑
刚接触测试脚本开发时,很容易陷入以下困境:

- 代码冗余 :每个测试用例重复编写相似的初始化代码,修改时要到处找
- 脆弱性高 :一个环境变量变化就能让整个测试套件崩掉
- 排查困难 :测试失败时只能看到
AssertionError,不知道具体失败原因 - 维护噩梦 :三个月后回头看自己写的代码,完全理不清逻辑
比如这样的典型反模式:
def test_login_success():
# 硬编码测试数据
user = {"username": "test1", "password": "123456"}
# 混合业务逻辑和断言
response = requests.post("http://api/login", json=user)
assert response.status_code == 200
# 重复 90% 相似代码的测试用例
def test_login_wrong_password():
user = {"username": "test1", "password": "wrong"}
response = requests.post("http://api/login", json=user)
assert response.status_code == 401
设计原则:好脚本的三大支柱
- 模块化 :像搭积木一样组织代码,比如:
- 将测试数据生成独立为模块
- 把通用操作封装成工具函数
-
业务断言与测试逻辑分离
-
可复用性 :
- 通过 fixture 共享测试上下文
- 使用参数化减少重复用例
-
设计可组合的测试步骤
-
可维护性 :
- 清晰的失败信息(哪个字段不符合预期)
- 合理的日志记录(测试执行轨迹)
- 版本友好的测试数据(避免绝对路径)
核心实现:Python 测试框架示例
基础框架结构
# test_framework.py
import pytest
from typing import Dict, Any
class TestContext:
"""测试上下文管理器"""
def __init__(self, config: Dict[str, Any]):
self.config = config
self._setup()
def _setup(self):
"""初始化测试环境"""
self.api_client = APIClient(self.config["base_url"])
def cleanup(self):
"""测试后清理"""
self.api_client.close()
@pytest.fixture(scope="module")
def test_context(config_loader):
"""模块级测试夹具"""
ctx = TestContext(config_loader())
yield ctx
ctx.cleanup()
测试数据管理(YAML 示例)
# configs/test_env.yaml
base_url: "http://api.staging.example.com"
auth:
admin_user:
username: "admin@example.com"
password: "!SecurePassword123"
# conftest.py
import yaml
import pytest
from pathlib import Path
@pytest.fixture
def config_loader():
"""加载环境配置的夹具"""
def _load(env="test_env"):
config_path = Path(__file__).parent / f"configs/{env}.yaml"
with open(config_path) as f:
return yaml.safe_load(f)
return _load
异常处理与断言
def test_user_login(test_context):
"""登录测试带异常处理"""
try:
# 从上下文获取配置
creds = test_context.config["auth"]["admin_user"]
# 执行测试动作
resp = test_context.api_client.post(
"/login",
json={"email": creds["username"], "password": creds["password"]}
)
# 多维度断言
assert resp.status_code == 200, "HTTP 状态码异常"
assert "access_token" in resp.json(), "响应缺少 access_token"
assert len(resp.json()["access_token"]) > 32, "token 长度不足"
except Exception as e:
pytest.fail(f"测试意外失败: {str(e)}")
进阶技巧
参数化测试
@pytest.mark.parametrize("username,password,expected_code", [("valid@user.com", "correct_pw", 200),
("valid@user.com", "wrong_pw", 401),
("invalid_format", "any_pw", 400),
])
def test_login_combinations(test_context, username, password, expected_code):
resp = test_context.api_client.post("/login", json={
"email": username,
"password": password
})
assert resp.status_code == expected_code
测试报告生成
安装插件:
pip install pytest-html
运行测试:
pytest --html=report.html --self-contained-html
CI 环境注意事项
- 使用环境变量覆盖敏感配置
- 设置合理的超时时间
- 处理测试依赖的服务状态
避坑指南
-
硬编码敏感信息
× 错误做法:在代码中写死密码
√ 解决方案:使用环境变量或加密配置 -
过度依赖 UI 自动化
× 错误做法:所有测试都通过 GUI 操作
√ 解决方案:优先测试 API/Service 层 -
忽略测试隔离
× 错误做法:测试用例之间有状态依赖
√ 解决方案:每个测试前重置数据库 -
断言粒度太粗
× 错误做法:只检查 HTTP 状态码
√ 解决方案:验证关键业务字段 -
没有失败分析
× 错误做法:直接抛 AssertionError
√ 解决方案:记录请求 / 响应到日志
实践建议
- 立即优化 :把重复的测试代码抽取到 fixture 中
- 明日计划 :为现有测试添加详细的失败信息
- 下周目标 :实现参数化测试覆盖边界值
思考题
- 如何处理测试脚本中需要的第三方服务依赖?(如支付网关)
- 当测试用例执行时间从 1 秒增加到 10 秒时,应该如何优化?
- 如何设计测试数据才能在多人协作时避免冲突?
正文完
