テストカバレッジ向上とCLIテスト修正(開発日記 No.094)
関連リンク
はじめに
前回の開発日記では、テンプレートファイル機能とモデル指定機能の実装、そして基本的なテストを完了させました。これで主要な機能は一通り形になったのですが、コードの品質という点ではまだまだ課題が残っています。特にテストカバレッジが低い状態です。
そこで、今日の開発テーマは「テストカバレッジの向上とドキュメント更新」としました。コードの信頼性を高め、同時にユーザーが使いやすいようにドキュメントを整備することを目指します。
背景と目的
現在のプロジェクト全体のテストカバレッジは64%と、決して高いとは言えません。特にいくつかの重要なモジュール(converter.py
, parser.py
, 各種プロバイダーなど)のカバレッジが20%前後と非常に低い状況です。これでは、今後機能を追加したり改修したりする際に、意図しないバグを混入させてしまうリスクが高まります。
また、現在のテストスイートにはいくつかの失敗するテストが含まれています。特にCLI関連のテストが SystemExit
で落ちてしまう問題や、Geminiプロバイダーのテストで環境変数が不足している問題がありました。テストが失敗している状態では、テストスイートがコードの健全性を保証する役割を果たせません。
これらの問題を解決し、コードの信頼性を向上させること、そして機能が増えてきた現状に合わせてドキュメントを最新の状態に保つことが、本日の開発の主な目的です。
検討内容
まずは、現在失敗しているテストを修正することから始めました。テストがパスしない状態では、カバレッジを計測しても意味が薄れてしまうからです。
特にCLIテストの SystemExit
問題は厄介でした。CLIアプリケーションは処理の最後に sys.exit()
を呼び出すことがありますが、これをテストコードから直接実行すると、テストランナーごと終了してしまう可能性があります。pytest
の pytest.raises(SystemExit)
を使うことで、特定の終了コードで例外が発生することをテストできますが、テスト対象のコードの書き方によっては工夫が必要です。今回は、テストしやすいようにCLIのエントリーポイントの呼び出し方やモックの設定を見直す方針としました。
Geminiプロバイダーのテストについては、APIキーなどの環境変数が必要になるため、テスト実行時にはこれらの環境変数をモックする必要がありました。unittest.mock.patch.dict
などを使って、テスト実行時だけ一時的に環境変数を設定する方向で修正を進めることにしました。
テストがパスするようになったら、カバレッジの低いモジュールから順にテストを追加していく計画です。特にカバレッジが19%と極端に低い platforms/note/provider.py
や、主要なロジックを含む llm/gemini.py
、cli.py
あたりを優先的に対応することにしました。
実装内容
まず取り組んだのは、CLIテストの修正です。test_cli.py
を開き、SystemExit
で失敗しているテストケースを詳しく調べました。原因は、テスト対象の関数が sys.exit()
を呼び出してしまうことでした。
これを解決するために、pytest.raises(SystemExit)
を使用して、テストが期待する終了コードで終了するかどうかを確認するようにテストコードを修正しました。また、CLIのエントリーポイントをテストから呼び出す際に、実際の終了処理がスキップされるように、テストランナーからの呼び出し方を調整しました。さらに、外部依存を排除するためにモックの設定も改善しました。
これらの修正を行った結果、すべてのCLI関連のテストが正常にパスするようになりました!
テストがパスしたことを確認した後、現在のカバレッジを再計測しました。その結果、cli.py
のテストカバレッジが修正前の53%から91%へと大幅に向上していることが確認できました。
この修正内容は、fix/cli-tests
というブランチを作成してコミットし、リモートリポジトリにプッシュしました。コミットハッシュは 398b3e4
です。
git checkout -b fix/cli-tests
# テストコードを修正...
git add .
git commit -m "fix: Resolve SystemExit in CLI tests and improve mocking"
git push origin fix/cli-tests
この時点で、プロジェクト全体のカバレッジはまだ64%のままですが、主要なモジュールの一つである cli.py
のカバレッジが大きく改善されたのは大きな進歩です。
技術的なポイント
CLIアプリケーションのテストで sys.exit()
を扱うのは、少し注意が必要です。テスト対象の関数が直接 sys.exit()
を呼んでいる場合、その関数をテストコードから呼び出すと、テストプロセス自体が終了してしまい、テスト結果が得られなくなります。
これを回避するための一般的な方法としては、以下のようなものがあります。
-
pytest.raises(SystemExit)
を使う: テスト対象のコードがsys.exit(exit_code)
を呼ぶことを期待する場合、with pytest.raises(SystemExit) as excinfo:
のように囲み、excinfo.value.code
で終了コードを確認します。 -
sys.exit
をモックする:unittest.mock.patch('sys.exit')
などを使って、sys.exit
が呼ばれても実際には終了せず、モックオブジェクトが呼び出されたことを確認するようにします。 -
終了処理とロジックを分離する: アプリケーションのコアロジックと、その結果を受けて
sys.exit
を呼ぶ部分を分離し、コアロジックだけをテスト可能にする設計にします。
今回は pytest.raises(SystemExit)
を使う方法で対応し、テストが期待通りに終了コードを返すことを確認できるように修正しました。これにより、CLIの正常終了やエラー終了のパスをテストできるようになりました。
所感
CLIテストの SystemExit
問題が解決し、関連モジュールのカバレッジが91%まで上がったのは、今日の大きな成果です。地味な修正ではありましたが、テストが失敗している状態が解消されたことで、精神的にもスッキリしました。これで安心して次のステップに進めます。
ただ、全体のカバレッジはまだ64%と、目標の80%以上には程遠い状況です。特にプロバイダー関連や、Markdownのパース・変換を行うモジュールのカバレッジが低いままです。これらのモジュールはアプリケーションの根幹に関わる部分なので、しっかりとテストを書いて信頼性を高める必要があります。
テストを書く作業は、新しい機能を実装するのに比べて地味で時間がかかるように感じ
Discussion