🦊
雑にPythonの関数修正時にそれが呼び出している処理を特定する
忙しい人向け
pytestとpytest-covを使ってカバレッジを出力し、依存するコードを洗い出す。
カバレッジを出力する方法。
- ローカル環境やリモート上の開発環境でコマンドライン環境が使用できる
- pytestコマンドを使用する (記事内リンク)
- データ基盤や分析基盤等Jupyter上でしかコードを実行できない
- jupyter上ipytestを使用する (記事内リンク)
- 関数の呼び出し履歴を見たい
- 標準ライブラリのtraceモジュールを使用する (記事内リンク)
こんな感じで
はじめに
プロジェクト途中参加してコードを修正してくださいと言われた時、どこ直せばいいかわからないくてどうしましょうねってなったことがあったので雑にどこ直せばいいか調べるにやった方法の忘備録です。
ぱっと思いつく方法はこれ↓。
- (この記事でやる) テストコードを書いてカバレッジを見る
- (この記事でやる) サンプルコード書いて関数の呼び出し履歴をトレースする
- loggingやブレークポイントぽちぽちしてprintfデバッグやデバッガでステップ実行する
- 内容知ってる人を質問攻めする(一番楽)
- コード読んで人力で呼び出しの依存関係調べる(辛い)
pytest-covのカバレッジから影響範囲特定する
pytestによるカバレッジ生成
pytestのプラグインであるpytest-covを使用すると、テストカバレッジを出力することができます。
サンプルコード
このコードではchainer.functions.sigmoid()を呼び出した際にchainerソースコードのカバレッジを出力する。
出力結果のカバレッジからどこの処理が影響範囲なのか雑に特定することができる。
import chainer.functions as F
import numpy as np
def test_sigmoid():
F.sigmoid(np.arange(10).astype(np.float32))
pytest --cov=`python -c "import chainer; print(chainer.__path__[0])"` --cov-report=html tests
ipytestによるjupyter上でカバレッジ生成
ipytestはjupyter(IPython)のセル上で定義したpytestのテストコードを実行するライブラリです。
また、ipytest.run
メソッドにpytestのコマンドラインオプションを渡すことでpytestの細かい設定をすることもできます。
上記テストと同じものをipythonを使って実行する例
# %%
import ipytest
import chainer
import chainer.functions as F
import numpy as np
# %%
def test_sigmoid():
F.sigmoid(np.arange(10).astype(np.float32))
# %%
ipytest.run(
f"--cov={chainer.__path__[0]}",
"--cov-report=html",
)
実行結果
実行すると、カレントディレクトリにhtmlcov
というディレクトリが生成され、テストコードのカバレッジが出力されます。
※サンプルコードではchainerのソースコードが入ってるパッケージディレクトリを指定したのでchainerソースコードのカバレッジが出力される。
この記事ではsigmoid()関数のテストを書いたので、そのコードを呼び出す際に通過したコードがカバレッジとして出力されます。
traceモジュールを使用する
標準ライブラリのtraceモジュールを使用することで呼び出した関数一覧を取得することができる
サンプルコード
import trace
import chainer.functions as F
import numpy as np
def example():
test_sigmoid()
def test_sigmoid():
F.sigmoid(np.arange(10).astype(np.float32))
tracer = trace.Trace(
countfuncs=1,
)
tracer.runfunc(example)
result = tracer.results()
for key, val in result.calledfuncs.items():
print(f"{key}: {val}")
出力例
('/var/folders/kp/_p1nx3ld5n70jx5y0qg6m3d40000gn/T/ipykernel_18663/2330830199.py', '2330830199', 'example'): 1
('/var/folders/kp/_p1nx3ld5n70jx5y0qg6m3d40000gn/T/ipykernel_18663/2330830199.py', '2330830199', 'test_sigmoid'): 1
('/Users/user/.pyenv/versions/3.9.7/envs/pytest_cov/lib/python3.9/site-packages/chainer/functions/activation/sigmoid.py', 'sigmoid', 'sigmoid'): 1
('/Users/user/.pyenv/versions/3.9.7/envs/pytest_cov/lib/python3.9/site-packages/chainer/function_node.py', 'function_node', 'FunctionNode.apply'): 1
('/Users/user/.pyenv/versions/3.9.7/envs/pytest_cov/lib/python3.9/site-packages/chainer/function_node.py', 'function_node', '_extract_apply_in_data'): 1
('/Users/user/.pyenv/versions/3.9.7/envs/pytest_cov/lib/python3.9/site-packages/chainerx/__init__.py', '__init__', 'is_available'): 1
('/Users/user/.pyenv/versions/3.9.7/envs/pytest_cov/lib/python3.9/site-packages/chainer/function_node.py', 'function_node', '<listcomp>'): 1
('/Users/user/.pyenv/versions/3.9.7/envs/pytest_cov/lib/python3.9/site-packages/chainer/function_node.py', 'function_node', 'label'): 1
('/Users/user/.pyenv/versions/3.9.7/envs/pytest_cov/lib/python3.9/site-packages/chainer/utils/__init__.py', '__init__', '_check_arrays_forward_compatible'): 1
('/Users/user/.pyenv/versions/3.9.7/envs/pytest_cov/lib/python3.9/site-packages/chainer/__init__.py', '__init__', 'is_arrays_compatible'): 1
長いので以下略
Discussion