Djangoの学習⑦（バグをあぶり出すためにテストを作成する）

アプリケーションのテストを書く場所は、慣習として、
アプリケーションの tests.py ファイル内ということになっています。
テストシステムが test で始まる名前のファイルの中から、自動的にテストを見つけてくれます。

## テストコードの作成

polls アプリケーションの tests.py ファイルに次のコードを書きます。

```python:polls/tests.py

import datetime

from django.test import TestCase
from django.utils import timezone

from .models import Question

# Create your tests here.
class QuestionModelTests(TestCase):
    def test_was_published_recently_with_future_question(self):
        """
        was_published_recently() returns False for questions whose pub_date
        is in the future
        """
        time = timezone.now() + datetime.timedelta(days=30)
        future_question = Question(pub_date=time)
        self.assertIs(future_question.was_published_recently(), False)

```

ここでは、was_published_recently() の出力をチェックしています。これは False になるはずです。



## テストの実行

ターミナルから、次のコマンドでテストが実行できます。

```
$ python manage.py test polls

Found 1 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests)
was_published_recently() returns False for questions whose pub_date
----------------------------------------------------------------------
Traceback (most recent call last):
  File "\path\to\mysite\polls\tests.py", line 17, in test_was_published_recently_with_future_question
    self.assertIs(future_question.was_published_recently(), False)
AssertionError: True is not False

----------------------------------------------------------------------
Ran 1 test in 0.100s

FAILED (failures=1)
Destroying test database for alias 'default'...

```

最後に、 assertIs() メソッドを使うことで、本当に返してほしいのは False だったにもかかわらず、 
was_published_recently() が True を返していることを発見します



## バグを修正する

問題の原因は、Question.was_published_recently() は 
pub_date が未来の日付だった場合には False を返さなければならない、
ということです。

models.py にあるメソッドを修正して、
日付が過去だった場合にのみ True を返すようにしましょう。


```python:polls/models.py
	def was_published_recently(self):
		now = timezone.now()
		return now - datetime.timedelta(days=1) <= self.pub_date <= now
```


そして、もう一度テストを実行します。
```
> python manage.py test polls
Found 1 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Destroying test database for alias 'default'...
```

コード内のバグを直したので、テストは無事にパスされました。


## より包括的なテスト

実際、一つのバグを直したことで
ほかのバグを作り出すことがないようにしなければならないです。
このメソッドの振る舞いをより包括的にテストするために、
同じクラスにさらに2つのテストを追加しましょう。

```python:polls/tests.py

    def test_was_published_recently_with_old_question(self):
        """
        was_published_recently() returns False for questions whose pub_date
        is older than 1 day.
        """
        time = timezone.now() + datetime.timedelta(days=1, seconds=1)
        old_question = Question(pub_date=time)
        self.assertIs(old_question.was_published_recently(), False)

    def test_was_published_recently_with_recent_question(self):
        """
        was_published_recently() returns True for questions whose pub_date
        is within the last day.
        """
        time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)
        recent_question = Question(pub_date=time)
        self.assertIs(recent_question.was_published_recently(), True)


```

これで、Question.was_published_recently() が
過去、現在、そして未来の質問に対して意味のある値を返すことを確認する3つのテストが揃いました。

そして、もう一度テストを実行します。

```
> python manage.py test polls
Found 3 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...
----------------------------------------------------------------------
Ran 3 tests in 0.002s

OK
Destroying test database for alias 'default'...
```

3つとも成功しました。