在進行 django 的測試過程中,經常會遇到需要登錄的情況。並且,登錄還分普通的賬號密碼登錄和oauth的方式進行登錄。雖然登錄是一件比較麻煩的事情,但是大多數時候我們都可以採用一定的方式將這個環節繞過去。在進行這一步驟之前,我們先簡單說下django對需要發請求的單元測試方案。
測試請求
在 django 中進行 request 的發送有幾種方式,這裏簡單列一下:
- 使用 TestCase 下的 Client 進行請求
- 使用 RequestFactory 構建 request 進行測試
- 使用最簡單的 requests 庫進行單元測試(比較少用)
- 在引入 django-rest-framework 的情況下,還可以使用 ApiTestCase 進行單元測試,裏面對 Client 又做了一層封裝
- 除此之外,還有其他一些測試方式,就不再贅述
解決登錄
屏蔽中間件
有一種比較暴力的方式,就是把django工程中有關於登錄的中間件全部屏蔽。雖然這不是一種好的方案,但是可以短暫解決一些不需要獲取登錄態的視圖函數的測試。
在django中有個默認的用戶認證中間件。django.contrib.auth.middleware.AuthenticationMiddleware
可以在settings.py的中間件配置中註釋掉。如果有其他登錄相關的中間件,也需要註釋掉。雖然這樣可以解決簡單登錄的問題,但是並不是一種友好的測試方式。修改了原先的工程代碼。
使用mock和patch
python 有個非常好用的測試框架mock,可以提供非常多的測試方案。比方說剛纔的操作,我們可以在單元測試的函數上加上@patch('django.contrib.auth.middleware.AuthenticationMiddleware', return_value=None)
的裝飾器,這樣就可以不用修改settings.py的代碼,還可以繞過登錄中間件。但是這樣依舊不能解決視圖函數中需要獲取用戶登錄後的信息的問題。
主動登錄
django的client提供了登錄函數 login(username, password),在簡單情況下,可以使用這個login函數登錄,然後就可以正常執行所有中間件和視圖函數。但是這個函數有點問題,就是有些用戶是不會有密碼的,而是利用token之類的方式進行登錄,如oauth2之類的方式。
force_login和force_authenticate
在django中有這樣的一個函數force_login(user, backend=None)
,如果使用django-rest-framework的話,也會有force_authenticate(user=None, token=None)
的函數。在使用client登錄後,可以在view函數中獲取用戶的登錄信息。不過這就有個問題,在中間件中是沒有辦法獲取到登錄態的。而且force_login函數在django1.9之前是沒有的。
get_user
最終極的解決方案,其實我們可以看一下django的django.contrib.auth.middleware.AuthenticationMiddleware
裏面的邏輯。從中發現一些貓膩。
class AuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
assert hasattr(request, 'session'), (
"The Django authentication middleware requires session middleware "
"to be installed. Edit your MIDDLEWARE%s setting to insert "
"'django.contrib.sessions.middleware.SessionMiddleware' before "
"'django.contrib.auth.middleware.AuthenticationMiddleware'."
) % ("_CLASSES" if settings.MIDDLEWARE is None else "")
request.user = SimpleLazyObject(lambda: get_user(request))
我們可以發現,有個函數get_user
,源代碼如下:
def get_user(request):
if not hasattr(request, '_cached_user'):
request._cached_user = auth.get_user(request)
return request._cached_user
從上我們可以看出,get_user
函數其實就是中間件獲取用戶的邏輯,我們只要mock掉這個函數就可以了。@patch('django.contrib.auth.middleware.get_user', return_value=User.objects.first())
只要把用戶model注入到 patch的return_value中去就可以了。解決了中間件和view函數登錄態的問題。