django-nose
とdjango-caverage
を使ってテストコードの実行とカバレッジの計測を行っていましたが、django 1.10
ではうまくカバレッジを計測してくれないという問題があったので、今回はCoverage.py
を利用してカバレッジを計測してみたいと思います。
django-nose
とdjango-caverage
を利用したテストコードの実行方法を知りたい方は、過去の記事を参照してください。

目次
django-noseとdjango-caverageを利用していた時の問題点
Django 1.10
でカバレッジを計測していた際に、下記のコードのカバレッジが計測できない問題がありました。
- デコレータのカバレッジが計測されない
- 多重継承しているスーパークラスのカバレッジが計測されない
デコレータのカバレッジが計測されないために、全てのコードをパスしているテストコードを書いてもカバレッジが100%にならず、計測結果をみてもこれ以上テストが必要かどうかをカバレッジから確認することができない状態でした。
そこで、Coverage.py
を利用してカバレッジを計測するようにしました。
Coverage.pyをインストールする
pip
を使ってCoverage.py
をインストールします。
$ pip install coverage
Coverage.pyの設定ファイルを作成する
プロジェクトディレクトリ直下にCoverage.py
に関する設定を記述する「.coveragerc
」というファイルを作成します。
とりあえず、最低限の設定を記述しておきます。
[run]
omit = */tests/*
[html]
directory = cover
.coveragerc
の設定に関する詳細な情報については下記のサイトを参照してください。
テストコードを準備する
今回は「accounts」アプリケーションのView
とModel
をテストを実施します。
下記のテストを実施するテストコードになります。
- Viewのテスト
- ログイン画面へのアクセステスト
- ログイン成功時のテスト
- ログイン失敗時のテスト
- Modelのテスト
- ユーザ作成(create_user)のテスト
- スーパーユーザ作成(create_superuser)のテスト
# accounts/tests.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils.encoding import python_2_unicode_compatible
from django.test import TestCase
from .models import AuthUser
@python_2_unicode_compatible
class AccountsViewTests(TestCase):
def setUp(self):
"""
ログイン可能なユーザを1名追加する
"""
AuthUser.objects.create_user(username='test',
email='test@test.com',
password='test',
last_name='テスト',
first_name='太郎')
def test_login_get(self):
"""
ログイン画面へのアクセスのテスト
"""
client = self.client
response = client.get('/accounts/login/')
self.assertEqual(response.status_code, 200)
client.login(username='test', password='test')
response = client.get('/accounts/login/')
self.assertEqual(response.status_code, 200)
def test_login_success(self):
"""
ログイン画面でログインできるかテストする
"""
client = self.client
response = client.post('/accounts/login/', data={'username': 'test', 'password': 'test'})
self.assertRedirects(response, '/')
def test_login_fail(self):
"""
ユーザ登録されている人以外はログインできないことをテストする
"""
client = self.client
response = client.post('/accounts/login/', data={'username': 'test2', 'password': 'test'}, follow=True)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.request['PATH_INFO'], '/accounts/login/')
@python_2_unicode_compatible
class AccountsModelTests(TestCase):
def test_create_user(self):
"""
AuthUserモデルのcreate_userをテストする
"""
# 普通にユーザ追加
result = AuthUser.objects.create_user(username='test', email='test@test.com', password='test', last_name='テスト', first_name='太郎')
user = AuthUser.objects.get(pk=result.pk)
# ちゃんとパラメータにセットした値でユーザが登録されたか確認する
self.assertEqual(result.username, 'test')
self.assertEqual(result.email, 'test@test.com')
# ハッシュ値は常にランダムになってしまうので、ここはDBに登録された値と比較する
self.assertEqual(result.password, user.password)
self.assertEqual(result.last_name, 'テスト')
self.assertEqual(result.first_name, '太郎')
# 以下の3つのフィールドはcreate_userした時に自動でセットされる項目なので、DBのレコードの値を検証する
self.assertTrue(user.is_active)
self.assertFalse(user.is_staff)
self.assertFalse(user.is_superuser)
# eメールが空だったらエラーをはく
# エラー時のメッセージも検証する
with self.assertRaisesMessage(ValueError, 'Users must have an email'):
AuthUser.objects.create_user(username='test', email='', password='test', last_name='テスト', first_name='太郎')
# usernameが空だったらエラーをはく
# エラー時のメッセージも検証する
with self.assertRaisesMessage(ValueError, 'Users must have an username'):
AuthUser.objects.create_user(username='', email='test@test.com', password='test', last_name='テスト', first_name='太郎')
def test_create_superuser(self):
"""
AuthUserモデルのcreate_superuserをテストする
"""
# スーパーユーザを追加する
result = AuthUser.objects.create_superuser(username='super', email='super@user.com', password='user', last_name='スーパー', first_name='ユーザ')
user = AuthUser.objects.get(pk=result.pk)
# ちゃんとパラメータにセットした値でユーザが登録されたか確認する
self.assertEqual(result.username, 'super')
self.assertEqual(result.email, 'super@user.com')
# ハッシュ値は常にランダムになってしまうので、ここはDBに登録された値と比較する
self.assertEqual(result.password, user.password)
self.assertEqual(result.last_name, 'スーパー')
self.assertEqual(result.first_name, 'ユーザ')
# 以下の3つのフィールドはcreate_userした時に自動でセットされる項目なので、DBのレコードの値を検証する
self.assertTrue(user.is_active)
self.assertTrue(user.is_staff)
self.assertTrue(user.is_superuser)
# eメールが空だったらエラーをはく
# エラー時のメッセージも検証する
with self.assertRaisesMessage(ValueError, 'Users must have an email'):
AuthUser.objects.create_superuser(username='super', email='', password='user', last_name='スーパー', first_name='ユーザ')
# usernameが空だったらエラーをはく
# エラー時のメッセージも検証する
with self.assertRaisesMessage(ValueError, 'Users must have an username'):
AuthUser.objects.create_superuser(username='', email='super@user.com', password='user', last_name='スーパー', first_name='ユーザ')
テストコードを実行してカバレッジをHTMLで出力する
下記コマンドを実行して、テストコードを実行しカバレッジをHTMLファイルで出力します。
$ coverage run --source=accounts manage.py test accounts
$ coverage report
$ coverage html
上記実行すると.coveragerc
の[html]
で設定したcover
ディレクトリにカバレッジの計測結果が格納されます。
計測されたカバレッジを確認する
cover
ディレクトリのindex.html
をブラウザで開くとカバレッジの計測結果が確認できます。
accounts/models.py
のカバレッジの詳細を確認してみます。
デコレータやスーパークラスのコードもカバレッジが通っていることが確認できました!!
最後に
django-nose
もカバレッジを簡単に計測することができて、使い方も簡単なのですが、「Django 1.8
」以降を利用して開発している場合、django-nose
がDjango
の新機能に対応しておらず、カバレッジが正確に測ることができなくなってしまっていました。
もし、Django 1.8
以降のバージョンを利用して開発を行っている方で、カバレッジをうまく計測できずにお悩みの方はCoverage.py
に切り替えるのもアリだと思います。
コメント