如何为Elixir GenServer Worker编写地道的集成测试?
为Elixir GenServer Worker编写集成测试
针对你基于GenServer实现的CotacoesFetcher和CotacaoDownloader两个Worker,结合已Mock的外部依赖,以下是编写集成测试的具体方案:
核心思路
集成测试的重点是验证Worker之间的协作逻辑、消息传递流程以及生命周期行为,而非孤立测试单个函数。测试中会启动真实的Worker进程,用Mock替代API、数据库等外部依赖,同时验证Worker间的交互是否符合预期。
一、CotacoesFetcher集成测试
测试覆盖以下场景:触发抓取流程后,是否正确调用依赖、插入数据并通知CotacaoDownloader;无新数据时的日志输出;以及调度下一次执行的逻辑。
测试代码示例
defmodule CotacoesETL.Workers.Pesagro.CotacoesFetcherIntegrationTest do use ExUnit.Case, async: false alias CotacoesETL.Workers.Pesagro.{CotacoesFetcher, CotacaoDownloader} alias Cotacoes.Handlers.CotacaoHandler alias CotacoesETL.Integrations.PesagroAPI setup do # 启动协作的两个Worker进程 {:ok, fetcher_pid} = CotacoesFetcher.start_link(nil) {:ok, downloader_pid} = CotacaoDownloader.start_link(nil) # 绑定Mock实现 Mox.stub_with(PesagroAPIMock, PesagroAPI) Mox.stub_with(CotacaoHandlerMock, CotacaoHandler) # 测试结束后清理进程 on_exit(fn -> GenServer.stop(fetcher_pid) GenServer.stop(downloader_pid) end) {:ok, fetcher_pid: fetcher_pid, downloader_pid: downloader_pid} end test "触发抓取后完成全流程并通知Downloader", %{downloader_pid: downloader_pid} do # 预设Mock返回值 test_doc = "<html><a href='test-boletin'>Boletim Teste</a></html>" test_link = "test-boletin" test_cotacao = %{id: "cot-test-01", link: test_link, baixada?: false} # 验证Mock调用 Mox.expect(PesagroAPIMock, :fetch_document!, fn -> test_doc end) Mox.expect(PesagroAPIMock, :fetch_all_links, fn ^test_doc -> [test_link] end) Mox.expect(CotacaoHandlerMock, :insert_cotacao_pesagro, fn ^test_link, _today -> {:ok, test_cotacao} end) Mox.expect(CotacaoHandlerMock, :get_cotacao_file_base_name, fn ^test_cotacao -> "boletim-teste" end) # 触发抓取并监听Downloader消息 CotacoesFetcher.trigger_fetching() assert_receive {:cast, CotacaoDownloader, {:download, ^test_cotacao}}, 1000 end test "无新报价时输出正确日志" do # 预设无链接返回 Mox.expect(PesagroAPIMock, :fetch_document!, fn -> "<html></html>" end) Mox.expect(PesagroAPIMock, :fetch_all_links, fn _ -> [] end) # 捕获并验证日志 assert capture_log(fn -> CotacoesFetcher.trigger_fetching() Process.sleep(500) end) =~ "Nenhuma nova cotação encontrada em Pesagro" end end
二、CotacaoDownloader集成测试
测试覆盖以下场景:处理下载消息时根据文件类型通知对应Worker;收到PDF转换完成消息后通知Ingester;处理ZIP解压完成后的批量转换请求。
测试代码示例
defmodule CotacoesETL.Workers.Pesagro.CotacaoDownloaderIntegrationTest do use ExUnit.Case, async: false alias CotacoesETL.Workers.Pesagro.{CotacaoDownloader, CotacaoIngester} alias CotacoesETL.Workers.{PDFConverter, ZIPExtractor} alias Cotacoes.Models.Cotacao alias CotacoesETL.Handlers.PesagroHandler alias CotacoesETL.Logic.PesagroLogic alias CotacoesETL.Events.{PDFConvertedEvent, IngestCotacaoEvent} setup do {:ok, downloader_pid} = CotacaoDownloader.start_link(nil) # 绑定Mock实现 Mox.stub_with(PesagroHandlerMock, PesagroHandler) Mox.stub_with(PesagroLogicMock, PesagroLogic) on_exit(fn -> GenServer.stop(downloader_pid) end) {:ok, downloader_pid: downloader_pid} end test "处理PDF类型报价并通知PDFConverter" do test_cotacao = %Cotacao{id: "cot-pdf-01", link: "pdf-link", baixada?: false, tipo: :pdf} test_file = "/tmp/test.pdf" Mox.expect(PesagroHandlerMock, :download_cotacao_from_pesagro!, fn _path, ^test_cotacao -> {test_cotacao, test_file} end) GenServer.cast(CotacaoDownloader, {:download, test_cotacao}) assert_receive {:cast, PDFConverter, {:convert, event}}, 1000 assert event.cotacao.id == test_cotacao.id assert event.pdf_path == test_file end test "处理ZIP类型报价并通知ZIPExtractor" do test_cotacao = %Cotacao{id: "cot-zip-01", link: "zip-link", baixada?: false, tipo: :zip} test_file = "/tmp/test.zip" Mox.expect(PesagroHandlerMock, :download_cotacao_from_pesagro!, fn _path, ^test_cotacao -> {test_cotacao, test_file} end) GenServer.cast(CotacaoDownloader, {:download, test_cotacao}) assert_receive {:cast, ZIPExtractor, {:extract, event}}, 1000 assert event.zip_path == test_file assert event.destination_path == Path.join("/tmp/peapescarte/cotacoes/pesagro/", test_cotacao.id) end test "收到PDF转换完成消息后通知Ingester" do test_cotacao = %Cotacao{id: "cot-test-01"} converted_file = "/tmp/converted.txt" pdf_event = %PDFConvertedEvent{cotacao: test_cotacao, file_path: converted_file} Process.send(CotacaoDownloader, {:pdf_converted, pdf_event}, []) assert_receive {:send, CotacaoIngester, {:ingest, %IngestCotacaoEvent{} = ingest_event}}, 1000 assert ingest_event.cotacao.id == test_cotacao.id assert ingest_event.file_path == converted_file end end
关键注意事项
- 进程管理:测试中启动真实Worker进程,测试结束后务必停止进程避免资源泄漏。
- Mock验证:使用Mox等工具预设Mock返回值,并验证依赖的调用参数、次数是否符合预期。
- 消息监听:通过
assert_receive验证Worker之间的消息传递是否正确。 - 日志验证:用
capture_log捕获日志,验证异常或特殊场景下的输出。 - 异步控制:必要时用
Process.sleep等待异步流程完成,避免测试超时或断言失败。
内容的提问来源于stack exchange,提问作者Zoey
相关产品推荐
相关产品推荐

