Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 5 years have passed since last update.

Djangoでのテスト駆動開発(2)

Last updated at Posted at 2017-11-13

今回はunit testを書いていきます.(前回使用したライブラリのunittestではなくテストの種類です!!まぎらわしいですね!!)
#unittestとfunctinal testの違いって?
完結にいうとunit testは内部(プログラマ)の観点から見たテスト,functional test は外部(ユーザ)の観点から見たテストです.
どのように使い分けたらいいのか考えてしまいますが,ワークフローを下のようにしてそれぞれを使い分けるといいでしょう.

  1. まず作りたいサイトはユーザの観点から見たときどのような動きになるのかを考え,それをストーリーにした後,functional testを作成します.
  2. functional testがfailするのを確認したら,このテストを満たすのにどんな機能が必要であるか考えます.ここで必要な機能をいくつかのunittestを使って記述します.
  3. unittestがfailするのを確認したら,これを満たすためのコードを試行錯誤します.
  4. すべての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で作ったアプリの動きを順番ごとに並べると

  1. HTTPリクエストを受け取る
  2. 受け取ったリクエストをどのview関数で処理するか決定する.
  3. 処理結果を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のルートを表すことができます.

superlists/urls.py

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属性を持っていないということなので作ります.

lists/views.py
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を変更します.

lists/tests.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)でしていることを順に説明していきます.

  1. HttpRequestを作成し,ユーザがブラウザでページを要求した状況を作る
  2. リクエストをhome_page関数に投げレスポンスを受け取る
  3. レスポンスからcontentメソッドで中身を抜き出す.この中身はraw bytes(1,0のバイト列)なのでdecodeでHTML文字列に変換する.
  4. htmlタグで囲まれていること,titleが正しいことを確認

テストを実行します.

$ python manage.py test
TypeError: home_page() takes 0 positional arguments but 1 was given

expected error(予測されたエラー)ですね.

##Code Cycle
ここから先は以下に示す単純なサイクルになります.

  1. テストを実行してfailしている部分を確認.
  2. failをpassするような最小の変更を施す.

はじめのうちは少し面倒に感じますが,完成までの最短の道のりを進んでいることにそのうち気づくはずです.

###変更

lists/views.py
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'

###変更

lists/views.py
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

.
.
.
.

.
.
###変更

lists/views.py
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!!

こんな感じで開発をしていきます.

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?