はじめに
七尾百合子さん、お誕生日 130日目 おめでとうございます! nikkieです。
genai-processorsのResearchAgentをモックしてテストを書いた備忘録です。
目次
書きたいテストは
llm-deep-researchを始めました。
2025年7月23日(水)のリリース - nikkie-ftnextの日記
ハマりながらテストを書く中で理解したことがあります。
simonw/llmのプラグインとして実装しているので、以下のexecute()メソッドをテストします。
https://github.com/ftnext/llm-deep-research/blob/0.0.1/llm_deep_research.py#L46
class GenAIProcessorsAsyncResearch(llm.AsyncKeyModel): async def execute(self, prompt, stream, response, conversation, key): ... async for content_part in ResearchAgent(api_key=key)(input_stream): yield content_part.text
- テスト対象の
GenAIProcessorsAsyncResearch.execute()は非同期ジェネレータ1 - テスト時にGemini APIにリクエストが送られないよう
ResearchAgentをモックしたい
Pythonでコルーチン(async def)のテスト
pytest-asyncioを使っています。
https://pypi.org/project/pytest-asyncio/
@pytest.mark.asyncio async def test_GenAIProcessorsAsyncResearch_execute(ResearchAgent): # await(など)が書ける
async forに書けるようにモックを設定する
ResearchAgentインスタンスの__call__()の返り値を非同期イテラブルにするのが最初見えていませんでした。
https://github.com/ftnext/llm-deep-research/blob/8ead3c51971fee8dfd29d56e70c4449bd36649d3/tests/test_deep_research.py#L18-L47
@pytest.mark.asyncio @patch("llm_deep_research.ResearchAgent") # クラスをモック async def test_GenAIProcessorsAsyncResearch_execute(ResearchAgent): researcher = ResearchAgent.return_value # ResearchAgentインスタンス async def generator(self): yield ProcessorPart("doing...", substream_name="status") yield ProcessorPart("DONE!", substream_name="") researcher.side_effect = generator # __call__() をモック # ... async for part in sut.execute( prompt, stream=False, response=MagicMock(), conversation=MagicMock(), key="test-key", ): # ...
side_effectで非同期ジェネレータをモックの__call__()として設定しています。
呼び出されたときに返す値は非同期ジェネレータイテレータ3となり、これは非同期イテラブルを満たします(=async forに書ける)。
思い返していたのはこちらの過去記事。
ResearchAgentの__call__()は、親のProcessorクラスでasync def __call__()として定義されていました。
https://github.com/google-gemini/genai-processors/blob/v1.0.5/genai_processors/processor.py#L109
実装を見るとyieldが使われていて、非同期ジェネレータになっています。
モックの仕方も同じように非同期ジェネレータでよいのではと考えています。
終わりに
非同期ジェネレータをモックしての学びを記しました。
- テストはpytest-asyncioを使って、コルーチンで書く
- モックの持つメソッドを、テストのスコープで非同期ジェネレータに差し替え
- 非同期ジェネレータイテレータは非同期イテラブルなので、
async forに書ける
- 非同期ジェネレータイテレータは非同期イテラブルなので、
一息にテストを書き上げようとすると非同期イテラブルまわりの概念に慣れていなかったので大変でしたが、一歩一歩進むことで時間はかかりましたが理解を深めて書き上げられました!