pythonのdoctestとcoverageでコメントの説明の十分性を簡易確認

pythonでは、ドキュメントとして書かれたコメントと、実際の動作が一致するか確認する手段として、doctestを標準で提供しています。
題材として、大まかな動作の説明をdocstringに書いた、以下のhoge()という簡単なメソッドを扱います。

# coding:utf-8
# sample.py
import re

def hoge(line):
	'''
	指定した文字列から定数名の定義を抽出する
	# (1)プリプロセッサのマクロ定義名を抽出する
	>>> from sample import hoge
	>>> hoge('#define PREPRO_MACRO 33')
	'PREPRO_MACRO'
	
	# (2)const変数の名前を抽出する
	>>> hoge('const int const_variable_name = 33;')
	'const_variable_name'
	'''
	
	result = re.search('[\s\t]*#define[\s\t]+([a-zA-Z_0-9]*)', line)
	if result != None:
		return result.group(1)
	result = re.search('[\s\t]*const[\s\t]+[a-zA-Z_0-9]*[\s\t]+([a-zA-Z0-9_]*)[\s\t]+=', line)
	if result != None:
		return result.group(1)
	result = re.search('[\s\t]*constexpr[\s\t]+[a-zA-Z_0-9]*[\s\t]+([a-zA-Z_0-9]*)[\s\t]+=', line)
	if result != None:
		return result.group(1)
	return ''

これを以下のようなコマンドで、doctest上で実行します。

python -m doctest sample.py

すると、doctestはコメント内容に基づき、hoge()を実行して以下の確認を行います。

  • hoge('#define PREPRO_MACRO 33')」の戻り値が「'PREPRO_MACRO'」であること。
  • hoge('const int const_variable_name = 33;')」の戻り値が「'const_variable_name'」であること。

たとえば適当なバグをコードに埋め込むと、次のようにコメントの仕様と動作の差異を報告してくれます。

File "sample.py", line 14, in sample.hoge
Failed example:
    hoge('const int const_variable_name = 33;')
Expected:
    'const_variable_name'
Got:
    ''

このdoctestは外部向けに仕様を解説するコメントの保守に便利です。特にTest as DocumentationやSpecification by Exampleを推進するテストファースト手法を直接的にサポートしてくれます。

カバレッジでコメント解説の十分性を評価

doctestではコードカバレッジの計測も可能です。doctestでのコードカバレッジを見ることで、コメントの解説の十分性を簡易チェックできるようになります。
(あくまで参考指標です。ここで「doctestでカバレッジ100%得られるまでコメントを書くこと」を強制しだすと当然おかしくなります)

例えばcoverage.pyを使う場合、以下のようなコマンドを実行します。

coverage run -m doctest sample.py

すると以下のconstexprについてのコードが実行されていないことがカバレッジレポートで報告されます。それで、該当部分についてのコメントの説明が足りないことに気づけます。

result = re.search('[\s\t]*constexpr[\s\t]+[a-zA-Z_0-9]*[\s\t]+([a-zA-Z_0-9]*)[\s\t]+=', line)
if result != None:
    return result.group(1)