A Complete Beginner's Guide to Djangoのチュートリアルを参考に認証関連のテストを作成してみる。
サインアップのテスト
前回サインアップ機能を作成したのでサインアップのテストを作成する。
accounts/tests.py
を以下のように修正。
from django.contrib.auth.forms import UserCreationForm from django.urls import reverse, resolve from django.test import TestCase from .views import signup class SignUpTests(TestCase): def setUp(self): url = reverse('signup') self.response = self.client.get(url) def test_signup_status_code(self): self.assertEquals(self.response.status_code, 200) def test_signup_url_resolves_signup_view(self): view = resolve('/signup/') self.assertEquals(view.func, signup) def test_csrf(self): self.assertContains(self.response, 'csrfmiddlewaretoken') def test_contains_form(self): form = self.response.context.get('form') self.assertIsInstance(form, UserCreationForm)
上記は基本的なテストでステータスコードの確認、ビューやフォームが適切かどうかの確認、CSRF対策のトークンが含まれているかを確認している。
サインアップ成功時のテスト
次にサインアップ成功時のテストをaccounts/tests.py
に追加する。
from django.contrib.auth.models import User from django.contrib.auth.forms import UserCreationForm from django.urls import reverse, resolve from django.test import TestCase from .views import signup class SignUpTests(TestCase): # code suppressed... class SuccessfulSignUpTests(TestCase): def setUp(self): url = reverse('signup') data = { 'username': 'john', 'password1': 'abcdef123456', 'password2': 'abcdef123456' } self.response = self.client.post(url, data) self.home_url = reverse('home') def test_redirection(self): ''' A valid form submission should redirect the user to the home page ''' self.assertRedirects(self.response, self.home_url) def test_user_creation(self): self.assertTrue(User.objects.exists()) def test_user_authentication(self): ''' Create a new request to an arbitrary page. The resulting response should now have a `user` to its context, after a successful sign up. ''' response = self.client.get(self.home_url) user = response.context.get('user') self.assertTrue(user.is_authenticated)
各テストの意味は以下の通り。
setUp()
: 適切なusername, passwordをPOST送信test_redirection()
:assertRedirects()
でhome
ページにリダイレクトされることを確認test_user_creation()
:User
オブジェクトが作成されていることを確認test_user_authentication()
:assertTrue(user.is_authenticated)
でユーザーが認証済みであることを確認
サインアップ失敗時のテスト
無効なデータによりサインアップが失敗するケースのテストを追加する。
from django.contrib.auth.models import User from django.contrib.auth.forms import UserCreationForm from django.urls import reverse, resolve from django.test import TestCase from .views import signup class SignUpTests(TestCase): # code suppressed... class SuccessfulSignUpTests(TestCase): # code suppressed... class InvalidSignUpTests(TestCase): def setUp(self): url = reverse('signup') self.response = self.client.post(url, {}) # submit an empty dictionary def test_signup_status_code(self): ''' An invalid form submission should return to the same page ''' self.assertEquals(self.response.status_code, 200) def test_form_errors(self): form = self.response.context.get('form') self.assertTrue(form.errors) def test_dont_create_user(self): self.assertFalse(User.objects.exists())
各テストの意味は以下の通り。
setUp()
: 失敗するように空の辞書{}
をPOST送信test_signup_status_code()
: 失敗時には再度フォームが表示されるのでステータスコードが200
であることを確認test_form_errors()
: エラーメッセージがあることを確認test_dont_create_user()
:User
オブジェクトが作成されていないことを確認
サインアップフォームにEメールフィールド追加
テストは一通り実装したのでサインアップフォームを改良する。
ビルトインのUserCreationForm
でサインアップフォームを作成しているが、このフォームにはusername
, password1
, password2
の3つのフィールドはあるがemail
フィールドがないのでサインアップフォームに追加する。
accounts/forms.py
を以下の内容で新規作成。
from django import forms from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User class SignUpForm(UserCreationForm): email = forms.CharField(max_length=254, required=True, widget=forms.EmailInput()) class Meta: model = User fields = ('username', 'email', 'password1', 'password2')
UserCreationForm
を継承したSignUpForm
クラスを作成しemail
フィールドを追加している。
SignUpForm
を使用するようにaccounts/views.py
を編集する。
from django.contrib.auth import login from django.shortcuts import render, redirect from .forms import SignUpForm def signup(request): if request.method == 'POST': form = SignUpForm(request.POST) if form.is_valid(): user = form.save() login(request, user) return redirect('home') else: form = SignUpForm() return render(request, 'signup.html', {'form': form})
これでEメールアドレス入力用のフィールドが追加され、ユーザー登録時にEメールアドレスがデータベースに登録されるようになる。
テストもSignUpForm
を使うように変更する。
また、フォームがcsrf, username, email, password1, password2の5つのinput
タグだけを含むことを確認するテストを追加する。
from .forms import SignUpForm class SignUpTests(TestCase): # ... def test_contains_form(self): form = self.response.context.get('form') self.assertIsInstance(form, SignUpForm) def test_form_inputs(self): ''' The view must contain five inputs: csrf, username, email, password1, password2 ''' self.assertContains(self.response, '<input', 5) self.assertContains(self.response, 'type="text"', 1) self.assertContains(self.response, 'type="email"', 1) self.assertContains(self.response, 'type="password"', 2) class SuccessfulSignUpTests(TestCase): def setUp(self): url = reverse('signup') data = { 'username': 'john', 'email': 'john@doe.com', 'password1': 'abcdef123456', 'password2': 'abcdef123456' } self.response = self.client.post(url, data) self.home_url = reverse('home') # ...
テストを実行して通ることを確認する。
$ python manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). ........................... ---------------------------------------------------------------------- Ran 27 tests in 1.958s OK Destroying test database for alias 'default'...
テストの構成を変更
次にSignUpForm
自体のテストを追加するが、ここでテストの構成を変更する。
accounts/tests/
というディレクトリを作成し、その中に__init__.py
ファイルを作成する。そして、tests.py
も同じディレクトリに移動しtest_view_signup.py
にリネームする。
結果、ディレクトリ構成は以下のようになる。
accounts/ ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── migrations/ │ └── __init__.py ├── models.py ├── tests/ │ ├── __init__.py │ └── test_view_signup.py └── views.py
ディレクトリ構成が変わったのでaccounts/tests/test_view_signup.py
のimport文を変更する。
from ..forms import SignUpForm from ..views import signup
SignUpForm
自体のテストをaccounts/tests/test_form_signup.py
というファイルに新規作成。
from django.test import TestCase from ..forms import SignUpForm class SignUpFormTest(TestCase): def test_form_has_fields(self): form = SignUpForm() expected = ['username', 'email', 'password1', 'password2',] actual = list(form.fields) self.assertSequenceEqual(expected, actual)
テストを実行して問題ないことを確認する。
$ python manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). ............................ ---------------------------------------------------------------------- Ran 28 tests in 1.964s OK Destroying test database for alias 'default'...
サインアップフォームのテンプレート変更
Bootstrap 4のCardsコンポーネントと背景パターンを使用してサインアップフォームの見た目を良くする。
背景パターンについては下記サイトで適当なパターンをダウンロード(今回はVintage Leavesを選択)する。
static/img/
ディレクトリを作成し、ダウンロードした画像ファイルを置く。
そして、static/css/
ディレクトリにaccounts.css
ファイルを作成する。
├── static/ │ ├── css/ │ │ ├── accounts.css │ │ ├── app.css │ │ └── bootstrap.min.css │ └── img/ │ └── vintage-leaves.png └── templates/
static/css/accounts.css
を以下のように編集。
body { background-image: url(../img/vintage-leaves.png); } .logo { font-family: 'Pacifico', cursive; } .logo a { color: rgba(0,0,0,.9); } .logo a:hover, .logo a:active { text-decoration: none; }
css/accounts.css
を読み込み、Cardsコンポーネントを使用するようにtemplates/signup.html
を編集する。
{% extends 'base.html' %} {% load static %} {% block stylesheet %} <link rel="stylesheet" href="{% static 'css/accounts.css' %}"> {% endblock %} {% block body %} <div class="container"> <h1 class="text-center logo my-4"> <a href="{% url 'home' %}">Django Boards</a> </h1> <div class="row justify-content-center"> <div class="col-lg-8 col-md-10 col-sm-12"> <div class="card"> <div class="card-body"> <h3 class="card-title">Sign up</h3> <form method="post" novalidate> {% csrf_token %} {% include 'includes/form.html' %} <button type="submit" class="btn btn-primary btn-block">Create an account</button> </form> </div> <div class="card-footer text-muted text-center"> Already have an account? <a href="#">Log in</a> </div> </div> </div> </div> </div> {% endblock %}
これで下図のような見た目になる。
まとめ
assertRedirects()
でリダイレクトのテストuser.is_authenticated
で認証済みであるかを確認UserCreationForm
を継承したクラスでEメールフィールドを追加- Bootstrap 4のCardsコンポーネントと背景パターンで見た目を変更