在 Elixir 中使用 Ecto 在 repo class 中测试覆盖率

Test coverage in repo class using Ecto in Elixir

我在 Elixir 中有一个使用 Ecto 的非常简单的应用程序,用于 运行 在 Postgres 上的 table 中的 SELECT 查询。 table 姓名是“人”,有两列:“姓名”和“年龄”。

这是应用程序:

/ecto_app
 |-application.ex
 |-repo.ex

application.ex:

defmodule EctoApp.Application do

  use Application

  require Logger

  @impl true
  def start(_type, _args) do
    children = [
      EctoApp.Repo
    ]

    opts = [strategy: :one_for_one, name: EctoApp.Supervisor]
    Logger.debug("Starting Ecto App")
    Supervisor.start_link(children, opts)
  end
end

repo.ex:

defmodule EctoApp.Repo do
  use Ecto.Repo,
    otp_app: :ecto_app,
    adapter: Ecto.Adapters.Postgres

  import Ecto.Query

  require Logger


  def get_people() do
    query = from p in "person",
            select: [p.name, p.age]
    result= EctoApp.Repo.all(query)
    Logger.debug(result)
  end

end

我正在使用这 2 个库:

[
  {:ecto_sql, "~> 3.0"},
  {:postgrex, ">= 0.0.0"}
]

该应用程序运行良好,但问题是我想为回购进行单元测试,但我不知道如何模拟 Postgres 连接,因为我不想启动 Postgres 实例运行 测试。

有没有人知道如何 Mock 或我可以使用哪个库来“模拟”与 Postgres 的连接,这样我就可以在 repo 上进行测试 class?

谢谢!

我建议 运行 在容器中使用 PostgreSQL,然后使用 Ecto Sandbox。 https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.Sandbox.html

您不需要像模拟其他功能那样模拟 PostGres 连接。这是因为 PostGres 可以将操作包装在一个可撤销的事务中。要点是您的测试使用 real 连接并对 real 数据库发出 real 请求,但是该数据库专用于通过特殊适配器进行测试和发出查询。这是迄今为止实现 database-backed 应用程序测试覆盖率的最常见方法;尝试模拟数据库连接会导致脆弱的测试充满误报,因此这种做法并不常见,尤其是当针对真实数据库发出所有请求如此简单时。

以下是在您的代码中促进这一点的重要部分:

  1. 在您的应用配置中指定沙盒适配器和专用测试数据库,例如通过配置文件:
# config/dev.exs
config :my_app, MyApp.Repo,
  pool: DBConnection.ConnectionPool,
  database: "something_dev",
  # ... etc...

# config/test.exs
config :my_app, MyApp.Repo,
  pool: Ecto.Adapters.SQL.Sandbox,
  database: "something_test",
  # ... etc...
  1. 您的测试必须在 运行ning 查询之前使用适配器检查 Repo。这就是将操作包装在事务中的内容,因此您可以安全地 re-run 测试而无需过多清理。一个很好的地方是在 setup callback 内部,在模块中的每个测试之前 运行s:
defmodule MyApp.SomeTest do
  use ExUnit.Case # you may require async: false

  alias Ecto.Adapters.SQL.Sandbox
  alias MyApp.Repo

  setup do
    Sandbox.checkout(Repo)
  end

  describe "get_people/0" do
    test "select" do
      # insert fake records here
      assert [] = Repo.get_people()
    end
  end
end

还有一些建议:

  1. 您编写的 get_people/0 函数不会 return result。请记住,Elixir 依赖于隐式 returns,因此它 returns 是最后一个操作的结果。在你的情况下,returned 将是 debug 语句的结果,它只是一个 :ok 而不是你的 result期待。

  2. 不要忘记您可能需要 运行 迁移到您的测试数据库——这不是自动发生的事情。您可以在 mix.exs 中创建一个别名以在测试之前执行 mix ecto.migrate,或者您可以只记住 运行 MIX_ENV=test mix ecto.drop 等针对测试数据库。

  3. 您经常会发现自动构建数据“夹具”(即具有正确形状的假记录)很有帮助。 ex_machina 包是执行此操作的便捷方法,但无论您使用包还是滚动自己的固定装置,您的测试都需要使用您期望的任何测试记录来准备数据库,作为“安排”的一部分标准“arrange-act-assert”测试方法的步骤。

如果你的情况确实需要你模拟数据库连接,那么我会推荐一个依赖于一种“依赖注入”的简单模式,例如您可以在其中将 Repo 模块作为函数参数或从配置中提取的内容覆盖,例如

def get_people(repo \ Repo)
  query = from p in "person",
    select: [p.name, p.age]
  repo.all(query)
end

comes with its own testing suite and it has dedicated documentation on How to Test with Ecto.

Ecto.Adapters.SQL.Sandbox 用于测试 但是 没有办法避免启动实际的数据库实例,主要是因为使用 mocked 没有什么意义用于测试的连接。