【Django】 標準のLoginFormをテストする 【TDD】

Djangoでテストをかこうとして、LoginFormってどうやってテストすりゃいいの…て仮名にテストすればいいの…？
と思いながらテスト作ったので、備忘録としてメモを残します。

# 環境
||バージョン|
|:---:|:---:|
|python|3.7.4|
|Django|2.2.5|

テストはDjango標準のもの(`unittest`っていうのかな？)を使っています。

# 何をテストすればいいのか
まず何をテストすればいいのかを考えます。
結論としては以下をテストすることにしました。

- ユーザー名が異なる場合エラーになるか
- パスワードが異なる場合エラーになるか
- 正しい入力の時にエラーにならないか

以上の3つです。
いろいろ考えていたのを下に列挙してみますが、テストしなかった理由も併せて書いておきます。

|テストしたいこと|テストしなかった理由|
|:---:|:---:|
|フォームのバリデーション(使えない文字)チェック|必要なのはユーザー登録のとき(登録できなければログインできないため)|
|ログインできたかの確認|CSRFトークンがあるため、ログインのテストはできないっぽい？|
|client.loginを使ったログインチェック|formを使ってるわけではないから必要なさそう|

以上になります。
読み返してみると、LoginFormとLoginViewがごちゃごちゃになってる気がします。
ただ、LoginViewでは、LoginFormしか使っていないので、Formsのテストだけすればいいのかなと思います。

# ViewsとFormsのコード
今回テストするFormsと、利用しているViewsのコードです。

```python:Views.py
from django.contrib.auth.views import LoginView
from ..forms import LoginForm

class Login(LoginView):
    form_class = LoginForm
    template_name = 'account_management/login.html'
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs) # はじめに継承元のメソッドを呼び出す
        context["username"] = self.request.user.username
        return context
```

```python:Views.py
from django.contrib.auth.forms import AuthenticationForm

class LoginForm(AuthenticationForm):
    """ログインフォーム"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs['class'] = 'form-control'
            field.widget.attrs['placeholder'] = field.label  # placeholderにフィールドのラベルを入れる
```

# testコードを書いていく
では上記のFormsに対してテストコードを書いていきます。
testは`tests.py`に記述していくことになります。(ディレクトリを変更している場合は適宜読み替えてください。)
クラス名、メソッド名は`test`から始まる必要があります。
コードは以下になります。


```python:Views.py
from django.test import TestCase
from django.contrib.auth.models import User
from ..forms import LoginForm

class Test_LoginForm(TestCase):

    def setUp(self):
        User.objects.create_user(username='test_account', email='test_account@aaa.bbb', password='test_pass') 
   
    def test_LoginForm_name_error(self):
        form_data = {
            'username': 'error',
            'password': 'test_pass'
        }
        form = LoginForm(data=form_data)
        self.assertFalse(form.is_valid(), 'ユーザー名で正しくエラーが出ませんでした。')
    
    def test_LoginForm_pass_error(self):
        form_data = {
            'username': 'test_account',
            'password': 'error'
        }
        form = LoginForm(data=form_data)
        self.assertFalse(form.is_valid(), 'パスワードで正しくエラーが出ませんでした。')

    def test_LoginForm_true(self):
        form_data = {
            'username': 'test_account',
            'password': 'test_pass'
        }
        form = LoginForm(data=form_data)
        self.assertTrue(form.is_valid(), '正しくフォームが動作しませんでした。')

```

ではクラスの中を見ていきます。
`setUp`は、クラス内のメソッドが実行される直前に実行してくれるものになります。
Djangoのテストは、一回のテストごとにDBを作り直してくれるので、毎回ユーザーを定義してあげる必要があります。
なので、今回は`setUp`の中でユーザーを定義してあげています。

続いて各テストメソッドです。３つとも特に大きな違いはないので一緒に説明します。
LoginFormでは、ユーザー名とパスワードを入れて、それぞれが正しければ、ログインできます。
`form_data`ではどのフィールドに何を渡すかを辞書型で記述しています。
そして、LoginFormにデータを渡します。

この渡した値が、(今回はログインなので)登録したユーザー名、パスワードの組と正しければ`form.is_valid()`は`True`を返してくれます。そうでない場合は`False`を返します。

今回、上二つのメソッドはエラーを返すことを想定している、つまりFalseを期待しているので
`self.assertFalse(form.is_valid(), 'パスワードで正しくエラーが出ませんでした。')`
と書いています。`True`を期待する場合は`assertTrue`となるわけです。
`assertFalse`の引数は、`評価する値`と`エラーだった場合の表示`となっています。

これを実行すると、

```shell
$ python manage.py test account_management/tests -k
Using existing test database for alias 'default'...
System check identified no issues (0 silenced).
...
----------------------------------------------------------------------
Ran 4 tests in 3.196s

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

このようにテストが通ったことが分かります。
コマンドの`-k`は、テストで作成したDBを削除せず、次も利用するというオプションです。
自分は実行後のDBを確認したかったのでつけて実行しています。

# まとめ
あっているかはわかりませんが、とりあえずテストを書くことができました。
なぜかこれにたどり着くまでに１日もかかってしまいました。
これで以降のテストはスムーズに作成できるといいのですが…