Django Logo

[Django] Coverage.py を使ってカバレッジを計測する

django-nosedjango-caverage を使ってテストコードの実行とカバレッジの計測を行っていましたが、 django 1.10 ではうまくカバレッジを計測してくれないという問題があったので、今回は Coverage.py を利用してカバレッジを計測してみたいと思います。

django-nosedjango-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 」アプリケーションの ViewModel をテストを実施します。
下記のテストを実施するテストコードになります。

  • 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-noseDjango の新機能に対応しておらず、カバレッジが正確に測ることができなくなってしまっていました。
もし、 Django 1.8 以降のバージョンを利用して開発を行っている方で、カバレッジをうまく計測できずにお悩みの方は Coverage.py に切り替えるのもアリだと思います。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です