今回はunit testを書いていきます.(前回使用したライブラリのunittestではなくテストの種類です!!まぎらわしいですね!!)
#unittestとfunctinal testの違いって?
完結にいうとunit testは内部(プログラマ)の観点から見たテスト,functional test は外部(ユーザ)の観点から見たテストです.
どのように使い分けたらいいのか考えてしまいますが,ワークフローを下のようにしてそれぞれを使い分けるといいでしょう.
- まず作りたいサイトはユーザの観点から見たときどのような動きになるのかを考え,それをストーリーにした後,functional testを作成します.
- functional testがfailするのを確認したら,このテストを満たすのにどんな機能が必要であるか考えます.ここで必要な機能をいくつかのunittestを使って記述します.
- unittestがfailするのを確認したら,これを満たすためのコードを試行錯誤します.
- すべてのunittestを通過させたとき,functional testの結果がはじめより改善されていることを確かめます.ここで2に戻りfunctional test が通過するまで繰り返します.
#Djangoでunit test
テストを始める前に,今のままではなんのアプリケーションもないのでlistsという名前でアプリケーションを一つ作りましょう.
$ python manage.py startapp lists
listsという名前でアプリケーションが一つできました.次にTestCaseを使ってテストを書いていきましょう.
##TestCase
Djangoにはtestのために用意されたTestCaseというものがあります.
これはその名の通り,unittest.TestCaseを拡張させたものです.
このTestCaseを継承したクラスはtestが実行されると自動的にsすべて実行されます(実行コマンドは後述).
早速テストを書いて行きたいのですが,その前に少しDjangoの理解を深めておきます.
###DjangoにおけるMVC,Url,View関数
DjangoはMVCでファイルを分けています.(MVCを知らない人は調べてください.)
Djangoでは以下のようにファイルわけがされています.
- Model : models.py
- View : templateファイル
- Controll :views.py
それぞれのファイルはどのように,連携してうごくのでしょうか.例えば,ユーザがサイトでurlを打ってきたとき(リンク先を踏んだとき)Djangoで作ったアプリの動きを順番ごとに並べると
- HTTPリクエストを受け取る
- 受け取ったリクエストをどのview関数で処理するか決定する.
- 処理結果をHTTPレスポンスとして返す
てな感じです.おそらく他のwebアプリケーションでもこれに近い形になっていると思います.
勉強はこの辺にしておいて,いよいよテストを記述していきます.
まずは何をテストするべきかをまとめて見ましょう.
- サイトのルートURL('/')に入ったとき,正しく処理できるか
- functional testを満たすようなHTMLレスポンスを返すことができるか
ですね.実際にコードを書いてみます.
# -*- coding: utf-8 -*-
from django.test import TestCase
from django.urls import resolve
from lists.views import home_page
class HomePageTest(TestCase):
def test_root_url_resolves_to_home_page_view(self):
found = resolve('/')
self.assertEqual(found.func,home_page)
resolve関数はurlを受け取ったとき,どのview関数にrequestが渡されるのかを確かめるのに使います.
これを実行すると以下のようになります.
$ python manage.py test
ImportError: cannot import name 'home_page'
expected error(予測されたエラー)です.成功ですね.
#urls.py
まずはurlの扱いから記述してみましょう.
Djangoはurlを正規表現を使って扱います.
まずは正規表現**^$**を使います.これは空白文字を表す正規表現で,urlのルートを表すことができます.
from django.conf.urls import url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$',views.home_page,name='home'),
]
テストしてみましょう
$ python manage.py test
AttributeError: module 'lists.views' has no attribute 'home_page'
list.viewsがhome_page属性を持っていないということなので作ります.
from django.shortcuts import render
# Create your views here.
def home_page():
pass
テストします.
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Destroying test database for alias 'default'...
成功しました!!
#ViewのUniteTest
先程のテストではhome_pageは何も返していなかったので,実際のHTMLで返してみましょう.
まずはlists/test.pyを変更します.
# -*- coding: utf-8 -*-
from django.test import TestCase
from django.urls import resolve
from lists.views import home_page
from django.http import HttpRequest
class HomePageTest(TestCase):
def test_root_url_resolves_to_home_page_view(self):
found = resolve('/')
self.assertEqual(found.func,home_page)
def test_home_page_returns_correct_html(self):
request = HttpRequest()
response = home_page(request)
html = response.content.decode('utf8')
self.assertTrue(html.startswith('<html>'))
self.assertIn('<title>To-Do lists</title>',html)
self.assertTrue(html.endswith('</html>'))
このテスト(test_home_page_returns_correct_html)でしていることを順に説明していきます.
- HttpRequestを作成し,ユーザがブラウザでページを要求した状況を作る
- リクエストをhome_page関数に投げレスポンスを受け取る
- レスポンスからcontentメソッドで中身を抜き出す.この中身はraw bytes(1,0のバイト列)なのでdecodeでHTML文字列に変換する.
- htmlタグで囲まれていること,titleが正しいことを確認
テストを実行します.
$ python manage.py test
TypeError: home_page() takes 0 positional arguments but 1 was given
expected error(予測されたエラー)ですね.
##Code Cycle
ここから先は以下に示す単純なサイクルになります.
- テストを実行してfailしている部分を確認.
- failをpassするような最小の変更を施す.
はじめのうちは少し面倒に感じますが,完成までの最短の道のりを進んでいることにそのうち気づくはずです.
###変更
from django.shortcuts import render
# Create your views here.
def home_page(request):
pass
###テスト
$ python manage.py test
html = response.content.decode('utf8')
AttributeError: 'NoneType' object has no attribute 'content'
###変更
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def home_page(request):
return HttpResponse()
###テスト
$ python manage.py test
self.assertTrue(html.startswith('<html>'))
AssertionError: False is not true
.
.
.
.
略
.
.
###変更
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def home_page(request):
return HttpResponse('<html><title>To-Do lists</title></html>')
###テスト
$ python manage.py test
Ran 2 tests in 0.001s
OK
unittestが通りました!!
さあ,もう一度functional_test.pyを実行してみましょう.(もちろん開発用サーバーを起動した状態でね.)
$ python functional_test.py
python functional_test.py
F
======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "functional_test.py", line 18, in test_can_start_a_list_and_retrieve_it_later
self.fail("Finish The Test!!")
AssertionError: Finish The Test!!
こんな感じで開発をしていきます.