在 Elixir 中创建宏时,如何使 do:block 和宏上下文具有正确的值和变量?

问题描述 投票:0回答:1

我试图避免冗余测试(这意味着测试始终以相同的方式进行,但参数略有不同)。例如,当我想测试对控制器的调用是否正常时,我总是会遇到这样的情况:

setup do
  count = 3
end

test "responds with right schema", %{conn: conn, swagger_schema: schema} do
    conn
    |> create_authenticated_conn()
    |> get(~p"/something",  %{a: 1})
    |> validate_resp_schema(schema, "Schema")
    |> json_response(200)
    |> assert_length(count)
end

改变的事情是方法调用(get、post…)、参数、要评估的模式,以及可选的如果我想做出更多断言(如检查结果数组的长度)。

因此,我想到创建一个宏来简化所有这些工作,参数化需要参数化的值,并允许 do: block 元素添加额外的检查。我最终得到的是这个:

defmacro test_ok(schema_name, method, url, params \\ nil, do: block) do
  quote do
    test "responds with right schema", %{conn: conn, swagger_schema: schema} do
      response =
        conn
        |> create_authenticated_conn()
        |> unquote(method)(unquote(url), unquote(params))
        |> validate_resp_schema(schema, unquote(schema_name))
        |> json_response(200)

      evaluate_response(response, block)
    end

    defp evaluate_response(var!(response), block) do
      unquote(block)
    end
  end
end

如果我忽略响应部分和块部分(evaluate_response 方法),则一切正常。一旦我想将块部分添加到宏并将响应传递到主体内部,我就会遇到两个问题。首先,我们举一个代码示例:

test_ok("Schema", :get, ~p"/something") do
  assert_length(response, count)
end

那么,第一个问题是块代码内的响应无法被识别。另一个是 count 也不可用,尽管以正常测试宏的方式可以完美识别。

我的错误是什么?

elixir ex-unit
1个回答
0
投票
  1. Kernel.var!/2
    不会神奇地使参数全局可见。这只会让它不卫生。这意味着如果您在
    quote/2
    块内为其分配一个值,则调用者可以看到它。在您的情况下,您明确地将
    response
    作为第一个参数传递给对
    evaluate_response/2
    的调用,因此
    var!/2
    是无操作。
  2. 在对
    evaluate_response/2
    的调用中,您传递一个未定义的局部
    block
    变量作为第二个参数,该参数在实现中被忽略。块外部的整个上下文在内部可用,这正是宏观卫生的本质。在
    evaluate_response/2
    的正文中,您直接取消引用
    block
    ,它已被传递给
    test_ok/5
    的参数。
  3. 我不确定为什么
    count
    应该在任何地方可用,
    setup/2
    块应该返回映射/关键字列表,而不是定义局部变量。
  4. 您不需要用宏包装整个
    test/2
    调用(相信我,
    ExUnit.Case.test/3
    在引擎盖下做了很多黑暗魔法,您将失去包装它。)相反,包装
     response
    评估为
    quote do: var!(response) = ...
    ,然后使用此响应变量调用其他内容(由于
    var!
    ,这会从宏内部泄漏。
  5. [奖励] 每当您需要定义宏时,请使用存根调用来完成,特别是当您不舒服地理解调用上下文和被调用上下文之间的区别时。
© www.soinside.com 2019 - 2024. All rights reserved.