Python
Django
unittest
Webアプリケーション
自動テスト

テスト駆動開発とは

テスト駆動開発とは,まずテストプログラムから作成し,そのテストプログラムの条件を満たすようにプログラムを組み立てていく開発方法のことを言います.

開発環境

PC : Spectre X 360
OS : Ubuntu17.04
言語 : Python3.6.3
フレームワーク :Django
その他ライブラリ :Selenium

開発

環境構築

まず環境はpython仮想環境で作ろうと思います.パッケージのインストールから.

$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install virtualenvwrapper
$ sudo apt-get install virtualenv

myvenvという名前で仮想環境を構築します.

$ mkdir django-tdd
$ cd django-tdd
$ virtualenv --pytohn=python3.6 myvenv
$ source myvenv/bin/activate

seleniumでブラウザを動かすにはドライバが必要になります.
今回はどのOSでも使えるFireFoxを使おうと思います.
https://github.com/mozilla/geckodriver/releasesf
から自分の環境にあったものをダウンロードしてきて解答し,pathの通ったディレクトリusr/local/bin/などの場所に置きます.僕の場合はgeckodriver-v0.19.1-linux64.tar.gzです.
違うブラウザでテストしたい場合(Chrome,safariなど)を使いたい場合は公式サイトなどからダウンロードしてくれば問題なくテストできます.

次に必要パッケージをインストールします.

(myvenv)$ pip install django
(myvenv)$ pip install selenium
(myvenv)$ pip install pillow

早速アプリケーションを作っていきます.
今回作るアプリケーションはTo-Doリストアプリです.名前は『superlists』にしておきましょう.

(myvenv) $ django-admin startprojects superlists

TDDなので,まずはテストから書いて見ます.
TDDの原則は『テストを書くまでは何もしてはいけない』です.
はじめに書くテストはFunctional Testから記述します.Functional Testはユーザ観点のテストであり,ユーザの行動を記述します.
まずはテストのコメントから書いて行きます.
ストーリーは小宮山くん(仮名)がサイトを訪れ,サイトを使ってサイトの機能を理解するというものです.

functional_test.py
from selenium import webdriver

browser = webdriver.Firefox()

#小宮山くんは素晴らしいToDoリストサイトがあると聞き,サイトを訪れる.
browser.get('http://localhost:8000')

#彼はtitleやheaderに『To-Do』と書いてあるためこのサイトが
# To-Doサイトで間違いないことを確認する
assert 'To-Do' in browser.title

#すぐにTo-Doアイテムに入る様に促される

#彼は『孔雀のはねを買う』を予定に追加

#彼がenterキーを押すと,ページが更新され
# ページのリストに"1:孔雀の羽を買う"が追加されている

#ページはまだ入力を促すような画面になっている.
# 彼は『孔雀の羽で飛ぶ』を追加する.

#enterするとページが更新され,pageのリストが2つになっている.

#彼は「このサイトはどうやってそれぞれのユーザのタスクを管理しているのだろう」
#と考える.サイトにはわかりやすくそれを説明する文章がある.
#それを読むと個々のユーザごとに一意なアドレスを割り当てているようである.

#彼がそのurlを訪れるとなるほどタスクリストに辿りつけた

#彼は満足して安眠できたのだった.

コメントについて

コメントはユーザがサイトを使う際のストーリーとなっています.
このようにストーリーをもとにプログラムを構築することで,常にユーザの側に立った(逆に言えば開発者の自己満足にならない)開発に近づけることができます.
また,このテストのコメントを開発の他のメンバー(プログラマでない人)に見せることで,このサイトの役割を率直に伝えることができます.

実行

(myvenv)$ python manage.py makemigrations
(myvenv)$ python manage.py migrate
(myvenv)$ python manage.py runserver

これで,開発用サーバーがローカルで起動します.
他の端末でテストプログラムを実行します.

$ python functional_test.py 
Traceback (most recent call last):
  File "functional_test.py", line 11, in <module>
    assert 'To-Do' in browser.title
AssertionError

AssertionErrorが出ました.
僕達はこれを事を予想された失敗と呼びます.
テストの成功ほどではないですが,正しい理由での失敗は前進の証拠です.

スタンダードライブラリ unittest

ここで2つ問題があります.
1つめ,AssertionErrorというメッセージはerrorとしては少し不親切です.
これは以下のように記述することでもっとわかりやすくできます.

functional_test.py
assert 'To-Do' in browser.title ,"Browser title was " + browser.title

2つめ,テストが終わってもブラウザが閉じないのが少し鬱陶しいです.
これはtry finallyを使えば回避できます.

この2つの問題はTDDでは一般的な問題であるため,pythonではこの問題を解決するためのモジュールが用意されています.それがunittestです.

from selenium import webdriver
import unittest

class NewVisitorTest(unittest.TestCase):
    def setUp(self):
        self.browser = webdriver.Firefox()
    def tearDown(self):
        self.browser.quit()

    def test_can_start_a_list_and_retrieve_it_later(self):
        #小宮山くんは素晴らしいToDoリストサイトがあると聞き,サイトを訪れる.
        self.browser.get('http://localhost:8000')

        #彼はtitleやheaderに『To-Do』と書いてあるためこのサイトが
        # To-Doサイトで間違いないことを確認する
        self.assertIn('To-Do',self.browser.title)
        self.fail('Finish the Test!!')
        #[以下ストーリー略]...
if __name__ == '__main__':
    unittest.main(warnings='ignore')

ポイント

  • テストはunittest.TestCaseを継承したクラスでまとめられます.
  • クラス内のtestで始まるすべてのメソッドがテストメソッドとなります.
  • テストメソッドはクラス内に一つ以上いくつでも持つことができます.
  • setUpはすべてのテストのはじめに,tearDownはすべてのテストの終わりにそれぞれ必ず実行されます.
  • unittestはassertの代わりに使えて,更に便利なメソッド(assertIn,assertEqual...など)を提供します.他にも色々あるので探して見ましょう.(https://docs.python.org/3/library/unittest.html)
  • self.failはエラーメッセージを吐き必ずfailします.今回はこれをテスト終了の合図で使っています.
  • 最後にメイン部分でunittest.main()を呼んでいます.これはすべてのテストを実行します.
  • warnings=ignoreは書いている際のResourceWarningを消してくれます.これがあるとなかなかイライラします.

続き→(https://qiita.com/ogihara/private/6a99f6325ea6af0440f0)