“新功能上线又出Bug了,用户投诉量激增!”
一个简单的API接口,在生产环境中崩溃了,影响了数千用户。
这本应是在测试阶段,就被发现的低级错误。
你们是不是也经常这样?
- 每次修改代码都提心吊胆,生怕搞坏已有功能?
- 手动测试耗时耗力,但却依然无法覆盖所有场景?
- 代码越多,越不敢重构,因为不知道会影响到哪里?
写代码不写测试,就像开车不系安全带,一个Bug就能让你“翻车”
今天,我就为你揭秘专业开发者都在用的 “代码安全带”——自动化测试
为什么需要自动化测试?
想象一下,你正在开发一个 TODO 应用,用户可以通过 API 添加、查看、更新和删除任务。没有自动化测试会怎样?
你每完成一个功能,都需要:
- 启动服务
- 打开Postman或浏览器
- 手动调用每个接口
- 检查每个响应是否正确
- 将重复这个过程几十次、上百次...
这样的手动测试的最大问题不是效率低,而是不可靠。 人总会疏忽,尤其是在重复性工作中。
而自动化测试,就像为你的代码请了一位24小时不休息的 “质量检查员”。
手动测试、单元测试、集成测试
01、手动测试:你正在做的低效工作
人工验证功能。它简单直观,但不可持续。
02、单元测试:代码的 “分子级检查
单元测试是在隔离状态下,对代码的最小可测试单元进行的测试。
简单说:一个函数、一个类方法,就是你要测试的“单元”。
# 被测函数:计算两个数的和
def add(a, b):
return a + b
# 单元测试:验证这个函数是否正确
def test_add():
assert add(2, 3) == 5 # 测试正常情况
assert add(-1, 1) == 0 # 测试负数
assert add(0, 0) == 0 # 测试边界情况
优势:
- 快速执行(毫秒级)
- 精确锁定问题位置
- 支持频繁重构
- 作为代码文档
03、集成测试:验证“零件组装”是否正常
集成测试关注不同组件之间的交互是否正确。
比如,测试一个FastAPI 接口:
- 数据库连接正常吗?
- 身份验证有效吗?
- 数据处理逻辑正确吗?
# 集成测试示例:测试TODO应用的API端点
def test_create_todo(client):
response = client.post("/todos/", json={"title": "学习测试"})
assert response.status_code == 200
assert response.json()["title"] == "学习测试"
关键区别:
- 单元测试:测试单个零件
- 集成测试:测试组装后的机器
Pytest:Python 测试的 “瑞士军刀”
Pytest 凭借简洁和强大脱颖而出,目前是最受欢迎的 Python 测试框架。
01、为什么选择Pytest?
- 极简语法:用
assert语句即可,无需记住复杂API - 智能发现:自动发现并运行测试文件
- 强大扩展:丰富插件生态
- 详细报告:失败时提供清晰的问题诊断
02、安装与基础使用
# 安装
pip install pytest -i https://pypi.tuna.tsinghua.edu.cn/simple
# 运行所有测试
pytest
# 运行特定测试文件
pytest test_example.py
从零开始,编写你的第一个测试
01、创建测试目录
在你的FastAPI项目根目录下
# 创建测试目录
mkdir tests
# 创建初始化文件(让Python将其视为模块)
touch tests/__init__.py
# 创建测试文件(命名约定:test_*.py)
touch tests/test_example.py
02、编写第一个断言测试
在tests/test_example.py 文件
def test_equal_or_not_equal():
assert 3 == 3
03、运行测试
进入终端,在项目根目录下运行
(.venv) wangerge_notes: TodoApp$ pytest
====================== test session starts ======================
platform darwin -- Python 3.12.8, pytest-9.0.2, pluggy-1.6.0
rootdir: /Users/wt/Course/fastapi-course/code/Chapter_14/TodoApp
plugins: anyio-4.12.0
collected 1 item
test/test_example.py . [100%]
====================== 1 passed in 0.14s ======================
(.venv) wangerge_notes: TodoApp$
再看下失败的情况,先修改 tests/test_example.py 文件
def test_equal_or_not_equal():
assert 3 == 2
进入终端,在项目根目录下运行
(.venv) wangerge_notes: TodoApp$ pytest
====================== test session starts ======================
platform darwin -- Python 3.12.8, pytest-9.0.2, pluggy-1.6.0
rootdir: /Users/wangtao/Course/fastapi-course/code/Chapter_14/TodoApp
plugins: anyio-4.12.0
collected 1 item
test/test_example.py F [100%]
====================== FAILURES ======================
______________________ test_equal_or_not_equal ______________________
def test_equal_or_not_equal():
> assert 3 == 2
E assert 3 == 2
test/test_example.py:10: AssertionError
====================== short test summary info ======================
FAILED test/test_example.py::test_equal_or_not_equal - assert 3 == 2
====================== 1 failed in 0.28s ======================
(.venv) wangerge_notes: TodoApp$
基础的断言测试
在tests/test_example.py 文件
def test_integer():
assert 3 == 3
def test_is_instance():
assert isinstance("this is a string", str)
assert isinstance("10", str)
def test_boolean():
validated = True
assert validated is True
assert ("hello" == "world") is False
def test_type():
assert type("hello" is str)
assert type("world" is not str)
def test_greater_and_less_than():
assert 7<3
assert 4<10
def test_list():
num_list = [1, 2, 3, 4, 5]
any_list = [False, False]
assert 1 in num_list
assert 7 not in num_list
assert all(num_list)
assert any(any_list)
为数据类编写单元测试
让我们创建一个学生类,并为其编写测试:
class Student:
def __init__(self, first_name: str, last_name: str, major: str, years: int):
self.first_name = first_name
self.last_name = last_name
self.major = major
self.years = years
def test_student_initialization():
student = Student("二哥", "王", "计算机科学", 3)
assert student.first_name == "二哥", "名字应该是二哥"
assert student.last_name == "王", "姓氏应该是王"
assert student.major == "计算机科学", "专业应该是计算机科学"
assert student.years == 3, "年级应该是3"
在终端执行测试:
(.venv) wangerge_notes: TodoApp$ pytest
====================== test session starts ======================
collected 1 item
test/test_example.py . [100%]
====================== 1 passed in 0.07s ======================
(.venv) wangerge_notes: TodoApp$
使用 Fixture 重用测试数据
如果,你发现每个测试函数,都要重复创建相同的数据对象,可以使用Fixture来优化。
Fixture的作用:提供可重用的测试数据或环境设置。
import pytest
@pytest.fixture
def default_employee():
return Student("二哥", "王", "计算机科学", 3)
def test_person_initialization(default_employee):
assert default_employee.first_name == "二哥", "名字应该是二哥"
assert default_employee.last_name == "王", "姓氏应该是王"
assert default_employee.major == "计算机科学", "专业应该是计算机科学"
assert default_employee.years == 3, "年级应该是3"
将测试融入你的 DNA
**你的测试覆盖率,就是你对代码的自信心指数。**从现在开始,为每个新功能编写测试。
如何在 FastAPI 应用程序中引入测试呢?如何编写 API 接口测试?如何编写数据测试?
下期,我将掰开了,揉碎了,把它们一次性讲清楚。
想要获取本章完整代码,请在评论区回复 【FastAPI】,代码直接复制就能跑。
关于 FastAPI 的其他疑问
加个字段,服务崩了?FastAPI新手避坑,Alembic三步搞定表结构变更!方案闭眼抄
别等着被骂:API上线前,一定要把SQLite换成MySQL,附 FastAPI对接代码
别等被骂才后悔:APP上线前,一定要把SQLite换成PostgreSQL,附 FastAPI对接代码
你的API在裸奔?踩坑8小时,从“越权裸奔”到“权限严控”:FastAPI+JWT+依赖注入,这套方案闭眼抄
你的APP要用户反复登录?密码传来传去?FastAPI+JWT实战,一个令牌全打通,安全与体验兼得,代码直接抄
相关内容我都给大家做好了,感兴趣的朋友来「我的主页」找一找,直接就可以看到。
欢迎关注 「王二哥的技术笔记」,每天分享「Python」、「职场」有趣干货,千万不要错过!