DjangoのTestCaseを使ってみた

##今回の内容
Djangoで作成した電子ジャーナルサイトをテストしてみました。

##準備
複数のテストファイルを管理しやすいように、Djangoのアプリケーションディレクトリ（今回はjournals）直下に``tests``ディレクトリ、さらにその中に``test_urls.py``ファイル、``test_views.py``ファイル、``test_models.py``ファイル、``__init__.py``ファイルを作ります。
ファイル名は``test_``で始めないとDjangoがテスト用のファイルと認識してくれないので注意してください。
また、デフォルトで作られているtests.pyは削除します。


##URL test
まず簡単なURLのテストを行います。
テスト内容は、「特定のURLでの動作が、そのURLのViewと一致しているか」です。

```python:urls.py

from django.urls import path
from . import views

urlpatterns = [
     path('signup/', views.signup, name='signup'),
     path('journal/<int:pk>/', views.journal_detail, name='journal_detail'),

...

]
```
今回はこの``signup``URLが``signup``Viewと一致しているかをテストします。

```python:test_urls.py
from django.test import TestCase
from django.urls import reverse, resolve
from journals.views import signup

class TestUrls(TestCase):
    def test_signup_url(self):
        url = reverse('signup')
        self.assertEqual(resolve(url).func, signup)

```

・TestCaseクラス　：　Djangoに標準的に組み込まれているunittestの拡張機能。最初にインポートしておき、今後作成するテストクラスはこのTestCaseクラスを継承します。
・reverse関数　：　引数のURLを返します。signupという名前のURLを返しています。
・resolve関数　：　URLのpathをResolveMatchオブジェクトにして返す。このオブジェクトからURLの様々な情報にアクセスできる。
・func　：　ResolveMatchオブジェクトのURLを使用するために使われるView関数を示す。
・assertEqual関数　：　第一引数と第二引数の値が等しいかどうかを返す。


##View test
今回は「特定のURLにおけるレスポンスが適切か」、「特定のURLに一致したtemplatesを出力できているか」をテストします。

```python:views.py
def signup(request):
    if request.method=='POST':
        form=UserCreationForm(request.POST)
        if form.is_valid():
            new_user=form.save()
            input_username=form.cleaned_data['username']
            input_password1=form.cleaned_data['password1']
            input_password2=form.cleaned_data['password2']
            new_user=authenticate(username=input_username, password=input_password1)
            if new_user is not None:
                login(request, new_user)
                return redirect('/')
    else:
        form=UserCreationForm()
    return render(request, 'journals/signup.html', {'form':form})
```

```python:test_views.py
from django.test import TestCase, Client
from django.urls import reverse
from journals.models import Journal

class TestViews(TestCase):

    def setUp(self):
        self.client = Client()
        self.index_url = reverse('index')
        self.detail_url = reverse('journal_detail', args=[1])
        self.apple = Journal.objects.create(
            author = 'apple',
            title = 'testjournal'
        )
    
    def test_index_view(self):
        response = self.client.get(self.index_url)
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'journals/index.html')

    def test_journal_view(self):
        response = self.client.get(self.detail_url)
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'journals/journal_detail.html')


```
・Clientクラス　：　Djangoテスト用のダミーブラウザクラス。これによりWebサーバーを起動せずにテストを実行することができる。Viewのテストに使われる。
・setUp関数　：　全てのテスト関数が呼び出される前に呼ばれる関数。予め諸々を設定しておく。detail_urlには引数（args）にpkとして１を設定しておきます。これは``urls.py``でわかるようにdetail_urlはpkを必要とするからです。今回はテスト用に使うJournalクラスのインスタンスを作成しています。このインスタンスを作っておかないと動作確認をするべきオブジェクトがないことになるため、後述するテスト内容が失敗してしまいます。
・statuscode　：　responseの状態を示す番号や名前がdjangoでは予め設定されている。200はOK、404はNot_Found。setUp関数でインスタンスを作っておいたのは``test_journal_view``関数でstatuscodeを200にするため。作らなければインスタンスが存在しないためresponseは404になる。
・assertTemplateUsed　：　第二引数のtemplateが第一引数のresponseで呼び出されているかを確認する。

##Model test
本当はモデル内に定義されている関数の動作をテストすべきなのですが、今回は関数を作成していないのでインスタンスのtitleの文字数が制約を守れているかを確認してみました。

```python:models.py
class Journal(models.Model):
    author = models.CharField(blank=False, null=False, max_length=100)
    title = models.CharField(blank=False, null=False, max_length=150)
    text = models.TextField(blank=True)
    created_datetime = models.DateTimeField(default=timezone.now)
    updated_datetime = models.DateTimeField(blank=True, null=True)
```

```python:test_models.py
from django.test import TestCase
from journals.models import Journal

class TestModels(TestCase):

    def setUp(self):
        self.apple = Journal.objects.create(
            author = 'apple',
            title = 'testjournal',
            text ='testtext'
        )

    def test_text_max_length(self):
        words_count = len(self.apple.title)
        self.assertTrue(words_count <=150)
```
・assertTrue関数　：　引数の値がTrueかどうかを確認する

##参考
・[Django Documentation(Reverse, Resolve)](https://docs.djangoproject.com/en/3.0/ref/urlresolvers/)
・[Status Codes](https://www.django-rest-framework.org/api-guide/status-codes/)
・[Django Documentation(The Test Client)](https://docs.djangoproject.com/en/3.0/topics/testing/tools/)
・[Django Test Tutorial](https://www.youtube.com/watch?v=qwypH3YvMKc&list=PLbpAWbHbi5rMF2j5n6imm0enrSD9eQUaM)