もた日記

くだらないことを真面目にやる

Djangoメモ(26) : coverage.pyでカバレッジ(網羅率)を計測

Python 3.6.4 Django 2.0.2

A Complete Beginner's Guide to Djangoのチュートリアルを参考に作成している掲示板アプリのテストコードのカバレッジを計測してみる。


coverage.pyのインストール

チュートリアルでは様々なテストコードを書いているがカバレッジ(網羅率)がどの程度なのか計測してみる。
ドキュメントにはcoverage.pyの説明があるのでcoverage.pyを試してみる。
coverage.pyはpipコマンドでインストール可能。

$ pip install coverage

今回はPipenvで環境を構築しているので下記コマンドでインストール。

$ pipenv install coverage

インストールできているか確認する。

$ coverage --help
Coverage.py, version 4.5.1 with C extension
Measure, collect, and report on code coverage in Python programs.

usage: coverage <command> [options] [args]

Commands:
    annotate    Annotate source files with execution information.
    combine     Combine a number of data files.
    erase       Erase previously collected coverage data.
    help        Get help on using coverage.py.
    html        Create an HTML report.
    report      Report coverage stats on modules.
    run         Run a Python program and measure code execution.
    xml         Create an XML report of coverage results.

Use "coverage help <command>" for detailed help on any command.
For full documentation, see https://coverage.readthedocs.io


coverage.pyの実行方法

coverage.pyでは最初にrunコマンドでテストコードを実行してデータを集める。

$ overage run --source='.' manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
....................................................................
----------------------------------------------------------------------
Ran 68 tests in 9.787s

OK
Destroying test database for alias 'default'...

実行が完了すると.coverageというファイルが作成される。
次に結果を表示するためにreportコマンドを実行する。
Stmtsはstatementsの略で実行可能なコードの行数、Missはその中でテストされなかったコードの行数、Coverはカバレッジ(網羅率)を表す。

coverage report
Name                                          Stmts   Miss  Cover
-----------------------------------------------------------------
accounts/__init__.py                              0      0   100%
accounts/admin.py                                 1      0   100%
accounts/apps.py                                  3      0   100%
accounts/forms.py                                 8      0   100%
accounts/migrations/__init__.py                   0      0   100%
accounts/models.py                                1      0   100%
accounts/tests/__init__.py                        0      0   100%
accounts/tests/test_form_signup.py                8      0   100%
accounts/tests/test_mail_password_reset.py       21      0   100%
accounts/tests/test_view_password_change.py      59      0   100%
accounts/tests/test_view_password_reset.py       99      0   100%
accounts/tests/test_view_signup.py               49      0   100%
accounts/views.py                                12      0   100%
boards/__init__.py                                0      0   100%
boards/admin.py                                   3      0   100%
boards/apps.py                                    3      0   100%
boards/forms.py                                   7      0   100%
boards/migrations/0001_initial.py                 7      0   100%
boards/migrations/__init__.py                     0      0   100%
boards/models.py                                 19      1    95%
boards/templatetags/__init__.py                   0      0   100%
boards/templatetags/form_tags.py                 12      0   100%
boards/tests/__init__.py                          0      0   100%
boards/tests/test_templatetags.py                24      0   100%
boards/tests/test_view_board_topics.py           25      0   100%
boards/tests/test_view_home.py                   17      0   100%
boards/tests/test_view_new_topic.py              63      0   100%
boards/views.py                                  24      0   100%
manage.py                                         9      2    78%
myproject/__init__.py                             0      0   100%
myproject/settings.py                            25      0   100%
myproject/urls.py                                10      2    80%
myproject/wsgi.py                                 4      4     0%
-----------------------------------------------------------------
TOTAL                                           513      9    98%


また、htmlコマンドを使うと結果をHTMLで確認できる。

$ coverage html

htmlcov/というディレクトリが作成されるのでその中のindex.htmlをブラウザで表示。
ソートなどもできるので詳しくはドキュメントの例を参照。

f:id:wonder-wall:20180325170121p:plain

f:id:wonder-wall:20180325170137p:plain


coverage.pyの設定

先ほどの結果にはテストコードのtestsディレクトリも含まれていたので--omitオプションで除外する。

$ coverage run --source='.' --omit='*/tests/*' manage.py test

reportコマンドでは-mオプションでMissingの行番号を表示でき、--skip-coveredオプションで網羅率が100%のファイルをスキップできる。

$ coverage report -m --skip-covered
Name                Stmts   Miss  Cover   Missing
-------------------------------------------------
boards/models.py       19      1    95%   10
manage.py               9      2    78%   9-10
myproject/urls.py      10      2    80%   65-66
myproject/wsgi.py       4      4     0%   10-16
-------------------------------------------------
TOTAL                 148      9    94%

18 files skipped due to complete coverage.

設定については以下のような.coveragercファイルを作成しておくことで毎回オプションを指定しなくてもよくなる。 設定の詳細についてはドキュメントを参照。

[run]
source = .
omit = */tests/*

[report]
show_missing = True
skip_covered = True


coverage.pyのプラグイン

ドキュメントのプラグインページdjango_coverage_pluginというテンプレートのカバレッジを計測するプラグインがあったので試してみる。

$ pip install django_coverage_plugin
または
$ pipenv install django_coverage_plugin

インストールしたら.coveragercファイルにプラグインを有効にする設定を追加する。

[run]
plugins =
    django_coverage_plugin

後は同様にrunコマンドを実行すればよいがエラーになってしまった。

$ coverage run manage.py test
 ...
django_coverage_plugin.plugin.DjangoTemplatePluginException: Template debugging must be enabled in settings.

テンプレートデバッグ機能を有効にすればよいらしいのでsettings.pyTEMPLATESOPTIONSに追加する。

TEMPLATES = [
    {
        ...
        'OPTIONS': {
            'debug': True,
            ],
        ...
    },
]

これでテンプレートのカバレッジも表示されるようになる。

$ coverage report
Name                           Stmts   Miss  Cover   Missing
------------------------------------------------------------
boards/models.py                  19      1    95%   10
manage.py                          9      2    78%   9-10
myproject/urls.py                 10      2    80%   65-66
myproject/wsgi.py                  4      4     0%   10-16
templates/base.html               56      2    96%   46, 49
templates/base_accounts.html       8      1    88%   15
templates/includes/form.html      24      5    79%   4-8
templates/topics.html             30      8    73%   29-36
------------------------------------------------------------
TOTAL                            459     25    95%

30 files skipped due to complete coverage.


まとめ

  • coverage.pyでカバレッジ(網羅率)を計測
  • runコマンドでテストコードの実行とデータ収集
  • reportまたはhtmlコマンドで結果を表示
  • .coveragercファイルに設定を記述
  • django_coverage_pluginというテンプレート用のプラグインもある