Test coverage statistics Django project

Author: HelloGitHub- Dream figure

Sample code files involved, synchronized to the updating HelloGitHub-Team warehouse

We completed the application and testing of blog comment application of two core app. Now we want to know is how to test what effect? Fully test it? Comprehensive test it? There is not no place to measure it?

The naked eye alone is difficult to answer the above questions, then we will help coverage.py , from the perspective of code coverage to detect what our test how effective.

Coverage.py (hereinafter referred to as Coverage) sector is one of the most popular Python testing a library for statistical test coverage. Test coverage can be measured from a point of view of the quality of the code, the higher the coverage, the more fully explain the test, code appears the smaller the chance of a bug. Of course, to note that the test coverage is just a measure of code quality point of view, even if 100% coverage can not say that the code is perfect, bug-free.

Installation Coverage

To use Coverage, first is to install it:

$ pipenv install coverage --dev

Since been used only when the development, so add --dev option to mark it as dependencies when developing the use of Pipenv installation.

Simple configuration Coverage

Coverage supports many configuration options, for convenience, these configurations are usually written in the named .coveragercfile, reads the configuration file from the project root Coverage runtime. So first in the project root directory create this file and write the most basic configuration:

[run]
branch = True
source = .

[report]
show_missing = True

Coverage follow ini configuration file syntax. Is simply, [section]it represents a configuration block, configured for a set of associated tissue. Here, for example, [run]is a configuration block, [report]a block of another configuration, some configuration items are associated with the two blocks.

Format for the configuration items key = value.

These few simple configuration items meaning:

  • branch = True. Whether conditional statement statistics branch coverage. if condition is True and False are usually determined in both cases, is provided branch = Trueafter, Coverages will measure whether these two cases have been tested.
  • source = .. Statistics need to specify the source code directory, here set to the current directory (ie project root directory).
  • show_missing = True. Display has not been tested lines of code numbers to cover in the statistical reports generated.

Run Coverage

After a simple configuration, since we can run the Coverage.

Open a command line, enter the project root directory, and then click Run the following command (note that if there is no need to activate the virtual command to make use pipenv run in a virtual environment).

First run on the erase command to clear the statistics once

$ pipenv run coverage erase

django manage.py test unit tests run, it is this time with coverage run to run

$ pipenv run coverage run manage.py test

Coverage generate statistical reports

$ pipenv run coverage report

Coverage statistics report output is as follows:

Name                                             Stmts   Miss Branch BrPart  Cover   Missing
--------------------------------------------------------------------------------------------
_credentials.py                                      2      2      0      0     0%   1-2
blog\__init__.py                                     0      0      0      0   100%
blog\admin.py                                       11      0      0      0   100%
blog\apps.py                                         4      0      0      0   100%
blog\elasticsearch2_ik_backend.py                    8      0      0      0   100%
blog\feeds.py                                       12      0      0      0   100%
blog\migrations\0001_initial.py                      7      0      0      0   100%
blog\migrations\0002_auto_20190711_1802.py           7      0      0      0   100%
blog\migrations\0003_auto_20191011_2326.py           4      0      0      0   100%
blog\migrations\0004_post_views.py                   4      0      0      0   100%
blog\migrations\__init__.py                          0      0      0      0   100%
blog\models.py                                      62      0      0      0   100%
blog\search_indexes.py                               8      0      0      0   100%
blog\templatetags\__init__.py                        0      0      0      0   100%
blog\templatetags\blog_extras.py                    15      0      0      0   100%
blog\tests\__init__.py                               0      0      0      0   100%
blog\tests\test_models.py                           58      0      2      0   100%
blog\tests\test_smoke.py                             4      0      0      0   100%
blog\tests\test_templatetags.py                    115      0      2      0   100%
blog\tests\test_utils.py                            11      0      0      0   100%
blog\tests\test_views.py                           170      0      8      0   100%
blog\urls.py                                         4      0      0      0   100%
blog\utils.py                                       10      0      2      1    92%   14->16
blog\views.py                                       40      7      2      0    79%   64-72
blogproject\__init__.py                              0      0      0      0   100%
blogproject\settings\__init__.py                     0      0      0      0   100%
blogproject\settings\common.py                      22      0      0      0   100%
blogproject\settings\local.py                        5      0      0      0   100%
blogproject\settings\production.py                   5      5      0      0     0%   1-8
blogproject\urls.py                                  4      0      0      0   100%
blogproject\wsgi.py                                  4      4      0      0     0%   10-16
comments\__init__.py                                 0      0      0      0   100%
comments\admin.py                                    6      0      0      0   100%
comments\apps.py                                     4      0      0      0   100%
comments\forms.py                                    6      0      0      0   100%
comments\migrations\0001_initial.py                  7      0      0      0   100%
comments\migrations\0002_auto_20191011_2326.py       4      0      0      0   100%
comments\migrations\__init__.py                      0      0      0      0   100%
comments\models.py                                  15      0      0      0   100%
comments\templatetags\__init__.py                    0      0      0      0   100%
comments\templatetags\comments_extras.py            12      0      2      0   100%
comments\tests\__init__.py                           0      0      0      0   100%
comments\tests\base.py                              10      0      0      0   100%
comments\tests\test_models.py                        8      0      0      0   100%
comments\tests\test_templatetags.py                 57      0      6      0   100%
comments\tests\test_views.py                        34      0      4      0   100%
comments\urls.py                                     4      0      0      0   100%
comments\views.py                                   17      0      2      0   100%
fabfile.py                                          21     21      0      0     0%   1-43
manage.py                                           12      2      2      1    79%   11-12, 20->exit
scripts\__init__.py                                  0      0      0      0   100%
scripts\fake.py                                     63     63     14      0     0%   1-106
--------------------------------------------------------------------------------------------
TOTAL                                              876    104     46      2    87%

The second column is the reciprocal of test coverage statistics file, first column is the number of lines of code uncovered.

Most file test coverage is 100%, indicating that our test is quite sufficient. However, reported results, we found the following questions:

  1. In fact, there are some files do not need to test or not the core of the project file (for example, the deployment script fabfile.py, django of migrations files, etc.), these files should be excluded from the statistics.
  2. Coverage default statistics show coverage of all the files, if the file is not good, then more than 100% coverage to find the file. After all, our goal is to increase code coverage, and thus reached 100% coverage of the code file that we no longer care. We have to do is to find a non-100% coverage of the document, to add the missing test.

Perfect Coverage Configuration

2 can easily solve the problem by adding Coverage above configuration items.

In [run]increase the allocation block omitconfiguration items can be specified statistics exclude files.

In [report]increasing the block configuration skip_coveredCI may specify the statistics report 100% coverage of the file is not displayed.

This is the .coveragercfinal configuration, note that we specify ignore some noncore project document omit configuration items:

[run]
branch = True
source = .
omit =
   _credentials.py
   manage.py
   blogproject/settings/*
   fabfile.py
   scripts/fake.py
   */migrations/*
   blogproject\wsgi.py

[report]
show_missing = True
skip_covered = True

Coverage on the run again in accordance with said one way the final report was as follows:

Name            Stmts   Miss Branch BrPart  Cover   Missing
-----------------------------------------------------------
blog\utils.py      10      0      2      1    92%   14->16
blog\views.py      40      7      2      0    79%   64-72
-----------------------------------------------------------
TOTAL             709      7     30      1    99%

33 files skipped due to complete coverage.

The report pointed out that we still have two files do not reach 100% coverage, we have to do is add unit tests for the code in the two files untested, allowed to reach 100 percent test coverage.

But before hands-on writing tests, we have to figure out what the code is not to be measured. Finally, a command line of the report pointed out the line number of untested code, but this looked not very intuitive. An experience better way is to generate HTML reports so that we can see directly in the HTML code is not specific to report to the test.

Generate HTML reports

coverage reportFrom the command line to generate statistical reports, and coverage htmlyou can generate HTML reports.

On the basis of an upper, run the following command:

$ pipenv run coverage html

After the completion of the project root directory run will be more of a htmlcov folder, HTML report file which is test coverage. Open the index.html file inside the browser you can view the results of the report:

Results Home and command line is the same, but we can click on the file name of the file into more specific statistics page report, such as blog \ views.py results are as follows:

Code Green has covered some representatives of the red part represents the code coverage.

Perfect unit testing

查看文件我们发现,blog\views.py 中未被覆盖的代码原来是 Django 博客实现简单的全文搜索 中的代码,现在我们已经将搜索替换为 Django Haystack 全文检索 了,这段代码也就不需要了,可以直接删除。

blog\views.py 的报告结果则表明我们在 Django Haystack 全文检索与关键词高亮 中自定义的搜索关键词高亮器有一个 if 分支条件未被测试到:

检查 blog/tests/test_utils.py 中的测试用例,我们发现只测试了比较短的标题不被截断,也就是

if len(text_block) < self.max_length:

判断条件为 True,缺失对判断条件为 False 的测试。所以我们来构造一个新的测试用例测试标题长度超过 max_length (默认值为 200)的情况时会被截断:

class HighlighterTestCase(TestCase):
    def test_highlight(self):
        # 省略已有代码 ...

        highlighter = Highlighter("标题")
        document = "这是一个长度超过 200 的标题,应该被截断。" + "HelloDjangoTutorial" * 200
        self.assertTrue(
            highlighter.highlight(document).startswith(
                '...<span class="highlighted">标题</span>,应该被截断。'
            )
        )

再次运行 Coverage 生成报告,测试覆盖率全都 100% 了!

$ pipenv run coverage erase
$ pipenv run coverage run manage.py test
$ pipenv run coverage report
# 输出
Name    Stmts   Miss Branch BrPart  Cover   Missing
---------------------------------------------------
---------------------------------------------------
TOTAL     704      0     28      0   100%

最后提醒一点,Coverage 运行后可能会在项目目录下生成一些文件,这些文件并不需要纳入版本管理,所以将其加入 .gitignore 文件中,防止被提交到代码库:

htmlcov/
.coverage
.coverage.*
coverage.xml
*.cover

HelloDjango 往期回顾:

第 30 篇:Django 博客单元测试:测试评论应用

第 29 篇:编写 Django 应用单元测试

第 28 篇:Django Haystack 全文检索与关键词高亮


关注公众号加入交流群

Guess you like

Origin www.cnblogs.com/xueweihan/p/12419866.html