【Django】-9- 单元测试和集成测试(上)

一、Django 项目单元 & 集成测试准备 👇

依赖安装(给项目装 "测试小帮手"🍼)

bash 复制代码
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 - Typeapplication/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页面)

python 复制代码
from django.test.client import Client

import pytest


@pytest.fixture
def client() -> Client:
    return Client()


def test_register_get(client: Client):
    resp: HttpResponse = client.get("/accounts/register")
    assert resp.status_code == 200

    html = resp.content.decode()

    assert "html" in html
    assert "用户名" in html
    assert "密码" in html
    assert "确认密码" in html

1. 引入工具:from django.test.client import Client

👉 作用 :从 Django 测试工具里,把「发 HTTP 请求的小助手 Client」请进来~

👀 为啥?

Django 专门给咱准备了 Client 类,用来模拟浏览器发请求(比如 GET、POST),测试咱的视图函数 / 接口。就像给代码一个 "虚拟小浏览器",不用真的开浏览器,也能测试网页 / 接口响不响应~

2. fixture 魔法:@pytest.fixture + def client() -> Client:

python 复制代码
@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

👉 作用 :检查服务器返回的状态码是不是 200200 代表 "请求成功",网页正常返回啦~)

👀 为啥要断言?

测试的核心!如果状态码不是 200(比如 404 找不到页面、500 服务器报错),说明注册页面可能有问题,测试就会 "失败",提醒咱去修~

6. 解析响应内容:html = resp.content.decode()

👉 作用 :把响应的二进制内容(resp.content)转换成字符串(decode() 解码),方便后面检查页面里有没有我们要的内容~

👀 为啥要解码?

网络编程(发送和接收网络数据包)的HttpResponse是字节流,二进制数据。
resp.content 存的是二进制数据(像 b'<html>...'),转成字符串(html)后,才能用字符串的方法(比如 in 关键字)检查内容~

7. 检查页面内容:一堆 assert

python 复制代码
assert "html" in html  
assert "用户名" in html  
assert "密码" in html  
assert "确认密码" in html  

👉 作用 :确认返回的 HTML 里,包含 "html""用户名""密码""确认密码" 这些关键字~

👀 为啥要检查?

保证注册页面的 HTML 里,确实有这些表单字段(用户名、密码输入框)。如果哪天代码不小心把这些字段删了,测试就会失败,提醒咱 "注册页面不对啦!"

四、测试DB数据库

🌟 user fixture ------ 提前造个 "测试用户"

python 复制代码
@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 就变成一个可复用的 "工具函数",其他测试用例要用时,直接传参即可!
    • 作用:提前帮你在数据库里造一个测试用户,不用每次测试都手动创建啦~
  • def user(_django_db_helper):

    • _django_db_helper 是 pytest-django 提供的 "数据库小助手",会自动帮你初始化、清理数据库,保证测试间互不干扰~
    • 函数名 user 是你给这个 "造用户工具" 起的名字,方便其他测试用例调用~
  • new_user = User.objects.create_user(...)

    • 调用 Django 的 create_user 方法,在数据库里实际创建一个用户(用户名、邮箱、密码都是测试用的假数据~)
    • 相当于:"嘿,数据库~ 帮我塞一条用户数据,测试时要用!"
  • return new_user

    • 把刚创建的用户对象返回,其他测试用例如果用了这个 user fixture,就能直接拿到这个 "测试用户" 啦~

🌟 参数化测试 ------ 批量测 "注册场景"

这部分是@pytest.mark.parametrize 批量测试不同注册情况(用户名空、密码不一致、注册成功等),超高效!

python 复制代码
@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=-1msg="username 不能为空"
      • 第 2 组:data{"password_confirm": "2"}(密码不一致),预期 code=-2msg="两次密码输入不一致"
      • 第 3 组:data{"username": "test_user_beifan"}(合法数据),预期 code=0msg="注册成功"
  • 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"] == codeassert resp_json["msg"] == msg

    • 检查响应的 codemsg 是否符合预期~ 比如:
      • 用户名空时,code 应该是 -1msgusername 不能为空
      • 注册成功时,code0msg注册成功

🌟 数据库断言 ------ 注册成功后,用户真的 "入库" 了吗?

这部分是测试 "注册成功后,数据库用户数量是否变化",保证代码真的把用户数据存到数据库啦~

python 复制代码
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 + 新注册的用户)。
    • 相当于:"嘿,数据库~ 注册成功后,用户是不是真的存进来啦?数量对不对呀?"

🌈 整体流程总结

  1. 造用户 :用 @pytest.fixture 提前在数据库造一个 test_user,当 "测试种子"。
  2. 批量测注册 :用 @pytest.mark.parametrize 批量测试各种注册场景(用户名空、密码错、注册成功)。
  3. 发请求 :用 client.post 模拟浏览器发注册请求,看服务器咋响应。
  4. 断言响应 :检查返回的 codemsg 是否符合预期(比如注册成功时 code=0)。
  5. 数据库校验:注册成功后,再查数据库用户数量,确保真的新增了用户~

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 里),比如:

python 复制代码
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 触发的 "全自动流程"

  1. user fixture 里的 User.objects.create_user() 触发 "需要数据库" 的信号
  2. pytest-django 接收到信号,创建临时数据库
  3. 自动修改数据库配置,连接到临时数据库
  4. 自动运行迁移,确保表结构正确
  5. 执行 create_user,往临时数据库里写入测试用户数据

整个过程完全自动,不需要你手动写 connect()create_database() 之类的代码~ 就像点外卖时,平台自动帮你完成 "找餐厅、下单、配送",你只需要等着吃(写测试)就行啦! 😋

为什么resp = client.post(...) 就能把提交的data放到数据库里呢

🌠 第一步:client.post(...) 是 "发件人"

🛣️ 第二步:Django 路由 "指路"

🏭 第三步:视图函数 "处理包裹"

register_view 视图函数会打开包裹(data),做一系列操作:

  1. 检查数据:比如看看用户名是不是空的、密码够不够长(就像快递员检查包裹是否违禁品)
  2. 创建用户 :如果数据没问题,就会调用 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 时:
  1. 测试开始前user fixture 先执行 User.objects.create_user → 触发数据库初始化(开煤气)
  2. 测试进行中client.post(...) 触发视图函数 → 执行 create_user(点火炒菜)→ 顺利执行(煤气已开)
没有 user fixture 时:
  1. 测试开始前:没有任何数据库操作 → pytest-django 认为 "不需要数据库"→ 不初始化(没开煤气)
  2. 测试进行中client.post(...) 触发视图函数 → 执行 create_user(想点火炒菜)→ 发现 "煤气没开"(数据库未初始化)→ 报错!

🛠️ pytest-django 的 "小规矩":

pytest-django 有个 "懒加载" 原则:只有在测试开始前,检测到 "明确需要数据库" 的信号(比如 fixture 里有数据库操作),才会提前初始化数据库

而视图函数里的 create_user 是 "测试运行到一半才执行的代码",这时候再想初始化数据库已经太晚了 ------ 就像炒菜炒到一半才发现没开煤气,火根本点不着呀!

🌟 解决办法:给测试 "明确开煤气"

如果不想用 user fixture,也可以用 pytest-django 提供的 django_db fixture,直接告诉 pytest:"我要用到数据库,麻烦提前准备好哦~"

python 复制代码
# 不用 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 会:

  1. 读取 settings.py 里的数据库配置(DATABASES 字典),比如数据库类型(MySQL/PostgreSQL)、地址、用户名密码等。
  2. 启动时自动创建数据库连接池(保持和数据库的 "长期通话" 状态)。
  3. 检查数据库表结构(如果用了 migrate 迁移),确保表已经存在。

这就像你家里的煤气灶,只要插上电、打开总开关,随时拧旋钮就能点火 ------数据库连接在项目启动时就已经 "待命" 了

🚀 所以视图里的 User.objects.create_user 能直接执行

当用户在浏览器里提交表单(或者你用 Postman 发请求),触发视图函数里的 User.objects.create_user 时:

  • Django 已经通过启动时建立的数据库连接,把 ORM 代码(create_user)翻译成 SQL。
  • 直接通过现成的连接发给数据库执行,根本不需要 "临时启动数据库"。

这和测试环境的区别在于:

  • 开发环境 :项目启动时强制初始化数据库连接(因为 runserver 命令默认需要数据库支持)。
  • 测试环境(pytest) :为了效率,默认不初始化数据库,只有明确告诉它 "要用数据库"(比如用 django_db fixture),才会提前准备。

📌 总结:开发时 "无需手动启动" 的核心原因

Django 的 runserver 命令在启动项目时,会自动根据 settings.py 的配置完成数据库连接初始化,相当于 "项目一启动,数据库就处于'开机待命'状态"。

相关推荐
计算机编程小央姐20 小时前
跟上大数据时代步伐:食物营养数据可视化分析系统技术前沿解析
大数据·hadoop·信息可视化·spark·django·课程设计·食物
诗句藏于尽头21 小时前
Django模型与数据库表映射的两种方式
数据库·python·django
IT学长编程1 天前
计算机毕业设计 基于Hadoop豆瓣电影数据可视化分析设计与实现 Python 大数据毕业设计 Hadoop毕业设计选题【附源码+文档报告+安装调试
大数据·hadoop·python·django·毕业设计·毕业论文·豆瓣电影数据可视化分析
Python私教1 天前
Django全栈班v1.04 Python基础语法 20250912 下午
后端·python·django
凡梦千华2 天前
Django时区感知
后端·python·django
程序设计实验室2 天前
Django过时了吗?从ASGI到AI时代的思考
django
SXTomi2 天前
【无人机】无人机用户体验测试策略详细介绍
集成测试·无人机·用户体验
Python私教2 天前
Django全栈班v1.04 Python基础语法 20250912 上午
后端·python·django
言之。2 天前
Django REST框架:ModelViewSet全面解析
数据库·python·django
魂尾ac2 天前
Django + Vue3 前后端分离技术实现自动化测试平台从零到有系列 <第一章> 之 注册登录实现
后端·python·django·vue