昔作ったPython製ライブラリのカバレッジ測ってみた
かっこ悪くて面倒でもテストコードを書こうを読んで。
私は職業プログラマではありませんが、unittestでテストを書きながらコーディングすれば良い物が出来るという体感はありました。で、カバレッジって何だ?どうやって測るんだ?って思って調べてみると、Pythonではcoverageパッケージを使えばC0: 命令網羅率(ステートメントカバレッジ)が簡単に測れるとのこと。
それでは昔作ったPython製ライブラリ(PyAmazonCloudDrive)でやってみよー!
coverageのインストール
easy_installするだけ
C:\temp>easy_install coverage Searching for coverage Reading http://pypi.python.org/simple/coverage/ Reading http://nedbatchelder.com/code/modules/coverage.html Reading http://nedbatchelder.com/code/coverage Reading http://nedbatchelder.com/code/coverage/3.5b1 Reading http://nedbatchelder.com/code/coverage/3.4b1 Reading http://nedbatchelder.com/code/coverage/3.4b2 Reading http://nedbatchelder.com/code/coverage/3.5.1b1 Best match: coverage 3.5.1 Downloading http://pypi.python.org/packages/2.7/c/coverage/coverage-3.5.1.win32-py2.7.exe#md5=04b9eb49d9e34a5b7310550f657a8a27 Processing coverage-3.5.1.win32-py2.7.exe Deleting c:\users\sakurai\appdata\local\temp\easy_install-wo97iz\coverage-3.5.1-py2.7-win32.egg.tmp\EGG-INFO\scripts\coverage-script.py Deleting c:\users\sakurai\appdata\local\temp\easy_install-wo97iz\coverage-3.5.1-py2.7-win32.egg.tmp\EGG-INFO\scripts\coverage.exe Deleting c:\users\sakurai\appdata\local\temp\easy_install-wo97iz\coverage-3.5.1-py2.7-win32.egg.tmp\EGG-INFO\scripts\coverage.exe.manifest creating 'c:\users\sakurai\appdata\local\temp\easy_install-wo97iz\coverage-3.5.1-py2.7-win32.egg' and adding 'c:\users\sakurai\appdata\local\temp\easy_install-wo97iz\coverage-3.5.1-py2.7-win32.egg.tmp' to it creating c:\python27\lib\site-packages\coverage-3.5.1-py2.7-win32.egg Extracting coverage-3.5.1-py2.7-win32.egg to c:\python27\lib\site-packages Adding coverage 3.5.1 to easy-install.pth file Installing coverage-script.pyc script to C:\Python27\Scripts Installing coverage-script.py script to C:\Python27\Scripts Installing coverage.exe script to C:\Python27\Scripts Installing coverage.exe.manifest script to C:\Python27\Scripts Installed c:\python27\lib\site-packages\coverage-3.5.1-py2.7-win32.egg Processing dependencies for coverage Finished processing dependencies for coverage
使い方
eraseで統計データの初期化、runでテストコードの実行/計測、reportでカバレッジの計測結果の表示、htmlでHTMLレポート作成とのこと。(※自身の環境ではC:\Python27\Scriptsにパスを通してます。)
C:\temp>coverage --help Coverage.py, version 3.5.1 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. Use "coverage help classic" for help on older command syntax. For more information, see http://nedbatchelder.com/code/coverage
やってみる
予想外にテストでERRORが発生しましたが、まぁ良しとします。例外が発生したおかげもあって(?)全体として85%のカバレッジらしいです。個人的には、まぁまぁな数字じゃないかと思います。
- coverage erase(統計データの初期化:一応)
- coverage run test.py(テストコードの実行/計測:コマンドライン引数も問題なし)
- coverage report(カバレッジの計測結果の表示:引数なしで全スクリプトの計測結果を表示)
- coverage html(HTMLレポート作成:htmlcovというフォルダに生成されます)
C:\temp\pyacd>coverage erase C:\temp\pyacd>coverage run test.py someone@example.com password *** Inputs are here *** email: someone@example.com password: password ******************** testLogin (__main__.AuthTest) ... GET->GET->GET->GET->POST->GET->ok ---------------------------------------------------------------------- Ran 1 test in 9.873s OK testEmptyRecycleBin (__main__.ApiTest) ... GET->ok testFile_Create_Upload_Download (__main__.ApiTest) ... GET->GET->POST->GET->GET->GET->GET->ok testFolder_Create_Rename_Copy_Recycle_Remove (__main__.ApiTest) ... GET->GET->GET->GET->GET->GET->GET->GET->ERROR testInfoByPathAndById (__main__.ApiTest) ... GET->GET->ok testListById (__main__.ApiTest) ... GET->GET->ok testSubscriptionProblem (__main__.ApiTest) ... GET->ok testUserStorage (__main__.ApiTest) ... GET->ok ====================================================================== ERROR: testFolder_Create_Rename_Copy_Recycle_Remove (__main__.ApiTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "test.py", line 144, in testFolder_Create_Rename_Copy_Recycle_Remove pyacd.api.remove_bulk_by_id([folder1.object_id,]) File "C:\temp\pyacd\pyacd\api.py", line 259, in remove_bulk_by_id _operate2_bulk_by_id("removeBulkById",source_inclusion_ids) File "C:\temp\pyacd\pyacd\api.py", line 278, in _operate2_bulk_by_id _error_check(resp_json) File "C:\temp\pyacd\pyacd\api.py", line 82, in _error_check raise pyacd.PyAmazonCloudDriveApiException(resp_json.get("Error")) PyAmazonCloudDriveApiException: PyAmazonCloudDriveError: MetadataNotFoundException:87940d81-1e17-41c8-a99e-9c660f213aa9 does not exist. ---------------------------------------------------------------------- Ran 7 tests in 19.612s FAILED (errors=1) C:\temp\pyacd>coverage report Name Stmts Miss Cover --------------------------------------- pyacd\__init__ 19 0 100% pyacd\api 197 31 84% pyacd\apiresponse 95 16 83% pyacd\auth 87 10 89% pyacd\connection 84 35 58% pyacd\exception 16 2 88% pyacd\multipart 46 2 96% pyacd\status 3 0 100% pyacd\types 5 0 100% test 81 2 98% --------------------------------------- TOTAL 633 98 85% C:\temp\pyacd>coverage html
HTMLレポートを見てみる
テストできていない部分(未実行のコード)が赤でハイライトされるため一目瞭然です。個人的には各クラスの__repr__や__str__のテストが抜ける傾向にあるんだなぁと気付きが有りました。
まとめ
coverageパッケージはかなりイイです。なんとなく書いたテストがどの程度の網羅性を持っているか、定量的に図れるメリットは非常に大きいと思います。「coverage run」でどんなスクリプトでも計測できるので、これからは必ずこのパッケージを使っていこうと思いました(マル)
追記:分岐網羅(C1)について
id:imagawa_yakataさんにコメントで教えて頂きましたC1:分岐網羅について。「coverage run」に「--branch」オプションをつけると、分岐の網羅性についても計測出来るとのこと。ということで早速、C1分岐網羅性の測定もやったところ82%でした。これもまずまず(?)かなぁと。。。
C:\temp\pyacd>coverage run --branch test.py *** Inputs are here *** email: password: ******************** testLogin (__main__.AuthTest) ... GET->GET->GET->GET->POST->GET->ok ---------------------------------------------------------------------- Ran 1 test in 9.134s OK testEmptyRecycleBin (__main__.ApiTest) ... GET->ok testFile_Create_Upload_Download (__main__.ApiTest) ... GET->GET->POST->GET->GET->GET->GET->ok testFolder_Create_Rename_Copy_Recycle_Remove (__main__.ApiTest) ... GET->GET->GET->GET->GET->GET->GET->GET->ERROR testInfoByPathAndById (__main__.ApiTest) ... GET->GET->ok testListById (__main__.ApiTest) ... GET->GET->ok testSubscriptionProblem (__main__.ApiTest) ... GET->ok testUserStorage (__main__.ApiTest) ... GET->ok ====================================================================== ERROR: testFolder_Create_Rename_Copy_Recycle_Remove (__main__.ApiTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "test.py", line 144, in testFolder_Create_Rename_Copy_Recycle_Remove pyacd.api.remove_bulk_by_id([folder1.object_id,]) File "C:\temp\pyacd\pyacd\api.py", line 259, in remove_bulk_by_id _operate2_bulk_by_id("removeBulkById",source_inclusion_ids) File "C:\temp\pyacd\pyacd\api.py", line 278, in _operate2_bulk_by_id _error_check(resp_json) File "C:\temp\pyacd\pyacd\api.py", line 82, in _error_check raise pyacd.PyAmazonCloudDriveApiException(resp_json.get("Error")) PyAmazonCloudDriveApiException: PyAmazonCloudDriveError: MetadataNotFoundException:f5a962d0-5578-4ce1-8c3c-c2a85bd8017c does not exist. ---------------------------------------------------------------------- Ran 7 tests in 21.944s FAILED (errors=1) C:\temp\pyacd>coverage report Name Stmts Miss Branch BrPart Cover ----------------------------------------------------- pyacd\__init__ 19 0 0 0 100% pyacd\api 197 31 40 18 79% pyacd\apiresponse 95 16 8 1 83% pyacd\auth 87 10 25 4 88% pyacd\connection 84 35 36 9 63% pyacd\exception 16 2 2 1 83% pyacd\multipart 46 2 10 3 91% pyacd\status 3 0 0 0 100% pyacd\types 5 0 0 0 100% test 81 2 6 2 95% ----------------------------------------------------- TOTAL 633 98 127 38 82%