一、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
?的配置完成數據庫連接初始化,相當于 “項目一啟動,數據庫就處于‘開機待命’狀態”。