0

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

    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

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

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

但是,正如我在问题开头所说的那样,我正在处理使用 GitHub Actions 和 Coveralls 以及依赖项中的 ExCoveralls 自动执行的覆盖率测试。在这个测试中,我收到了两个raise/1语句的警告。尽管我理解这意味着什么可能是错误的,但我理解工作服报告了这种“遗漏的线条”或未覆盖的线条,因为我没有进行测试来涵盖这种情况。因此,我开始研究如何编写一个涵盖这两种情况的测试。

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

  1. 服务器关闭与用户部分缺少互联网连接不同。
  2. 我试图应用这种“上下”机制,但没有完成。我不打算分享它,因为我认为这不是所描述的第一个问题的最终解决方案。

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

编辑. 我评论我尝试的bypass是安装依赖项,编写一个设置,bypass.open/0然后编写一个类似下一个的测试,在其中我尝试使用以下命令断言捕获输出capture_io/1

  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

但是,正如我所想,它不包括可能的缺乏互联网的情况,只是检查服务器何时关闭的可能性。

4

1 回答 1

2

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

我不知道你为什么排除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)

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

于 2020-05-15T03:52:37.453 回答