有没有办法在Elixir的ExUnit中测试模块中的私有函数?

64

defp 定义的函数没有被导出,因此我无法在模块以外的地方执行它们。


3
我在想是否有可能编写一个宏,在 DEV 和 PROD Mix 环境中扩展为 defp,而在 TEST 环境中扩展为 def - TalkLittle
@TalkLittle 是的,这是可能的。请看下面我的回答。 - acj
4个回答

106

不,无法通过ExUnit测试私有函数。

个人而言,我避免测试私有函数,因为通常会测试实现而不是行为,这些测试在需要更改代码时很容易失败。相反,我通过公共函数测试期望的行为,将其分解成小而一致的块。


32
不论使用何种语言或测试库,这都是有益的建议。 - Wil Moore III
4
我倾向于尊重地不同意:有两种测试方法。在一个行为上。 - lab419
20
抱歉,我在编辑时被打断了。总体上同意,但认为某些私有函数可以通过测试来实现重构的目的。 - lab419
8
@tompave,我想在某些情况下你是对的,但我希望为我的库用户提供对该实用模块的受限访问,但这似乎有点复杂。尽管如此,我仍然觉得重构用例被低估了。 - lab419
3
你可以在一个模块中添加 @moduledoc false 来将其和其中的函数排除在文档之外。这些函数仍然可以被调用。我有些犹豫,不知道要按你的喜好还是直接将这些函数公开。从某种意义上说,它们并不重要,因此希望测试这些函数但不将其作为应用程序或库的公共 API 的一部分公开似乎是合理的。也许,实际上,说明一个模块或函数不是公共 API 的一部分,并可能会被删除或进行其他“破坏性更改”,这样就足够了。 - Kenny Evitt
显示剩余5条评论

26
在您的模块定义中,您可以使用 @compile 指令将您的私有函数仅导出到测试环境中。
defmodule Foo do
  @compile if Mix.env == :test, do: :export_all

  # This will be exported for tests
  defp bar() do
  ... code ...
  end
end

2
更新:这将在 Elixir 1.5 中发出警告,因此可能不是最佳方法。 - Michael Bishop

9

可以使用宏来根据环境改变函数的可见性:

defmacro defp_testable(head, body \\ nil) do
  if Mix.env == :test do
    quote do
      def unquote(head) do
        unquote(body[:do])
      end
    end
  else
    quote do
      defp unquote(head) do
        unquote(body[:do])
      end
    end
  end
end

然后,您可以按照以下方式向测试中暴露功能:
defp_testable myfunc do
  ...
end

我建议谨慎使用此功能,原因详见José的回答。它不是测试模块外部行为的替代品。然而,在某些场景下是有价值的。

(源代码)


@TalkLittle 确实如此。这个宏仅影响可见性,不会改变参数和主体。 - acj
这并不是一个很好的想法,因为当私有函数被错误调用时,这将无法检测到潜在的错误。 - lab419
@lab419 这是一个公正的批评。这种技术不能替代其他测试方法,以便检测私有函数的不当使用,并且应该小心使用。正如您和其他人所指出的那样,这对于重构和集成测试场景可能会有用。 - acj
@acj 抱歉如果我表达不清楚,如果您根据混合环境更改函数的隐私性,则所有测试都是针对测试混合环境进行的。您不再测试开发或生产环境,因为它们不包含相同的代码。更糟糕的是,您将会有错误的负面结果,因为您在测试环境中具有公共函数,在其他地方则是私有的。希望这能帮到您。 - lab419
@lab419 我理解并同意你的观点。:) 任何选择使用这个的人都应该小心,并且应该有一个额外的机制来测试开发/生产环境。 - acj

3

私有函数不能从其模块外部调用,即使在使用ExUnit进行测试的情况下也是如此。

其他人建议采用复杂的方式来暴露私有函数以供测试,因此我建议采用以下两种方法,它们似乎更简单:

1)将要测试的函数公开,但标记为@doc false,这意味着它不是您的模块的公共API的一部分。

2)或者,如果你想测试的函数是defp foo,那么在同一个模块中创建一个def test_foo,并在测试期间对你想要变化的参数进行适应,以符合foo的要求,并最终调用foo。你也可以像这样仅在测试期间生成你的特殊测试函数。

  if Mix.env == :test do
    def test_foo ...
  end

任何一种替代方案都更简单,不需要在您的项目中添加额外的依赖项。
当然,正如其他人所提到的,通常规则是避免测试私有函数,但有时测试它们是简化测试并提高测试覆盖率的方法。
祝编码愉快!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接