前言
好久沒搞 Django 了,最近維護一個我之前用 Django 開發的專案竟然有親切的感覺😂
測試,在以前確實是經常被忽略的話題,特別是對於 Python Web 這種快速開發框架,怎麼敏捷怎麼來,快速開發快速上線,而不是慢工出細活做得很規範,往往也是因為這種粗狂的開發風格,導致專案後續難以維護,這時候再給 Python 冠上一個開發容易維護難的名字。
Python: 我不背這鍋😒
說回正題,這次的測試包括兩部分,在 Django 專案內部寫單元測試和整合測試,保證專案功能正常,然後我還開發了一個獨立的自動測試工具,可以根據 OpenAPI 的文件來測試,並且在測試完成後輸出測試報告,報告內容包括每個介面是否測試透過和響應時間等。
這個工具我使用了 go 語言開發,主要是考慮到了 go 語言可以傻瓜式的實現交叉編譯,生成的可執行檔案直接上傳到伺服器就可以執行,非常方便。
這個自動測試工具我會在下一篇檔案介紹。
Django 的測試
不得不說 Django 的文件寫得真不錯👍
我看了一會文件就開始寫測試,Django 全家桶真的舒服,開發體驗太絲滑了
使用 startapp
建立 app 的時候,每個 app 目錄下都有個 tests.py
檔案,我們的測試程式碼就寫在這個檔案裡面好了。
如果測試程式碼很多的話,還可以拆分,如何拆分參考 views.py
的拆分,把 tests.py
改成 package ,即建立個 tests
目錄,下面放各個測試檔案,然後在 __init__.py
裡引入。
測試分為單元測試和整合測試,在 Django 裡寫單元測試比 AspNetCore 舒服多了,不用考慮依賴注入的問題,Django 全給你處理好了。
在測試的時候,Django 會自動建立測試資料庫,因此也不用自己去折騰環境隔離啥的。
關於這倆種測試的區別,我之前的文章裡有介紹,就不復制貼上了。
Asp-Net-Core學習筆記:單元測試和整合測試
例子
這次以兩個 app 為例
- config - 單元測試
- dashboard - 整合測試
在 Django 裡這倆種測試都沒啥心智負擔,也不用啥額外的操作,直接在 tests.py
裡寫就完事了。
單元測試
以 config app 作為單元測試的例子
我封裝了一個 ConfigService
程式碼如下
from datetime import date
from .models import CommonConfig
class ConfigService(object):
def __init__(self):
...
@staticmethod
def get_config(key: str) -> str:
queryset = CommonConfig.objects.filter(key=key)
if queryset.exists():
return queryset.first().value
return ''
@property
def today(self):
return date.today()
@property
def start_year(self):
value = self.get_config('start_year')
return str(self.today.year) if len(value) == 0 else value
@property
def start_month(self):
value = self.get_config('start_month')
return str(self.today.month) if len(value) == 0 else value
@property
def end_year(self):
value = self.get_config('end_year')
return str(self.today.year) if len(value) == 0 else value
@property
def end_month(self):
value = self.get_config('end_month')
return str(self.today.month) if len(value) == 0 else value
編輯 apps/config/tests.py
from django.test import TestCase
from apps.config.models import CommonConfig
from apps.config.services import ConfigService
class CommonConfigTestCase(TestCase):
def setUp(self):
CommonConfig.objects.create(key='start_year', value='2023')
CommonConfig.objects.create(key='end_year', value='2024')
CommonConfig.objects.create(key='start_month', value='1')
CommonConfig.objects.create(key='end_month', value='10')
def test_common_config(self):
cfg = ConfigService()
self.assertEqual(cfg.start_year, '2023')
self.assertEqual(cfg.end_year, '2024')
self.assertEqual(cfg.start_month, '1')
self.assertEqual(cfg.end_month, '10')
整合測試
整合測試是模擬 HTTP 請求去訪問介面,看看介面正不正常。
Django 的 TestCase
類裡自帶了 client
屬性,可以很方便的請求介面。
一般介面都要登入才能用,然後 client
裡也很貼心的整合了 Django 的認證授權體系,直接 login
就完事了。
注意測試的時候是自動建立了臨時資料庫,所以得先新增使用者。
接著呼叫 self.client.get
, self.client.post
之類的方法去測試介面就好了。
from django.test import TestCase
from django.shortcuts import reverse
from django.contrib.auth.models import User
from rest_framework import status
class DashboardTests(TestCase):
def setUp(self):
User.objects.create_user(username='user', password='pwd')
self.client.login(username="user", password="pwd")
def test_overview(self):
resp = self.client.get(reverse('dashboard:overview'), {'grant_year': 2023, 'grant_month': 2})
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(resp.json()['code'], status.HTTP_200_OK)
def test_monthly_data(self):
resp = self.client.get(reverse('dashboard:monthly_data'), {'grant_year': 2023, 'grant_month': 2})
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(resp.json()['code'], status.HTTP_200_OK)
def test_county_data(self):
resp = self.client.get(reverse('dashboard:county_data'), {'grant_year': 2023, 'grant_month': 2})
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(resp.json()['code'], status.HTTP_200_OK)
執行測試
測試寫完之後
使用命令執行測試
python manage.py test
還有其他引數請參考官方文件
不過執行測試的時候就沒有 AspNetCore 爽了,沒有那種一個個介面相繼亮起綠燈的快感;
Django 的測試只能看到測試結果,有多少個測試透過了,如果有報錯會看到 Traceback 資訊。
$ python .\manage.py test
Found 5 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.....
----------------------------------------------------------------------
Ran 5 tests in 0.831s
OK
Destroying test database for alias 'default'...
小結
Django 還是熟悉的味道,好用就對了。
參考資料
沒想到不知不覺中 Django 刷版本號到 5.0 了…
- https://docs.djangoproject.com/zh-hans/5.0/topics/testing/