覆蓋率簡介
測量代碼覆蓋率的工具在測試套件運行時觀察你的代碼,並跟蹤哪些行被運行,哪些沒有。這種測量被稱爲行覆蓋率,其計算方法是將運行的總行數除以代碼的總行數。代碼覆蓋率工具還可以告訴你在控制語句中是否覆蓋率了所有的路徑,這種測量稱爲分支覆蓋率。
Coverage.py是測量代碼覆蓋率的首選 Python 覆蓋工具。pytest-cov是一個流行的 pytest 插件,經常與 coverage.py 結合使用。
在pytest-cov中使用coverage.py
$ pip install coverage
$ pip install pytest-cov
要用coverage.py運行測試,你需要添加--cov標誌,並提供你要測量的代碼的路徑,或者你要測試的安裝包。cards項目是已安裝的包,所以我們用--cov=cards來測試它。
$ pytest --cov=Cards ch7
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0
rootdir: D:\code\pytest_quick\ch7, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0
collected 27 items
ch7\test_add.py ..... [ 18%]
ch7\test_config.py . [ 22%]
ch7\test_count.py ... [ 33%]
ch7\test_delete.py ... [ 44%]
ch7\test_finish.py .... [ 59%]
ch7\test_list.py .. [ 66%]
ch7\test_start.py .... [ 81%]
ch7\test_update.py .... [ 96%]
ch7\test_version.py . [100%]D:\ProgramData\Anaconda3\lib\site-packages\coverage\inorout.py:507: CoverageWarning: Module Cards was never imported. (module-not-imported)
self.warn(f"Module {pkg} was never imported.", slug="module-not-imported")
D:\ProgramData\Anaconda3\lib\site-packages\coverage\control.py:838: CoverageWarning: No data was collected. (no-data-collected)
self._warn("No data was collected.", slug="no-data-collected")
D:\ProgramData\Anaconda3\lib\site-packages\pytest_cov\plugin.py:297: CovReportWarning: Failed to generate report: No data to report.
self.cov_controller.finish()
WARNING: Failed to generate report: No data to report.
---------- coverage: platform win32, python 3.9.13-final-0 -----------
============================= 27 passed in 0.66s ==============================
# 等效的調用方法
$ coverage run --source=cards -m pytest ch7
.coveragerc
[paths]
source =
cards_proj/src/cards
*/site-packages/cards
要在終端報告中添加遺漏的行,我們可以重新運行測試並添加 --cov-report=term-missing 標誌,或者coverage report --show-missing。
$ pytest --cov=cards --cov-report=term-missing ch7
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0
rootdir: D:\code\pytest_quick\ch7, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0
collected 27 items
ch7\test_add.py ..... [ 18%]
ch7\test_config.py . [ 22%]
ch7\test_count.py ... [ 33%]
ch7\test_delete.py ... [ 44%]
ch7\test_finish.py .... [ 59%]
ch7\test_list.py .. [ 66%]
ch7\test_start.py .... [ 81%]
ch7\test_update.py .... [ 96%]
ch7\test_version.py . [100%]
---------- coverage: platform win32, python 3.9.13-final-0 -----------
Name Stmts Miss Cover Missing
----------------------------------------------------------------
cards_proj\src\cards\__init__.py 3 0 100%
cards_proj\src\cards\api.py 70 3 96% 72, 78, 82
cards_proj\src\cards\cli.py 86 53 38% 20, 28-30, 36-40, 51-63, 73-80, 86-90, 96-100, 106-107, 113-114, 122-123, 127-132, 137-140
cards_proj\src\cards\db.py 23 0 100%
----------------------------------------------------------------
TOTAL 182 56 69%
============================= 27 passed in 0.75s ==============================
$ coverage report --show-missing
Name Stmts Miss Cover Missing
----------------------------------------------------------------
cards_proj\src\cards\__init__.py 3 0 100%
cards_proj\src\cards\api.py 70 3 96% 72, 78, 82
cards_proj\src\cards\cli.py 86 53 38% 20, 28-30, 36-40, 51-63, 73-80, 86-90, 96-100, 106-107, 113-114, 122-123, 127-132, 137-140
cards_proj\src\cards\db.py 23 0 100%
----------------------------------------------------------------
TOTAL 182 56 69%
參考資料
- 本文涉及的python測試開發庫 謝謝點贊!
- 本文相關海量書籍下載
生成HTML報告
$ cd /path/to/code
$ pytest --cov=cards --cov-report=html ch7
# 或
$ pytest --cov=cards ch7
$ coverage html
這兩條命令都要求coverage.py生成HTML報告。該報告被稱爲htmlcov/,在你運行該命令的目錄中。
用瀏覽器打開htmlcov/index.html,你會看到。
點擊api.py文件會顯示該文件的報告,如圖所示。
報告的頂部顯示了所覆蓋的行的百分比(96%),語句的總數(72),以及有多少語句被運行(69),遺漏(3)和排除(0)。向下滾動,你可以看到被遺漏的高亮行。
ist_cards()函數有幾個可選參數--owner和state,允許對列表進行過濾。這些行沒有被我們的測試套件所測試。
將代碼排除在覆蓋範圍之外
if __name__ == '__main__': # pragma: no cover
exit(main())
爲了達到100%的覆蓋率而添加測試的問題是,這樣做會掩蓋這些行沒有被使用的事實,因此應用程序不需要。它還會增加不必要的測試代碼和編碼時間。
在測試中運行覆蓋率
除了使用覆蓋率來確定我們的測試套件是否擊中了代碼外。讓我們把我們的測試目錄添加到覆蓋率報告中。
$ pytest --cov=cards --cov=ch7 ch7
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0
rootdir: D:\code\pytest_quick\ch7, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0
collected 27 items
ch7\test_add.py ..... [ 18%]
ch7\test_config.py . [ 22%]
ch7\test_count.py ... [ 33%]
ch7\test_delete.py ... [ 44%]
ch7\test_finish.py .... [ 59%]
ch7\test_list.py .. [ 66%]
ch7\test_start.py .... [ 81%]
ch7\test_update.py .... [ 96%]
ch7\test_version.py . [100%]
---------- coverage: platform win32, python 3.9.13-final-0 -----------
Name Stmts Miss Cover
------------------------------------------------------
cards_proj\src\cards\__init__.py 3 0 100%
cards_proj\src\cards\api.py 70 3 96%
cards_proj\src\cards\cli.py 86 53 38%
cards_proj\src\cards\db.py 23 0 100%
ch7\conftest.py 22 0 100%
ch7\test_add.py 31 0 100%
ch7\test_config.py 2 0 100%
ch7\test_count.py 9 0 100%
ch7\test_delete.py 28 0 100%
ch7\test_finish.py 13 0 100%
ch7\test_list.py 11 0 100%
ch7\test_start.py 13 0 100%
ch7\test_update.py 21 0 100%
ch7\test_version.py 5 0 100%
------------------------------------------------------
TOTAL 337 56 83%
============================= 27 passed in 0.71s ==============================
--cov=cards命令告訴coverage觀察card包。--cov=ch7命令告訴覆蓋率觀察ch7目錄,也就是我們的測試所在的地方。
在所有的編程中,特別是在編碼測試中,一個常見的錯誤是用複製/粘貼/修改的方式添加新的測試函數。對於新的測試函數,我們可能會複製現有的函數,將其粘貼爲新的函數,並修改代碼以滿足新的測試案例。如果我們忘記改變函數的名稱,那麼兩個函數就會有相同的名稱,而且只有文件中的最後一個函數會被運行。重複命名的測試的問題很容易被抓住,包括你的測試代碼在覆蓋率的來源。
類似的問題也會發生在大型測試模塊中,當我們只是忘記了所有的函數名稱,而不小心將第二個測試函數的名稱與前一個函數的名稱相同。
第三個問題更微妙。覆蓋率有能力結合幾個測試環節的報告。這是必要的,例如,在持續集成的不同硬件上進行測試。一些測試可能是特定於某些硬件的,而在其他硬件上被跳過。綜合報告,如果我們包括測試,將幫助我們確保我們所有的測試最終至少在一些硬件上運行。它還有助於找到未使用的固定裝置,或固定裝置中的死代碼。
在目錄上運行覆蓋率
在 ch9/some_code 目錄中,我們有幾個源代碼模塊和一個測試模塊。
ch9/some_code
├──bar_module.py
├──foo_module.py
└── test_some_code.py
執行
$ pytest --cov=ch9/some_code ch9/some_code/test_some_code.py
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0
rootdir: D:\code\pytest_quick, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0
collected 2 items
ch9\some_code\test_some_code.py .. [100%]
---------- coverage: platform win32, python 3.9.13-final-0 -----------
Name Stmts Miss Cover
-----------------------------------------------------
ch9\some_code\bar_module.py 4 1 75%
ch9\some_code\foo_module.py 2 0 100%
ch9\some_code\test_some_code.py 6 0 100%
-----------------------------------------------------
TOTAL 12 1 92%
============================== 2 passed in 0.14s ==============================
# 或者
$ pytest --cov=some_code some_code/test_some_code.py
# 或者
$ pytest --cov=some_code some_code
統計單個文件覆蓋率
ch9/single_file.py
def foo():
return "foo"
def bar():
return "bar"
def baz():
return "baz"
def main():
print(foo(), baz())
if __name__ == "__main__": # pragma: no cover
main()
# test code, requires pytest
def test_foo():
assert foo() == "foo"
def test_baz():
assert baz() == "baz"
def test_main(capsys):
main()
captured = capsys.readouterr()
assert captured.out == "foo baz\n"
下面是它的運行情況。
$ pytest --cov=single_file single_file.py
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0
rootdir: D:\code\pytest_quick, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0
collected 3 items
single_file.py ... [100%]
---------- coverage: platform win32, python 3.9.13-final-0 -----------
Name Stmts Miss Cover
------------------------------------
single_file.py 16 1 94%
------------------------------------
TOTAL 16 1 94%
============================== 3 passed in 0.16s ==============================