一、Django 项目单元 & 集成测试准备 👇
依赖安装(给项目装 “测试小帮手”🍼)
pdm add -d black isort flake8 pytest pytest-django pytest-coverage
👉 这行命令像在给项目 “采购” 测试工具:
black
✨ 自动格式化代码,让代码整齐得像排好队的小士兵isort
🧹 帮你把 import 语句整理得明明白白flake8
🔍 像代码 “小侦探”,揪出语法和风格问题pytest
🧪 单元测试 “大主角”,跑测试用例超好用pytest-django
🐍+🧪 让 pytest 能和 Django 好好 “交朋友”,测试 Django 项目pytest-coverage
📊 看看测试覆盖了多少代码,心里有底
配置文件(给工具们定 “小规矩”📜)
1. .flake8
(flake8 的 “小手册”)
[flake8]
exclude = venv # 告诉 flake8 别去碰 venv 文件夹~🚫
extend-ignore = E501 # 放宽点啦,忽略行太长的报错(E501)🙈
2. pytest.ini
(pytest 的 “剧本”)
[pytest]
DJANGO_SETTINGS_MODULE = Tesla.settings # 告诉 pytest 用哪个 Django 配置⚙️
python_files = tests.py test_*.py # 哪些文件算测试文件呀?找 tests.py 和 test_开头的~🔍
测试用例(给项目 “模拟闯关”🎮)
根据我们的业务逻辑代码进行分析~
1. 注册功能 📝
- 允许匿名访问 👻(游客也能注册!)
- URL:
http://127.0.0.1:8000/accounts/register
🔗- GET:返回 HTML 页面~像打开注册 “小窗口”🖥️
- POST:提交 JSON 数据,还要验证:
- 用户名不能为空 ❌(空用户名像没名字的小幽灵,不行!)
- 密码不能为空 ❌(没密码咋保护自己~)
- 两次密码得一样 🔄(不然自己都记混啦)
- 密码长度≥6 位 📏(太短不安全呀)
- 用户名不能重复 👥(不能撞名呀)
- 都对的话,返回 “注册成功”🎉
2. 登录功能 🔑
- 允许匿名访问 👻(游客也能登录页逛逛)
- URL:
http://127.0.0.1:8000/accounts/login/
🔗- GET:返回登录 HTML 页面 🖥️
- POST:提交表单,还要验证:
- 用户名不能为空 ❌(没用户名咋找账号~)
- 密码不能为空 ❌(没密码进不去呀)
- 用户名、密码得对 ✅(不然进错家门啦)
3. 提交反馈 📮
- 不允许匿名访问 🔒(得登录才能反馈!)
- URL:
http://127.0.0.1:8000/beifan/submit
🔗- GET:返回 HTML 页面 🖥️
- POST:提交 JSON 数据,还要验证:
- 数据能存到数据库里 🗄️(像把信放进邮箱~)
- 数据和用户关联上 👤(谁发的反馈要记好)
- 同一用户不能重复发 🔄(别刷屏呀~)
4. 反馈结果 🔍
- 允许匿名访问 👻(谁都能看看结果)
- URL:
http://127.0.0.1:8000/beifan/result
🔗 - 不管 GET/POST,都返回 HTML 页面 🖥️(看看反馈结果啥样~)
二、HttpResponse
数据结构角度
HttpResponse
类定义了一系列属性和方法来管理响应相关的数据。
- 属性方面:
content
:以字节串形式存储响应的主体内容,比如返回的 HTML 页面内容、JSON 数据经过编码后的字节串等。例如返回一个简单 HTML 页面,这个 HTML 文本内容最终会编码后存到content
中。status_code
:记录 HTTP 响应状态码,像常见的200
(请求成功)、404
(页面未找到)、500
(服务器内部错误)等,通过这个属性可以让客户端快速知晓请求处理的结果状态。headers
:是一个类似字典的数据结构,用来存放 HTTP 响应头信息,比如Content - Type
(指定响应内容的类型,像text/html
表示 HTML 页面,application/json
表示 JSON 数据)、Content - Length
(响应内容的长度)等。
- 方法方面:
- 它提供了一些方法来操作响应数据,比如
__setitem__
方法(可以像操作字典一样response['key'] = 'value'
),用于设置响应头信息。
- 它提供了一些方法来操作响应数据,比如
面向对象角度
HttpResponse
类遵循面向对象编程范式,通过封装、继承和多态等特性,来实现对 HTTP 响应的管理和扩展:
- 封装:把与 HTTP 响应相关的各种信息(内容、状态码、响应头)和操作(设置响应头、获取内容等)封装在一个类中,提供了统一且便捷的接口来处理响应。比如在视图函数中,只需要创建
HttpResponse
实例并设置相关属性,就能轻松构建一个完整的 HTTP 响应。 - 继承:Django 提供了一些
HttpResponse
的子类,如HttpResponseRedirect
(用于重定向,默认状态码为302
)、JsonResponse
(专门用于返回 JSON 数据,自动设置Content - Type
为application/json
) 。这些子类继承了HttpResponse
的基本属性和方法,并根据自身功能需求进行了扩展和定制。 - 多态:在 Django 的视图函数返回机制中,无论是返回
HttpResponse
对象还是它的子类对象,都遵循统一的规则(都被视为合法的响应返回值),这体现了多态性。视图函数根据业务逻辑的不同,灵活返回不同类型的响应对象,而 Django 的请求处理机制都能正确处理并发送给客户端。
Web 开发流程角度
在 Django 应用处理 HTTP 请求的流程中,HttpResponse
是请求处理结果的最终承载者:
- 当客户端发起一个 HTTP 请求到 Django 服务器,Django 会根据 URL 配置找到对应的视图函数进行处理。
- 视图函数在处理完业务逻辑(如查询数据库、进行数据计算等)后,需要构建一个响应返回给客户端,此时就会创建
HttpResponse
对象(或者它的子类对象),将处理结果填充到响应对象的相关属性中(如设置响应内容、状态码、响应头)。 - 最后,Django 的请求处理机制会将这个
HttpResponse
对象转换为符合 HTTP 协议规范的格式,通过网络发送给客户端,客户端再根据响应信息进行相应的展示或处理(如浏览器渲染 HTML 页面、解析 JSON 数据等) 。
总之,HttpResponse
类是 Django 构建和管理 HTTP 响应的核心组件,通过数据结构、面向对象编程以及在 Web 开发流程中的关键作用,实现了从服务器端到客户端的响应信息传递。
三、测试HTTP请求
先测试一个简单的登录视图的get请求(返回一个html页面)
from django.test.client import Clientimport pytest@pytest.fixture
def client() -> Client:return Client()def test_register_get(client: Client):resp: HttpResponse = client.get("/accounts/register")assert resp.status_code == 200html = resp.content.decode()assert "html" in htmlassert "用户名" in htmlassert "密码" in htmlassert "确认密码" in html
1. 引入工具:from django.test.client import Client
👉 作用:从 Django 测试工具里,把「发 HTTP 请求的小助手 Client
」请进来~
👀 为啥?
Django 专门给咱准备了 Client
类,用来模拟浏览器发请求(比如 GET、POST),测试咱的视图函数 / 接口。就像给代码一个 “虚拟小浏览器”,不用真的开浏览器,也能测试网页 / 接口响不响应~
2. fixture 魔法:@pytest.fixture
+ def client() -> Client:
@pytest.fixture
def client() -> Client: return Client()
👉 作用:用 pytest 的 fixture
,创建一个可复用的 “发请求工具”,叫 client
~
👀 为啥这么写?
@pytest.fixture
是 pytest 的 “魔法标记”🧙,标记后,这个client
函数就变成了一个 “工具工厂”,其他测试函数要用的时候,直接当参数传进去就行!return Client()
:每次调用client
,都会新建一个Client
实例(也就是新的 “虚拟小浏览器”),保证测试之间互不干扰~
3. 测试用例:def test_register_get(client: Client):
👉 作用:定义一个测试用例,名字叫 test_register_get
,专门测试注册页面的 GET 请求~
👀 为啥参数是 client: Client
?
因为上面用 @pytest.fixture
标记了 client
,pytest 会自动把 Client
实例传进来,供这个测试用例使用!相当于 “自动给你递上小浏览器,不用自己手动创建啦”~
4. 发请求:resp: HttpResponse = client.get("/accounts/register")
👉 作用:用 client
(虚拟小浏览器),发一个 GET 请求 到 /accounts/register
(注册页面的 URL),然后把服务器返回的响应存到 resp
里~
👀 为啥这么写?
模拟用户在浏览器里输入 http://.../accounts/register
访问注册页的行为。client.get(...)
就是帮我们发 GET 请求的 “快捷方式”,不用真的启动浏览器~
5. 断言状态码:assert resp.status_code == 200
👉 作用:检查服务器返回的状态码是不是 200
(200
代表 “请求成功”,网页正常返回啦~)
👀 为啥要断言?
测试的核心!如果状态码不是 200
(比如 404
找不到页面、500
服务器报错),说明注册页面可能有问题,测试就会 “失败”,提醒咱去修~
6. 解析响应内容:html = resp.content.decode()
👉 作用:把响应的二进制内容(resp.content
)转换成字符串(decode()
解码),方便后面检查页面里有没有我们要的内容~
👀 为啥要解码?
网络编程(发送和接收网络数据包)的HttpResponse是字节流,二进制数据。
resp.content
存的是二进制数据(像 b'<html>...'
),转成字符串(html
)后,才能用字符串的方法(比如 in
关键字)检查内容~
7. 检查页面内容:一堆 assert
assert "html" in html
assert "用户名" in html
assert "密码" in html
assert "确认密码" in html
👉 作用:确认返回的 HTML 里,包含 “html”“用户名”“密码”“确认密码” 这些关键字~
👀 为啥要检查?
保证注册页面的 HTML 里,确实有这些表单字段(用户名、密码输入框)。如果哪天代码不小心把这些字段删了,测试就会失败,提醒咱 “注册页面不对啦!”
四、测试DB数据库
🌟 user
fixture —— 提前造个 “测试用户”
@pytest.fixture()
def user(_django_db_helper): new_user = User.objects.create_user( username='test_user', email='test_user@qq.com', password='test_user_pass', ) return new_user
逐行拆解
@pytest.fixture()
- pytest 的 “魔法标记”🧙,标记后,
user
就变成一个可复用的 “工具函数”,其他测试用例要用时,直接传参即可! - 作用:提前帮你在数据库里造一个测试用户,不用每次测试都手动创建啦~
- pytest 的 “魔法标记”🧙,标记后,
def user(_django_db_helper):
_django_db_helper
是 pytest-django 提供的 “数据库小助手”,会自动帮你初始化、清理数据库,保证测试间互不干扰~- 函数名
user
是你给这个 “造用户工具” 起的名字,方便其他测试用例调用~
new_user = User.objects.create_user(...)
- 调用 Django 的
create_user
方法,在数据库里实际创建一个用户(用户名、邮箱、密码都是测试用的假数据~) - 相当于:“嘿,数据库~ 帮我塞一条用户数据,测试时要用!”
- 调用 Django 的
return new_user
- 把刚创建的用户对象返回,其他测试用例如果用了这个
user
fixture,就能直接拿到这个 “测试用户” 啦~
- 把刚创建的用户对象返回,其他测试用例如果用了这个
🌟 参数化测试 —— 批量测 “注册场景”
这部分是用 @pytest.mark.parametrize
批量测试不同注册情况(用户名空、密码不一致、注册成功等),超高效!
@pytest.mark.parametrize( "data, code, msg", [ ({"username": ""}, -1, "username 不能为空"), # 用户名空 ({"password_confirm": "2"}, -2, "两次密码输入不一致"), # 密码不一致 ({"username": "test_user_beifan"}, 0, "注册成功"), # 注册成功 ]
)
def test_register_post(user, client, data, code, msg): # 发 POST 请求测试注册 resp = client.post( "/accounts/register", data=data, content_type="application/json" ) # 解析响应 html = resp.content.decode() resp_json = json.loads(html) # 断言响应是否符合预期 assert resp_json["code"] == code assert resp_json["msg"] == msg
这里的data有简化省略了其他的键值对
逐行拆解
@pytest.mark.parametrize("data, code, msg", [...])
- pytest 的 “参数化魔法”🪄!括号里的
"data, code, msg"
是 “参数名”,后面的列表是 “参数值组合”。 - 作用:批量生成测试用例,列表里每一个元组,都会对应一条测试用例~ 比如:
- 第 1 组:
data
是{"username": ""}
(用户名空),预期code=-1
,msg="username 不能为空"
- 第 2 组:
data
是{"password_confirm": "2"}
(密码不一致),预期code=-2
,msg="两次密码输入不一致"
- 第 3 组:
data
是{"username": "test_user_beifan"}
(合法数据),预期code=0
,msg="注册成功"
- 第 1 组:
- pytest 的 “参数化魔法”🪄!括号里的
def test_register_post(user, client, data, code, msg):
- 测试用例函数,参数里:
user
:就是图 1 里的user
fixture,会自动传入 “测试用户”(如果需要的话~)client
:Django 测试客户端(图 1 里讲过的 “虚拟小浏览器”)data, code, msg
:来自@pytest.mark.parametrize
的参数,每组数据都会跑一次测试~
- 测试用例函数,参数里:
resp = client.post(...)
- 用
client
(虚拟小浏览器)发一个 POST 请求 到/accounts/register
(注册接口),还带了data
(请求体)和content_type="application/json"
(告诉服务器,我发的是 JSON 数据哟~)
- 用
html = resp.content.decode()
→resp_json = json.loads(html)
- 把响应的二进制内容(
resp.content
)解码成字符串(html
),再转成 JSON(resp_json
),方便断言~
- 把响应的二进制内容(
assert resp_json["code"] == code
→assert resp_json["msg"] == msg
- 检查响应的
code
和msg
是否符合预期~ 比如:- 用户名空时,
code
应该是-1
,msg
是username 不能为空
- 注册成功时,
code
是0
,msg
是注册成功
- 用户名空时,
- 检查响应的
🌟 数据库断言 —— 注册成功后,用户真的 “入库” 了吗?
这部分是测试 “注册成功后,数据库用户数量是否变化”,保证代码真的把用户数据存到数据库啦~
def test_register_post(user, client, data, code, msg): # 1. 发请求前,先查数据库用户数量 user_list = list(User.objects.all()) user_count = len(user_list) assert user_count == 1 # 假设测试前只有 1 个用户(图 1 里的 test_user) # 2. 发 POST 请求测试注册 resp = client.post(...) # (和之前一样,发请求、解析响应) # 3. 断言响应是否符合预期(code、msg) assert resp_json["code"] == code assert resp_json["msg"] == msg # 4. 如果注册成功(code == 0),再查数据库用户数量 if code == 0: user_list = list(User.objects.all()) user_count = len(user_list) assert user_count == 2 # 注册成功后,应该新增 1 个用户 → 总数 2
逐行拆解
user_list = list(User.objects.all())
→user_count = len(user_list)
- 发请求前,先查数据库里的所有用户,数一下有多少个(
user_count
)。 - 假设测试环境里,一开始只有图 1 里创建的
test_user
,所以user_count == 1
。
- 发请求前,先查数据库里的所有用户,数一下有多少个(
assert user_count == 1
- 确保测试前数据库状态 “干净”,只有 1 个测试用户,避免其他数据干扰测试结果~
if code == 0:
code == 0
代表 “注册成功”,这时需要再查数据库,确认用户真的新增了!
user_list = list(User.objects.all())
→user_count = len(user_list)
- 发请求后,再次查数据库用户数量。
assert user_count == 2
- 注册成功的话,用户数量应该从
1
变成2
(原来的test_user
+ 新注册的用户)。 - 相当于:“嘿,数据库~ 注册成功后,用户是不是真的存进来啦?数量对不对呀?”
- 注册成功的话,用户数量应该从
🌈 整体流程总结
- 造用户:用
@pytest.fixture
提前在数据库造一个test_user
,当 “测试种子”。 - 批量测注册:用
@pytest.mark.parametrize
批量测试各种注册场景(用户名空、密码错、注册成功)。 - 发请求:用
client.post
模拟浏览器发注册请求,看服务器咋响应。 - 断言响应:检查返回的
code
和msg
是否符合预期(比如注册成功时code=0
)。 - 数据库校验:注册成功后,再查数据库用户数量,确保真的新增了用户~
user 固件也就是说意义在于数据库的初始化,管理 和 验证是否用户名重复
1. 🛠️ 帮数据库 “热热身”
user
fixture 里的 User.objects.create_user(...)
一执行,就像给数据库发了条消息:“喂~ 准备好啦,要开始测试咯!”
Django 会因此自动完成数据库连接、创建测试表等一系列准备工作,避免测试时出现 “数据库还没启动” 的尴尬错误~ 就像玩游戏前先加载地图,不然点 “开始” 会卡住呀!
2. 🆚 提供 “参照物” 方便验证
比如测试 “用户名不能重复” 时,user
就像一个 “标杆用户”🆔:
- 它的用户名是
test_user
,已经存在于数据库里(先建一个user数据,所以后面的断言是1->2) - 当你用同样的用户名
test_user
去注册时,就能验证系统会不会报错 “用户名已存在” - 如果没有这个 “参照物”,数据库空空如也,根本测不出 “重复注册” 的逻辑对不对呀~
所以哪怕 user
没在代码里被直接 “点名”,它也是测试里的 “幕后功臣”🌟:既让数据库准备好工作,又提供了关键的 “对比数据”,保证各种注册场景都能被准确测试到~
Django 测试中通过 user
fixture 自动完成数据库连接的过程
🌱 第一步:pytest-django
的 “数据库开关”
user
fixture 里有 User.objects.create_user(...)
这行代码 —— 它要往数据库里写数据,这就像给 pytest-django
递了一张 “需要数据库” 的门票🎫。
pytest-django
看到这张 “门票” 后,会自动触发一个核心机制:启用数据库连接。
(如果测试里完全用不到数据库操作,pytest-django
会默认 “关闭” 数据库,让测试跑得更快~)
🛠️ 第二步:创建 “临时测试数据库”
为了不污染你的真实数据库(比如开发环境的 db.sqlite3
),pytest-django
会偷偷做一件事:
自动创建一个全新的临时数据库(名字通常是 test_你的数据库名
,比如 test_myproject
)。
这个临时数据库就像一个 “一次性舞台”:
- 结构和你的真实数据库一模一样(表、字段都照着抄)
- 但里面的数据是干净的,专门给测试用
- 测试结束后会自动删除,不会留下任何痕迹~
🔗 第三步:自动连接到临时数据库
Django 的核心配置里有 DATABASES
选项(在 settings.py
里),比如:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', # 真实数据库 }
}
当 pytest-django
检测到需要数据库时,会自动 “替换” 这个配置:
把 NAME
改成临时数据库的路径(比如 test_db.sqlite3
),然后调用 Django 内置的 connection
模块,建立和这个临时数据库的连接。
这一步就像:
你本来要去 “正式餐厅”(真实数据库),但测试时被悄悄引导到了 “隔壁的临时分店”(临时数据库),地址变了,但进门的方式(连接方式)完全一样~
🧹 第四步:自动执行数据库迁移
连接好临时数据库后,pytest-django
还会自动做一件事:
运行所有 migrations
(数据迁移文件),确保临时数据库的表结构和你的项目代码完全同步。
就像:
临时舞台搭好了,但还得按设计图(migrations
文件)摆好桌椅(数据表),演员(测试数据)才能上场~
🌟 总结:user
fixture 触发的 “全自动流程”
user
fixture 里的User.objects.create_user()
触发 “需要数据库” 的信号pytest-django
接收到信号,创建临时数据库- 自动修改数据库配置,连接到临时数据库
- 自动运行迁移,确保表结构正确
- 执行
create_user
,往临时数据库里写入测试用户数据
整个过程完全自动,不需要你手动写 connect()
或 create_database()
之类的代码~ 就像点外卖时,平台自动帮你完成 “找餐厅、下单、配送”,你只需要等着吃(写测试)就行啦! 😋
为什么resp = client.post(...) 就能把提交的data放到数据库里呢
🌠 第一步:client.post(...)
是 “发件人”
🛣️ 第二步:Django 路由 “指路”
🏭 第三步:视图函数 “处理包裹”
register_view
视图函数会打开包裹(data
),做一系列操作:
- 检查数据:比如看看用户名是不是空的、密码够不够长(就像快递员检查包裹是否违禁品)
- 创建用户:如果数据没问题,就会调用
User.objects.create_user(...)
(Django 的 ORM 方法),把data
里的用户名、密码等信息 “翻译” 成数据库能懂的语言(SQL 语句)。
📦 第四步:ORM 当 “翻译官”,把数据存进数据库
Django 的 ORM(对象关系映射)是个超厉害的 “翻译官”:
- 你写的
User.objects.create_user(username="小明")
,它会自动翻译成 SQL 语句:INSERT INTO auth_user (username, password) VALUES ('小明', '加密后的密码')
- 然后通过之前
user
fixture 已经建好的数据库连接,把这条 SQL 发给临时数据库执行。
数据库收到指令后,就会在 auth_user
表(用户表)里新增一行记录 —— 你的 data
就这样成功 “住进” 数据库啦!
那没有user这个固件了 直接resp = client.post(...) 能不能连接上数据库
哈哈,这个问题就像在问:“如果没有提前打开厨房门,能直接炒菜吗?” 答案是 ——大部分情况会失败!用超萌的例子解释:
假设 user
fixture 是 “提前打开厨房门的人”👩🍳,而 client.post(...)
是 “炒菜的动作”:
🍳 情况 1:如果你的 post
请求需要操作数据库(比如注册用户)
- 这时候必须先有 “可用的厨房”(数据库连接),否则炒不了菜~
- 没有
user
fixture,pytest-django
可能会 “偷懒” 不初始化数据库(因为没收到 “需要数据库” 的信号)。 - 当
client.post(...)
触发视图函数去写数据库时,会发现 “厨房门没开”(数据库没连接),直接报错:DatabaseError
或 “表不存在” 之类的错误~
就像没开煤气就想炒菜,锅是冷的,根本炒不熟呀!
🍳 情况 2:如果你的 post
请求完全不碰数据库(比如只是返回固定文本)
- 比如视图函数只是返回
{"code":0}
,不读写数据库,那就算没有user
fixture,client.post(...)
也能正常运行~ - 这就像 “凉拌菜”,不需要开火(数据库),直接拌一拌就能吃~
📌 关键结论
client.post(...)
本身不会主动触发数据库连接,它只是发请求的 “快递员”。
是否能连接数据库,取决于测试中有没有其他触发数据库初始化的操作(比如 user
fixture 里的 User.objects.create_user
)。
如果你的 post
请求需要操作数据库,最好还是带上 user
fixture 或其他 “数据库触发器”(比如 django_db
fixture),确保 “厨房门是开着的” 哦~ 🔑
可是resp = client.post(...) 的后端视图处理不是有创建User.objects.create_user的代码吗 为啥不能执行到这里的时候开始初始化数据库
这个问题超棒!就像在问:“既然炒菜时会用到煤气,为啥不能在炒菜时再开煤气呢?” 这里藏着一个 “先后顺序” 的小秘密~
🔥 核心原因:“煤气”(数据库连接)必须在 “点火”(执行代码)前打开
Django 测试中,数据库初始化(连接、创建临时表等)是 **“提前准备工作”,必须在任何数据库操作代码(比如 User.objects.create_user
)执行之前 ** 完成。
而 client.post(...)
触发的视图函数里的 create_user
,属于 “测试过程中才会执行的代码”—— 就像 “点火炒菜” 这个动作,必须在 “煤气已经打开” 之后才能进行。
🍳 具体流程对比:
有 user
fixture 时:
- 测试开始前:
user
fixture 先执行User.objects.create_user
→ 触发数据库初始化(开煤气) - 测试进行中:
client.post(...)
触发视图函数 → 执行create_user
(点火炒菜)→ 顺利执行(煤气已开)
没有 user
fixture 时:
- 测试开始前:没有任何数据库操作 → pytest-django 认为 “不需要数据库”→ 不初始化(没开煤气)
- 测试进行中:
client.post(...)
触发视图函数 → 执行create_user
(想点火炒菜)→ 发现 “煤气没开”(数据库未初始化)→ 报错!
🛠️ pytest-django 的 “小规矩”:
pytest-django 有个 “懒加载” 原则:只有在测试开始前,检测到 “明确需要数据库” 的信号(比如 fixture 里有数据库操作),才会提前初始化数据库。
而视图函数里的 create_user
是 “测试运行到一半才执行的代码”,这时候再想初始化数据库已经太晚了 —— 就像炒菜炒到一半才发现没开煤气,火根本点不着呀!
🌟 解决办法:给测试 “明确开煤气”
如果不想用 user
fixture,也可以用 pytest-django 提供的 django_db
fixture,直接告诉 pytest:“我要用到数据库,麻烦提前准备好哦~”
# 不用 user fixture,改用 django_db fixture
def test_register_post(client, django_db, data, code, msg): resp = client.post("/accounts/register", data=data) # 此时数据库已提前初始化 # ... 后续断言 ...
django_db
就像一个 “备用煤气开关”,专门负责在测试前打开数据库连接~
可是不做单元测试 我只做开发的话视图User.objects.create_user前也没有什么启动数据库的操作啊
在实际开发中,情况和测试环境完全不同!就像 “家里做饭” 和 “野外露营做饭” 的区别 —— 家里的煤气灶是长期打开、随时可用的,而露营时需要提前手动生火~
🏠 开发环境中,数据库连接是 “自动提前就绪” 的
在开发阶段,当你启动 Django 项目(比如 python manage.py runserver
)时,Django 会自动完成一系列 “初始化工作”,其中就包括数据库连接的建立,根本不需要你手动写代码 “启动数据库”。
具体来说,Django 会:
- 读取
settings.py
里的数据库配置(DATABASES
字典),比如数据库类型(MySQL/PostgreSQL)、地址、用户名密码等。 - 启动时自动创建数据库连接池(保持和数据库的 “长期通话” 状态)。
- 检查数据库表结构(如果用了
migrate
迁移),确保表已经存在。
这就像你家里的煤气灶,只要插上电、打开总开关,随时拧旋钮就能点火 ——数据库连接在项目启动时就已经 “待命” 了。
🚀 所以视图里的 User.objects.create_user
能直接执行
当用户在浏览器里提交表单(或者你用 Postman 发请求),触发视图函数里的 User.objects.create_user
时:
- Django 已经通过启动时建立的数据库连接,把 ORM 代码(
create_user
)翻译成 SQL。 - 直接通过现成的连接发给数据库执行,根本不需要 “临时启动数据库”。
这和测试环境的区别在于:
- 开发环境:项目启动时强制初始化数据库连接(因为
runserver
命令默认需要数据库支持)。 - 测试环境(pytest):为了效率,默认不初始化数据库,只有明确告诉它 “要用数据库”(比如用
django_db
fixture),才会提前准备。
📌 总结:开发时 “无需手动启动” 的核心原因
Django 的 runserver
命令在启动项目时,会自动根据 settings.py
的配置完成数据库连接初始化,相当于 “项目一启动,数据库就处于‘开机待命’状态”。