【问题标题】:Unit testing elixir functions properly单元测试长生不老药功能正常
【发布时间】:2017-04-01 03:40:34
【问题描述】:

总的来说,我对 elixir 和函数式编程还很陌生,我正在努力正确地对由其他函数组成的函数进行单元测试。一般问题是:当我有一个函数 f 使用其他函数 g 时,h...在内部,我应该采用哪种方法来测试整体?

来自 OOP 世界,想到的第一个方法涉及注入 f 依赖的函数。我可以对 gh... 进行单元测试并将所有这些作为参数注入 f。然后,f 的单元测试将确保它按预期调用注入的函数。不过,这感觉像是过拟合,并且作为一种总体上笨重的方法,它违背了函数式思维方式,函数组合应该是一件便宜的事情,你不应该担心自己在整个代码库中传递所有这些参数。

我还可以对 gh...以及 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

谢谢!

【问题讨论】:

    标签: unit-testing testing functional-programming elixir


    【解决方案1】:

    也许这将是一个相当主观的答案,因为这样的问题可能没有完美和终极的答案。

    就在其他公共函数中使用公共函数而言,您对我的假设是错误的。您根本不应该在业务逻辑区域中这样做,因为它们应该分开并且您可以这样做的唯一地方 - 事实上 - 您必须在控制器中,但是您使用集成测试而不是单元测试控制器测试,因此您在此类测试中所关心的只是正确且有效的响应。

    我喜欢 Erlang 使用 export 子句声明哪些函数应该公开的明确方法。在 Elixir 中,您也应该遵循这种方法,并且应该在模块中隐藏任何内容,应分别使用 defpdefmacrop 声明私有函数和私有宏。

    你的单元测试应该遵循黑盒规则——你关心基于输入的输出。就这样。测试是愚蠢的,根本不知道被测函数的外观和包含的内容。

    在您的示例中,您在 Plug callfunction 中使用了一些函数,我很确定这个插件的功能超出了应有的范围 - 请记住 single responsible principle。这使得这个功能几乎不可能在没有模拟的情况下进行测试......我会将此插件重写为 3 或 4 个四个独立的插件,因为 with 子句是多余的 - 插件检查前一个插件的输出以继续 - 它是 case 在里面case,就像 with 所做的那样。

    考虑到您有新的插件,您可以在插件中使用一些额外的函数,除了 callinit 之外,它们执行定义为私有函数的实际工作,此操作可能会帮助您组织代码并避免在其中创建链式模块使用条款和责任。

    然后,单元测试会容易得多,因为您将测试孤立的插头。

    假设你有这样的插件:

    plug MyPlug
    

    你会改写成:

    plug :validate_is_admin
    plug :coerce_form_data
    plug :create_from_form_data
    

    也许它被简化了,但我希望你明白我的意思。

    TL; DR: 将函数拆分为更小的函数并单独测试它们。在私有函数中隐藏内部计算并仅测试公共 API。

    【讨论】:

    • 您建议将程序 P 建模为函数链 P = fg ... z 其中所有这些内部函数都可以进行单元测试,因为它们没有依赖关系,只有 P 必须作为集成进行测试。但是,如果在该内部函数列表中您有重复的模式,例如 f 会发生什么。 gfg?在这种情况下,您希望将模式提取到它自己的函数 h = f 中。 g 不能进行单元测试。在插件示例中,如果多个端点必须 :validate_is_admin 和 :coerce_form_data 怎么办?
    • 我可以对这些插件进行单元测试,涵盖多种场景:)
    • 因为 h 的复杂性大于它的一个组件,黑盒测试这些功能无法扩展。如果这种功能不是很常见,这是可以接受的,但我的假设是它们是。总的来说,虽然我认为你的方法是朝着好的方向迈出的一步,我肯定会使用你的插件技巧。谢谢@PatNowak!
    猜你喜欢
    • 1970-01-01
    • 2017-10-21
    • 1970-01-01
    • 1970-01-01
    • 2016-02-01
    • 2010-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多