5分で始める:PynguinのインストールとTest自動生成
計算処理を含むいくつかの関数が書かれたPythonファイルがあるのに、まだテストがない——そんな場面を想像してください。「どんなテストケースが必要だろう」と頭を悩ませる前に、AIに任せてしまいましょう。
Pynguinをインストールする:
pip install pynguin
calculator.pyファイルをいくつかのサンプル関数で作成する:
def add(a: int, b: int) -> int:
return a + b
def divide(a: float, b: float) -> float:
if b == 0:
raise ValueError("0で割ることはできません")
return a / b
def is_palindrome(s: str) -> bool:
s = s.lower().strip()
return s == s[::-1]
Pynguinを実行してテストを自動生成する:
pynguin \
--project-path . \
--module-name calculator \
--output-path tests/
数十秒後、Pynguinがtests/test_calculator.pyを生成します。出力例はこのようになります:
import pytest
from calculator import add, divide, is_palindrome
def test_add_0():
assert add(0, 0) == 0
def test_add_1():
assert add(1, 2) == 3
def test_divide_0():
assert divide(4.0, 2.0) == pytest.approx(2.0)
def test_divide_raises():
with pytest.raises(ValueError):
divide(1.0, 0.0)
def test_is_palindrome_0():
assert is_palindrome("racecar") is True
def test_is_palindrome_1():
assert is_palindrome("hello") is False
テストを実行してcoverageを確認する:
pytest tests/ -v --cov=calculator --cov-report=term-missing
これで完了です。テストがゼロの状態から、数分で80〜90%のcoverageに到達できます。
Pynguinの仕組み
Pynguinはsearch-based software testing(SBST)アルゴリズムを採用しています——具体的には遺伝的アルゴリズムを使ってテストケースを複数世代にわたって「進化」させ、ブランチカバレッジを最大化します。やみくもにランダムなのではなく、できるだけ多くのコードブランチをカバーするという目標に向けて最適化を行います。
実際に使ってみて面白いと感じたのは、Pynguinがエッジケースの発見に優れているという点です——特に例外ブランチの検出が得意です。None、負の数、空文字列、非常に大きなfloatなどを自動的に試して、関数がどう処理するかを確認します。以前のコードでtype checkingに関する隠れたバグがいくつかありましたが、Pynguinが初回実行でそれらをすべて検出してくれました。
重要な前提条件: コードにはtype hintsが必要です。Pynguinはどの型の入力値を生成すべきかを判断するためにtype annotationsに依存しています。a: intや-> floatといった型指定のない関数では、生成されるテストが極端に限られるか、まったく生成できない場合があります。
CodiumAI:カバレッジ計測だけでなくロジックを理解するAI
Pynguinはstructural coverageに優れていますが、生成されるテストはやや「機械的」です——test_add_0のような名前や、add(1234567, -9876543)のような意味不明な値が並びます。こうした場面でCodiumAI(現在はQodoに改名)が真価を発揮します。
CodiumAIはLLMを使って関数のビジネスロジックを読み解き、意味のあるテストケースを生成します——テスト名が振る舞いを説明し、より現実的なシナリオをカバーします。
VS CodeでCodiumAIをインストールする
VS Code Marketplaceで「Qodo Gen」(旧名:CodiumAI)を検索してインストールし、無料アカウントを作成します。インストール後、Pythonファイルを開いて任意の関数名をクリックすると——「Generate Tests」ボタンがその関数のすぐ上に表示されます。クリックするとAIが解析を行い、提案されたテストケースが右側のパネルに表示されます。
先ほどのdivide()関数に対して、CodiumAIが生成するテストはこのようになります:
class TestDivide:
def test_divide_positive_numbers(self):
"""正の数同士の除算——正確な結果が返ること"""
assert divide(10.0, 2.0) == 5.0
def test_divide_negative_dividend(self):
"""負の被除数でも正しく計算されること"""
assert divide(-10.0, 2.0) == -5.0
def test_divide_by_zero_raises_value_error(self):
"""0除算時にValueErrorが正しいメッセージで送出されること"""
with pytest.raises(ValueError, match="0で割ることはできません"):
divide(5.0, 0.0)
def test_divide_result_precision(self):
"""float型の結果が正確な精度を保つこと"""
result = divide(7.0, 2.0)
assert result == pytest.approx(3.5)
同じ関数のテストでも、シナリオを明確に説明する名前があり、docstringがあり、エラーメッセージまで検証しています。コードレビューに出しても、チームメンバーがすぐに読めて説明不要です。
両者を組み合わせる:実践的な戦略
実務を通じて、これは習得すべき重要なスキルの一つだと実感しました——PynguinかCodiumAIかを選ぶのではなく、フェーズに応じて両方を使い分けることが大切です:
- Pynguin — テストのない既存コードベースのcoverageを素早く確保したいときに使う。一度実行するだけで60〜80%のベースラインが手に入る。
- CodiumAI — 新機能に使う。意味があり、レビュー可能で、CI/CDに組み込んでも恥ずかしくないテストが必要なときに。
実践的なステップバイステップのワークフロー
# ステップ1:Pynguinでモジュール全体をスキャンする
pynguin \
--project-path . \
--module-name myapp.utils \
--output-path tests/auto/ \
--maximum-search-time 120
# ステップ2:現在のcoverageを確認する
pytest tests/ --cov=myapp --cov-report=html
# ステップ3:htmlcov/index.htmlを開き、まだ赤いブランチを確認する
# ステップ4:CodiumAIを使って該当ブランチに的を絞ったテストを書く
複雑なコードへのPynguinカスタマイズ
デフォルトでは、Pynguinはモジュールあたり600秒実行されます。ブランチの多いモジュールには、DYNAMOSAアルゴリズムに切り替えましょう:
pynguin \
--project-path . \
--module-name myapp.services.payment \
--output-path tests/auto/ \
--maximum-search-time 300 \
--algorithm DYNAMOSA \
--seed 42
DYNAMOSA(Dynamic Many-Objective Sorting Algorithm)は、モジュールにネストした条件分岐が多い場合により効果的に機能します。
見逃せない実践的なTips
1. Pynguinを実行する前にtype hintsを追加する。 これは必須条件です。アノテーションのない既存コードベースには、mypy --install-typesで自動提案を受けるか、MonkeyTypeを使ってランタイムからtype hintsを自動追加しましょう。
2. AIが生成したテストはcommit前に必ず確認する。 Pynguinはビジネスロジックを理解しません——関数の現在の出力をそのまま記録するだけです。関数にバグがあれば、テストはそのバグを正しいものとしてassertします。以前、テストがすべてpassしていたにもかかわらず割引計算のロジックが誤っていたケースがありましたが、Pynguinは実際の出力が10だったためassert discount == 10を生成していました。
3. 効果を明確に把握するために、前後のcoverageを計測する:
# 適用前
pytest --cov=myapp --cov-report=term | grep TOTAL
# TOTAL 342 289 15%
# AI生成テスト追加後
pytest --cov=myapp --cov-report=term | grep TOTAL
# TOTAL 342 62 82%
4. 外部依存関係を先にモックする。 PynguinもCodiumAIも、データベースやHTTPを呼び出す関数に対しては効果的なテストを生成できません。純粋なロジック部分をI/O部分から分離するか、モックを使いましょう:
from unittest.mock import patch, MagicMock
def test_fetch_user_with_mock():
with patch("myapp.services.requests.get") as mock_get:
mock_get.return_value = MagicMock(
status_code=200,
json=lambda: {"id": 1, "name": "Test User"}
)
result = fetch_user(1)
assert result["name"] == "Test User"
5. 関数にdocstringがあると、CodiumAIはより良いテストを生成する。 振る舞いと期待されるケースを明確に説明するdocstringがあれば、LLMはコンテキストを理解して、何もない関数よりもずっと適切なシナリオのテストを生成します。
両ツールの真の強みはテストを書くことを代替することではなく——素早く始める手助けをし、基本的な部分のcoverageを確保し、見落としがちなエッジケースを提案することにあります。重要なビジネスロジックに関するテストは、引き続き手動で書いてしっかりとレビューすべきです。

