有没有办法在 Elixir 测试中模拟缺少互联网连接?

Is there a way to simulate the lack of internet connection in an Elixir test?

我正在处理用 Elixir 开发的命令行界面应用程序的覆盖率测试。该应用程序是 tldr-pages 的客户端,其功能包含在使用 escript 构建的脚本中。为了执行这些操作,我在 HTTPoison.get/1 函数上使用了 case 结构,我在其中引入了格式化的 url。在这个 case 中,我比较了对不同类型值的响应,例如如果页面存在,它会显示信息;如果不是,它会向用户报告,然后在另一个 case 中继续评估其他可能性。最后,第一个 case 以两种模式结束以匹配错误,一种是由于缺少互联网连接,另一种是由于意外错误。描述的结构是下一个:

    case HTTPoison.get(process_url(os, term)) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        IO.puts(body)

      {:ok, %HTTPoison.Response{status_code: 404}} ->
        IO.puts(
          "Term \"#{term}\" not found on \"#{os}\" pages\nExTldr is looking on \"common\" pages."
        )

        case HTTPoison.get(process_url("common", term)) do
          {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
            IO.puts(body)

          {:ok, %HTTPoison.Response{status_code: 404}} ->
            IO.puts("Term not found on \"common\" pages.")
        end

      {:error, %HTTPoison.Error{reason: reason}} when reason == :nxdomain ->
        raise NoInternetConnectionError

      {:error, %HTTPoison.Error{reason: reason}} when reason != :nxdomain ->
        raise UnexpectedError, reason
    end

NoInternetConnectionErrorUnexpectedError 是在另一个文件中定义的异常。最后的两种模式显然都很好,至少第一种是这样:

      {:error, %HTTPoison.Error{reason: reason}} when reason == :nxdomain ->
        raise NoInternetConnectionError

但是,正如我在问题开头所说的那样,我正在处理使用 GitHub Actions 和 Coveralls 在依赖项中使用 ExCoveralls 自动执行的覆盖率测试。在此测试中,我收到了针对两个 raise/1 语句的警告。虽然我可能错误地理解了这意味着什么,但我理解 Coveralls 报告了 "missed lines" 或未覆盖的行,因为我没有执行涵盖这种情况的测试。因此,我开始研究如何编写一个测试来涵盖这两种情况。

最重要的问题是如何在测试中模拟缺少互联网连接来解决这个问题。我虽然打算开发一个模拟,但我没有在 Elixir 的一些模拟包中找到对这种情况有用的东西,比如 Mox. Then I found bypass,一个非常有趣的包,我认为它可能有用,因为它有 down/1up/1 关闭和启动 TCP 套接字,因此可以测试 HTTP 服务器关闭时发生的情况。但是我有两个问题:

  1. 服务器宕机与用户部分没有互联网连接是不一样的。
  2. 我尝试应用这个"down and up"机制,但没有成功。我不打算分享它,因为我认为这不是第一个问题描述的最终解决方案。

我不是假装用解决这个问题的代码来回答,我只是想了解这个测试应该如何工作以及开发它应该遵循的逻辑。我什至正在研究 Erlang 文档,因为 Erlang 可能提供本地函数来解决它(例如,我现在正在阅读 Erlang 的 Common Test Reference Manual,因为那里可能有一些有用的东西)。

编辑。我评论说我尝试使用 bypass 是安装依赖项,使用 bypass.open/0 编写设置,然后像下一个一样编写测试,其中我尝试使用 [=26= 断言捕获输出]:

  test "lack of internet connection", %{bypass: bypass} do
    Bypass.down(bypass)

    execute_main = fn ->
      ExTldr.main([])
    end

    assert capture_io(execute_main) =~ "There is not internet connection"
  end

然而,正如我所想,它不包括可能没有互联网的情况,只是检查服务器何时宕机的可能性。

旁注:我个人一直反对成为本应有助于开发的工具的奴隶。覆盖率是一个不错的指标,但不应将建议视为必须的。不管怎样

我不确定你为什么排除 Mox。经验法则是:除非绝对不可避免,否则测试不应涉及跨界调用。通过互联网进行的测试仍然不稳定:报道不会告诉你这一点,我会。如果测试环境根本无法永久访问互联网怎么办?临时连接问题?遥控器坏了?

这正是 Mox 诞生的原因。而且,幸运的是,HTTPoison 完全可以将 Mox 用作模拟库,因为它声明了主要操作模块 HTTPoison.Base.

的行为

您只需要让您的实际 HTTP 客户端成为一个注入的依赖项。大致是这样的:


@http_client Application.get_env(:my_app, :http_client, HTTPoison)

...

case @http_client.get(process_url(os, term)) do
   ...
end

config/test.exs 中,您可以指定自己的 :http_client,瞧瞧——漂亮的模拟测试环境就是您的了。


或者,您可以直接声明模拟:

Mox.defmock(MyApp.HC, for: HTTPoison.Base)

我也很擅长调用第 3 方的应用程序级别的界限。也就是说,您可以为所需的外部 HTTP 调用定义自己的行为,并定义实现此行为的包装器。这样模拟会更容易,并且您将受益于轻松更改真实客户端HTTPoison 远不是当今最好的客户端(它几乎不支持 HTTP2 等),明天您可能会决定切换到 Mint。如果所有代码都位于包装器中,那么完成起来会容易得多。