相關(guān)資源
簡單的例子
from django.test import TestCase
from myapp.models import Animal
# Django的單元測試基于unittest庫
class StudentTestCase(TestCase):
# 測試函數(shù)執(zhí)行前執(zhí)行
def setUp(self):
print("======in setUp")
# 需要測試的內(nèi)容
def test_add(self):
student = Student(name='aaa')
student.save()
self.assertEqual(student.name, 'aaa')
# 需要測試的內(nèi)容
def test_check_exit(self):
self.assertEqual(0, Student.objects.count())
# 測試函數(shù)執(zhí)行后執(zhí)行
def tearDown(self):
print("======in tearDown")
關(guān)于django的單元測試,需要知道的是
- 對于每一個測試方法都會將setUp()和tearDown()方法執(zhí)行一遍
- 會單獨(dú)新建一個測試數(shù)據(jù)庫來進(jìn)行數(shù)據(jù)庫的操作方面的測試幸冻,默認(rèn)在測試完成后銷毀宫盔。
- 在測試方法中對數(shù)據(jù)庫進(jìn)行增刪操作,最后都會被清除拷恨。也就是說,在test_add中插入的數(shù)據(jù),在test_add測試結(jié)束后插入的數(shù)據(jù)會被清除。
- django單元測試時為了模擬生產(chǎn)環(huán)境翼岁,會修改settings中的變量,例如, 把DEBUG變量修改為True, 把ALLOWED_HOSTS修改為[*]绳姨。
運(yùn)行單元測試
在單元測試中登澜,可以指定測試粒度。這樣可以專注于只測試還沒測試的單元測試飘庄,而已經(jīng)測試過的就不測試了脑蠕。
# 測試整一個工程
$ ./manage.py test
# 只測試某個應(yīng)用
$ ./manage.py test app --keepdb
# 只測試一個Case
$ ./manage.py test animals.tests.StudentTestCase
# 只測試一個方法
$ ./manage.py test animals.tests.StudentTestCase.test_add
一些常見問題的解決
數(shù)據(jù)表多時創(chuàng)建數(shù)據(jù)庫銷毀過多時間
在單元測試時,若migrations的文檔過多時跪削,每次單元測試時間絕大部分都消耗在數(shù)據(jù)庫的創(chuàng)建谴仙。試過,單元測試代碼運(yùn)行只要幾十秒碾盐,而數(shù)據(jù)庫的創(chuàng)建卻用去了十分鐘晃跺。這是個讓人絕望的速度,萬幸的是django有提供命令使用進(jìn)行單元測試過后不刪除數(shù)據(jù)庫毫玖。 這個命令就是: --keepdb
指定測試數(shù)據(jù)庫的默認(rèn)字符集
在創(chuàng)建測試數(shù)據(jù)庫時掀虎,數(shù)據(jù)庫的默認(rèn)字符集也許不是我們想要的例如latin1「斗悖可以通過在數(shù)據(jù)庫配置中指定TEST_CHARSET, TEST_COLLATION 參數(shù)烹玉,來指定字符集以及排序規(guī)則
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'xxxx',
'USER': 'xxxx',
'PASSWORD': '',
'HOST': 'localhost',
'PORT': '3306',
'OPTIONS': {
'charset': 'utf8mb4',
'init_command': 'SET default_storage_engine=INNODB',
},
'TEST_CHARSET': 'utf8',
'TEST_COLLATION': 'utf8_general_ci',
},
}
** settings變量的修改**
若干需要在單元測試時修改,setting命令阐滩。例如二打,django在單元測試時會將settings.DEBUG 設(shè)置為True, 而我們需要將其設(shè)置為False
方式1: 直接在修改
class BaseApiTest(TestCase):
def setUp(self):
#testcase DEBUG = False
settings.DEBUG = False
def test_b(self):
self.assertEqual(2, 1+1)
def tearDown(self):
pass
方式2: 通過裝飾器修改
from django.test.utils import override_settings
class BaseTest(TestCase):
def setUp(self):
pass
# 利用該裝飾器,可以在但個測試函數(shù)內(nèi)修改settings變量, 而不影響
@override_settings(DEBUG=False)
def test_b(self):
self.assertEqual(2, 1+1)
def tearDown(self):
pass
API權(quán)限問題的解決
在測試API的時候掂榔,往往需要等等進(jìn)行用戶登錄才有權(quán)限調(diào)用继效,此時需要指定登錄用戶來解決接口調(diào)用的權(quán)限問題
# 如果是API使用了rest_framework框架
from rest_framework.test import APIClient
class BaseTest(TestCase):
def setUp(self):
# 創(chuàng)建一個用戶
self.user = create_user(uuid4().hex, '123456789')
self.client = APIClient()
# 通過force_authenticate函數(shù)來執(zhí)行用戶
self.client.force_authenticate(self.user)
def test_b(self):
self.assertEqual(2, 1+1)
def tearDown(self):
pass
Celery異步任務(wù)的測試
在代碼中幾乎肯定是會有celery異步任務(wù),若想對異步任務(wù)進(jìn)行單元測試装获∪鹦牛可以將CELERY_ALWAYS_EAGER=True, BROKER_BACKEND='memory'
from xxx.celery import app
@app.task(bind=True)
def add(self,x, y):
return x + y
class TaskTest(TestCase):
def setUp(self):
settings.CELERY_ALWAYS_EAGER = True
def test_add(self):
self.assertEqual(2, add.apply_async((1,1)))
def tearDown(self):
pass
單元的等級(來自知乎的gashero)
在知乎上看到gashero根據(jù)經(jīng)驗總結(jié)出來的單元測試總結(jié),非常認(rèn)同穴豫。根據(jù)功能的重要性喧伞,來進(jìn)行不同程度的測試。
- Level1:正常流程可用,即一個函數(shù)在輸入正確的參數(shù)時潘鲫,會有正確的輸出
- Level2:異常流程可拋出邏輯異常翁逞,即輸入?yún)?shù)有誤時,不能拋出系統(tǒng)異常溉仑,而是用自己定義的邏輯異常通知上層調(diào)用代碼其錯誤之處
- Level3:極端情況和邊界數(shù)據(jù)可用挖函,對輸入?yún)?shù)的邊界情況也要單獨(dú)測試,確保輸出是正確有效的
- Level4:所有分支浊竟、循環(huán)的邏輯走通怨喘,不能有任何流程是測試不到的
- Level5:輸出數(shù)據(jù)的所有字段驗證,對有復(fù)雜數(shù)據(jù)結(jié)構(gòu)的輸出振定,確保每個字段都是正確的地方
小小的感想
單元測試是需要時間必怜,若要把各種情況都測試一遍,也許單元測試的編寫時間要比寫代碼的時間還要長后频。目前由于時間關(guān)系梳庆,比較少寫單元測試,但我很是期望能擠出時間盡量的編程單元測試卑惜。對于我來說膏执,單元測試存在的意義就是,可以讓我放心的重構(gòu)代碼露久,可以在重構(gòu)代碼的時候省下測試重構(gòu)的代碼能否正確運(yùn)行的時間更米。