一般来说,我对 elixir 和函数式编程还比较陌生,我正在努力对由其他函数组成的函数进行正确的单元测试。一般的问题是:当我有一个使用其他函数 g 的函数 f 时,h... 在内部,我应该采用哪种方法来测试整体?
来自 OOP 世界的第一个方法是注入(inject) f 所依赖的函数。我可以对 g、h... 进行单元测试并将所有这些作为参数注入(inject)到 < em>f。然后,f 的单元测试将确保它按预期调用注入(inject)的函数。不过,这感觉像是过度拟合,而且作为一种整体上很麻烦的方法,有悖于函数式思维,函数组合应该是一种廉价的做法,您不应该担心自己会在整个代码库中传递所有这些参数。
我还可以对 g、h... 以及 f 将其中的每一个都视为黑盒子,这感觉是应该做的事情,但是 f 的复杂性测试急剧增加。进行可扩展的简单测试是单元测试的主要目的之一。
为了使论点更具体,我将举一个函数示例,该函数在内部组合其他函数,但我不知道如何正确进行单元测试。这尤其是用于处理以 RESTful 方式创建资源的插件的代码。请注意,一些“依赖项”是纯函数(例如 validate_account_admin)但其他不是(Providers.create):
def call(conn, _opts) do
account_uuid = conn.assigns.current_user.account["uuid"]
with {:ok, conn} <- Http.Authorization.validate_account_admin(conn),
{:ok, form_data} <- Http.coerce_form_data(conn, FormData),
{:ok, provider} <- Providers.create(FormData.to_provider(form_data), account_uuid: account_uuid) do
Http.respond_create(conn, Http.provider_path(provider))
else
{:error, reason, messages} -> Http.handle_error(conn, reason, messages)
end
end
谢谢!
最佳答案
也许这将是一个非常主观的答案,因为对于这样的问题可能没有完美和最终的答案。
就在其他公共(public)函数中使用公共(public)函数而言,你对我的假设是错误的。你根本不应该在业务逻辑领域这样做,因为它们应该是分开的,而且你唯一可以这样做的地方 - 事实上 - 你必须在 Controller 中,但是你用集成测试而不是单元来测试 Controller 测试,因此您在此类测试中所关心的只是正确有效的响应。
我喜欢 Erlang 通过使用 export
子句来声明哪些函数应该公开的明确方法。在 Elixir 中,你也应该遵循这种方法,无论什么应该隐藏在模块中,都应该分别用 defp
和 defmacrop
声明私有(private)函数和私有(private)宏。
你的单元测试应该遵循黑盒规则——你关心基于输入的输出。就这样。测试是愚蠢的,根本不知道被测试的功能是什么样子以及它包含什么。
在您的示例中,您在插件 call
函数中使用了一些函数,我很确定该插件的功能超出了应有的范围 - 请记住单一责任原则
。这使得这个功能几乎不可能在没有模拟的情况下进行测试......我会把这个插件重写成 3 或 4 个四个独立的插件,因为 with
子句是多余的 - 插件检查前一个插件的结果以继续 -它是 case
中的 case
,正是 with
所做的。
考虑到您有新的插件,您可以在插件内使用一些额外的函数,除了 call
和 init
,它们完成定义为私有(private)函数的实际工作,这个操作可能会有所帮助您组织代码并避免在使用和责任方面创建链式模块。
然后,单元测试会容易得多,因为您将测试隔离的插头。
假设你有这样的插件:
plug MyPlug
你会改写成:
plug :validate_is_admin
plug :coerce_form_data
plug :create_from_form_data
也许它被简化了,但我希望你明白我在这里的意思。
TL; DR: 将函数拆分成更小的函数并单独测试它们。在私有(private)函数中隐藏内部计算并仅测试公共(public) API。
关于unit-testing - 单元测试 elixir 功能正常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43137698/