ENECHANGE Developer Blog

ENECHANGE開発者ブログ

AIペアプログラミングで自動テストを習得:Vibe Codingをテスト学習に応用してみた

ENECHANGEでQAエンジニアをしています。id:RyoTanitanです。 好きなことは効率化で、プロセスの改善や品質向上の取り組みをしています。

「AIとの壁打ち」をテスト技術の学習に応用できないか?

昨日のsjisjisさんの記事「マネージドに依存しないTerraform CIでPR可視化と段階的整備 - Claude Codeと進めるGitHub Actionsへのtfcmt導入」では、新しいツールを導入する前にClaude Codeとの「壁打ち」対話によって要件定義や課題整理を行うことで、検討の視野を広げつつ設計や実装を素早く行っておられました。

私はこの手法を見て、「これ、テストの学習にも使えるのでは?」と考え、Vibe Codingをテスト学習に特化させた「Vibe Test Coding」を実践してみました。具体的には:

  • AIにテスト対象のアプリを作ってもらう
  • 分からないことはその場で質問
  • 複数のテストパターンを比較検討

この手法でClaude Codeを相談相手に自動テストを学んだところ、想像以上に効果的だったので、その体験を3つの観点から紹介します。

※ 「Vibe Coding」は、AIに自然言語で「こんな感じで」と指示してコードを生成してもらう手法を指す用語です。「Vibe Test Coding」は私がこの手法をテスト学習に応用した造語です。

Vibe Test Codingの利点1:AIがテスト対象を即座に生成

自動テストを学ぶ際の最初のハードルは、適切なテスト対象を見つけることです。

本に書かれているサンプルコードは理解のためには役に立ちますが、その分単純化されているため、実務のコードをみると、「どこから手をつければいいのか・・・」と戸惑ってしまいます。

最近は、HOTEL PLANISPHERE - テスト自動化練習サイトがあることで練習しやすくなりましたが、APIテストや単体テストはなかなか手頃なものがみつからないと思っていました。

私が感じていた課題:現実的な複雑さのテスト対象がない

テストの練習をしようにも、以下のジレンマに悩まされていました:

  • 単純すぎる例:「足し算関数のテスト」では実務に活かせない
  • 複雑すぎる例:既存の大規模OSSは理解するだけで時間がかかる
  • 環境依存の問題:DBやAPIの設定でつまずいて本質的な学習ができない

解決:要件を伝えるだけで適切な複雑さのプロダクトが完成

Claude Codeに頼めば、動くプロダクトをすぐに作成してくれます!

また、プロダクトによってはDBや外部APIの呼び出しなど、外部コンポーネントに依存した構成になっていることもあります。

こういったテストで障害になりやすい技術的な要素も再現することも可能です。

今回は、「テスト学習用に、適度に複雑なプロダクトを作りたい」「RAGとかAIを使っているといい」「外部サービスに依存しているとベスト」と相談したところ、「RAGベースのテスト支援ツールはどうですか?」という提案を受けました。

その後OKを出して何度かFBをするだけで、期待していた以下の技術要素が含まれていました。

  • FastAPIによる REST API サーバー
  • ChromaDBによるベクトルデータベース
  • OpenAI/HuggingFace の埋め込みモデル
  • LangChainによる RAG パイプライン

AIが作ったコードだからこそ純粋なテスター視点を保てた

開発者がテストを書くとき、実装の詳細を知りすぎているため、ブラックボックステストの視点を持ちにくいという問題があります。しかし、AIが生成したコードであれば、私は「テストエンジニア」として純粋な視点で向き合えました。

例えば、VectorStoreManagerクラスという、AIサービスで生成したベクトルをDBに保存するクラスを見たとき、私は実装詳細ではなく「このクラスの責務は何か?」という視点から考えることができました。

自分が実装者で、「ここでは何をテストしたいか?」を考えながらテストしていたとしたら、内部視点でテストを書くことになり、このクラスの責務という、外側からの視点でテストを考えることはできなかったと思います。

Vibe Test Codingの利点2:疑問を対話で即座に解決

従来の学習方法では、疑問が生じるたびに学習が中断されます。書籍を調べ、Webを検索し、それでも解決しないことも多いでしょう。

自分の環境特有の問題に悩まされたりと、本質的ではないところに時間を取られてしまうことが多くあります。

私もPythonやJSで何度もトライしましたが、ライブラリや言語の仕様のキャッチアップに苦労して続けることができませんでした。

疑問をその場で解決:シングルトンパターンとデコレータ

こういった質問がたくさん出てくるのですが、以前は本で調べて、自分でサンプルを作って納得していました。

これもいい学習体験でしたが、時間がかかってしまいます。

また、設計パターンのような抽象的なことでは、理解ができないこともありました。

しかし、AIに聞けばすぐに説明をしてもらえます。わからないところがあれば更に聞くこともできます。

実際の質問例:シングルトンパターン

私:「VectorStoreManagerのnewメソッドにあるシングルトンって何ですか?」

Claudeの回答: シングルトンパターンは、クラスのインスタンスを1つだけに制限するデザインパターンです。

VectorStoreManagerの場合:


class VectorStoreManager:
    _instance = None  # クラス変数でインスタンスを保持

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance  # 常に同じインスタンスを返す

これにより、何度VectorStoreManager()を呼んでも同じインスタンスが返されます。

データベース接続のように、システム全体で1つだけ存在すべきリソースを管理する場合に使用します。

テストの観点では注意が必要です:

  • 各テストが独立していることを保証するため、setup_methodでシングルトンをリセット

  • VectorStoreManager._instance = None でリセット可能

実際の質問例:デコレータ

私:「@pytest.mark.parametrizeってデコレータですか?何をしているんですか?」

Claudeの回答: はい、デコレータです!@マークで始まる記法がPythonのデコレータです。 関数やクラスを修飾(デコレート)して、動作を拡張します。


@pytest.mark.parametrize("api_key,model", [...])
def test_embedding_init(api_key, model):
    # このテストは複数回実行される

このデコレータは、1つのテスト関数を複数のパラメータで実行します。

VectorStoreManagerのテストでは、OpenAI/HuggingFace/Fakeの3パターンを

1つの関数で効率的にテストできます。

こういった初心者的な疑問がすぐ解決することで、本来進めたかった自動テストの学習が進みます。

特に、今見ているコードを例に解説してくれることで、文脈に合わせた置き換えが不要です。

もちろんAIの回答をすべて信じることはできませんが、調べるために時間を使うことに比べれば学習効率は高いと感じました。

Vibe Test Codingの利点3:複数パターンを短時間で比較検証

テスト設計には複数のアプローチが存在しますが、それぞれのメリット・デメリットを実際に体験することは時間的に困難です。

Claude Codeとの対話では、複数のアプローチを実際に試して比較できました。

AAA(Arrange-Act-Assert)パターンとGherkin (Given-When-Then)パターンの比較

テストコードの構造化パターンについて、実際のコードを見ながら理解できました。

私:「テストコードの構造がバラバラになってしまいます。何か標準的な書き方はありますか?」

Claude:「はい、業界標準のAAAパターンがあります。VectorStoreManagerのテストで見てみましょう:

def test_singleton_pattern():
    # Arrange(準備):テストに必要な状態を設定
    VectorStoreManager._instance = None  # シングルトンをリセット

    # Act(実行):テスト対象の動作を実行
    manager1 = VectorStoreManager()
    manager2 = VectorStoreManager()

    # Assert(検証):期待する結果を確認
    assert manager1 is manager2  # 同じインスタンスであることを確認

BDD(振る舞い駆動開発)では、Given-When-Thenパターンも使われます:

def test_embedding_fallback():
    '''
    Given: OpenAI APIキーが無効な状態
    When: VectorStoreManagerを初期化する
    Then: HuggingFaceEmbeddingsにフォールバックする
    '''
    # Given
    mock_settings.OPENAI_API_KEY = None

    # When
    manager = VectorStoreManager()

    # Then
    assert isinstance(manager.embeddings, HuggingFaceEmbeddings)

どちらも同じ3段階構造で、コードの意図が明確になります」

私:「BDDとAAAはどのような使い分けをするといいですか?」

Claudeの回答: チームの文化と、テストの目的によって使い分けるのがおすすめです:

AAAパターンが向いている場面:

  • 単体テスト(Unit Test)
  • 技術的な詳細をテストする場合
  • 開発者同士のコミュニケーション
def test_singleton_thread_safety():
     # Arrange: スレッドセーフのテスト準備
     # Act: 複数スレッドから同時アクセス
     # Assert: 同一インスタンスであることを確認

Given-When-Thenが向いている場面:

  • 受け入れテスト(Acceptance Test)
  • ビジネスロジックのテスト
  • 非技術者(PO、顧客)とのコミュニケーション
def test_ユーザーがドキュメント検索を行う():
    '''
    Given: テストドキュメントが登録済み
    When: ユーザーが「テスト戦略」で検索
    Then: 関連度の高いドキュメントが返される
    '''

このパターンを学んでから、自分の書くテストコードが格段に読みやすくなりました。将来的にチーム開発に参加する際も、この標準的な構造なら共有しやすそうだと感じています。

パラメタライズテストを活用して可読性を高める

同じようなテストを何度も書いていると、コードの重複が気になってきました。

私:「OpenAI、HuggingFace、Fakeの3パターンでテストを書いたら、ほぼ同じコードが3つできました...」

Claude:「パラメタライズテストで解決できます!1つのテスト関数で複数のケースをテストできます:

Before:3つの似たようなテスト関数

def test_openai_embedding():
    manager = VectorStoreManager(api_key="sk-valid")
    assert isinstance(manager.embeddings, OpenAIEmbeddings)

def test_huggingface_embedding():
    manager = VectorStoreManager(api_key=None)
    assert isinstance(manager.embeddings, HuggingFaceEmbeddings)

def test_fake_embedding():
    # 省略...

After:パラメタライズで1つに統合

@pytest.mark.parametrize("api_key,expected_type", [
    ("sk-valid", OpenAIEmbeddings),
    (None, HuggingFaceEmbeddings),
    ("invalid", FakeEmbeddings),
])
def test_embedding_initialization(api_key, expected_type):
    manager = VectorStoreManager(api_key=api_key)
    assert isinstance(manager.embeddings, expected_type)

メリット:

  • コードの重複削減
  • テストケースの追加が簡単
  • 全パターンの網羅性が一目瞭然

managerオブジェクトの生成とassertメソッドの重複が解消したことで、簡潔なコードになりました。

idを使ってログを読みやすくする

パラメタライズテストを導入したあと動作確認をすると、エラーログをみると、それぞれのテストの意図が分かりにくくなっていることに気づきました。

実際の改善例:idを使ったログの読みやすさ

私:「test_embedding_initialization[param0]とか表示されて、何のテストか分からない...」

Claudeの回答: pytest.paramのid引数で解決できます!


@pytest.mark.parametrize("api_key,expected_type", [
    pytest.param("sk-valid", OpenAIEmbeddings, 
                 id="OpenAI正常動作"),
    pytest.param(None, HuggingFaceEmbeddings, 
                 id="HuggingFace_フォールバック"),
    pytest.param("invalid", FakeEmbeddings, 
                 id="全失敗時_Fake使用"),
])

実行結果の違い:

Before

test_embedding.py::test_embedding_initialization[param0] PASSED
test_embedding.py::test_embedding_initialization[param1] PASSED

After

test_embedding.py::test_embedding_initialization[OpenAI正常動作] PASSED
test_embedding.py::test_embedding_initialization[HuggingFace_フォールバック] PASSED 

日本語IDの効果:

  • テスト失敗時、何が失敗したか一目瞭然
  • レポートを見た人が内容を即座に理解
  • 仕様書との対応が明確

この改善により、テスト失敗時にログを見ただけで問題箇所が特定できるようになりました。将来的にCIを導入する際にも、この形式なら効率的にデバッグできそうです。

テスト全体の見通しが悪い

練習用のプロジェクトとはいえ、テストファイルが増えてくると「どこに何のテストがあるのか」「このメソッドはテストされているのか」といった全体像が見えなくなってきました。今後実際のプロジェクトに参加した際、この問題はもっと深刻になりそうだと感じ、Claudeに相談してみました。

実際の質問例:テストの俯瞰手法

私:「テスト全体を俯瞰するためにできることはないですか?」

Claudeの回答:

3つの手法があります:マップ、ツリー、マトリックス

私:「比較したい。それぞれのサンプルをつくってください」

手法1:ツリー構造による階層表現

VectorStoreManagerテスト/
├── Construction責務/
│   ├── test_singleton_pattern ✅
│   └── test_thread_safety ❌(未実装)
├── Initialization責務/
│   ├── test_embedding_initialization/
│   │   ├── OpenAI正常動作 ✅
│   │   ├── HuggingFace_フォールバック ✅
│   │   └── 全失敗時_Fake使用 ✅
│   └── test_vector_store_init ✅
└── Functionality責務/
    ├── test_add_documents ✅
    ├── test_similarity_search ❌(未実装)
    └── test_get_status ✅

メリット:階層構造が直感的、テストの包含関係が明確

デメリット:横断的な関係が見えない、メソッドとの対応が不明確

手法2:マトリックス形式による網羅性確認

┌────────────────────┬──────┬──────┬──────┬──────┬──────┐
│ メソッド\テスト種別│ 正常系│ 異常系│ 境界値│ 性能 │ 並行性│
├────────────────────┼──────┼──────┼──────┼──────┼──────┤
│ __new__            │  ✅  │  -  │  -  │  -  │  ❌  │
│ __init__           │  ✅  │  ✅  │  -  │  -  │  -  │
│ _initialize_embeddings│  ✅  │  ✅  │  ✅  │  -  │  -  │
│ add_documents      │  ✅  │  ❌  │  ✅  │  ❌  │  -  │
│ similarity_search  │  ❌  │  ❌  │  ❌  │  ❌  │  -  │
│ get_status         │  ✅  │  -  │  -  │  -  │  -  │
└────────────────────┴──────┴──────┴──────┴──────┴──────┘

メリット:テストカバレッジが一目瞭然、テスト種別の偏りが発見しやすい

デメリット:テストの詳細が見えない、責務の概念が表現しにくい

手法3:最終的に選んだテストマップ方式

"""
VectorStoreManager テスト全体マップ
============================================

🎯 責務領域の説明:
- Construction: オブジェクトの構築・生成
- Initialization: 内部コンポーネントの初期化
- Functionality: ビジネス機能の提供

📋 メソッド-責務-テスト対応:
┌─────────────────────┬─────────────────┬─────────────────────┐
│ 対象メソッド         │ 責務領域         │ テストケース         │
├─────────────────────┼─────────────────┼─────────────────────┤
│ __new__             │ Construction    │ test_singleton      │
│ _initialize_embeddings│ Initialization  │ test_embedding_init │
│ add_documents       │ Functionality   │ test_add_docs       │
└─────────────────────┴─────────────────┴─────────────────────┘
"""

検討前はツリー方式かなと考えていたものの、実例を作るとテスト観点との対応関係がわかりにくいことが気になり、テストマップ方式で進めることにしました。

こういった実例を短時間で作れることもAIならではのアプローチかと思います。

まとめ:Vibe Codingのテスト学習への応用がもたらす可能性

一般的なVibe Codingは「AIと対話しながらコードを生成する」手法ですが、これをテスト学習に応用した「Vibe Test Coding」により、以下の成果を得られました:

得られた3つの成果

  1. 学習環境の即座構築: 適切な複雑さのテスト対象を数分で準備
  2. 知識ギャップの即座解消: シングルトンやデコレータなどの概念を文脈に沿って理解
  3. 実践的な比較学習: AAA/BDD、ツリー/マトリックス/マップなど複数手法を実体験

Vibe Codingの「細かい実装はAIに任せて本質に集中する」という思想は、テスト学習においても「環境構築や文法エラーに悩まずテスト設計に集中する」という形で活きました。

今後の展開

このアプローチは、Vibe Codingが持つ「AIとの協調作業」という特性を、学習という文脈で最大限に活用した例と言えるでしょう。今後、他の技術領域の学習にも応用できる可能性を感じています。

  • チーム開発での実践
  • CI/CDパイプラインへの組み込み
  • より複雑なテストパターンへの挑戦

完璧を求めず、対話しながら、実際に動かして体感する。このサイクルを回すことで、初心者でも実践的なスキルを効率的に身につけることができるのです。 この手法をつかって、新しい手法や技術にどんどんトライしていきたいとおもいます。