Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/elixir/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
有没有办法在编译时向elixir模块添加函数?_Elixir - Fatal编程技术网

有没有办法在编译时向elixir模块添加函数?

有没有办法在编译时向elixir模块添加函数?,elixir,Elixir,我正在使用长生不老药,并听从医生的建议 博客文章 我遇到了跟踪哪个模拟函数对应的问题 哪个测试。我为api包装器的测试环境添加了一个模拟模块。当我将模拟函数添加到模拟api模块时,我发现我不记得编写了哪些函数来返回哪些测试的结果 我一直在试图找到一种在测试附近使用宏来定义模拟方法的方法。作为一项学习练习,我对这个问题也很感兴趣 以下是我对其工作的设想: defmodule SomeMockModule do end defmodule MockUtil do defmacro add_mo

我正在使用长生不老药,并听从医生的建议 博客文章

我遇到了跟踪哪个模拟函数对应的问题 哪个测试。我为api包装器的测试环境添加了一个模拟模块。当我将模拟函数添加到模拟api模块时,我发现我不记得编写了哪些函数来返回哪些测试的结果

我一直在试图找到一种在测试附近使用宏来定义模拟方法的方法。作为一项学习练习,我对这个问题也很感兴趣

以下是我对其工作的设想:

defmodule SomeMockModule do
end

defmodule MockUtil do
  defmacro add_mock module, block do
    # <THE MISSING PIECE>
  end
end

defmodule Test do
  use ExUnit.Case
  require MockUtil

  MockUtil.add_mock SomeMockModule do
    def some_func do
      "mock value"
    end
  end

  test "The mock value is returned" do
    assert SomeMockModule.some_func == "mock value"
  end
end
2) 我还尝试了以下方法:

Module.eval_quoted module, block
但是,它会抛出一个错误:

could not call eval_quoted on module {:__aliases__, [counter: 0, line: 10], [:SomeMockModule]} because it was already compiled
我想我遇到了编译问题的顺序


有没有办法在编译时将函数添加到模块中?

您是否尝试过
use
宏?你可以阅读更多关于它的内容。如果我正确理解了您的问题,那么您需要的似乎是通过
&\uuuu使用\uuuu/1
回调将功能注入模块

编辑: 我只是不确定有没有一种方法可以在编译时向模块中添加函数而不使用宏。。。我们使用以下宏:

defmacro define(name, value) do
  quote do
    def unquote(name), do: unquote(value)
  end
end

为了定义常量,您可能会收到一个
块,而不是

您是否能够提供更多关于“我遇到了跟踪哪个模拟函数对应哪个测试的问题”的信息,因为我认为您可能过于复杂了

根据您链接的文章,您将使用OTP应用程序配置来指定在哪个环境中使用哪个模块。例如,在
prod
中,您可能希望使用“真正的”HTTP客户端

# config/dev.exs
config :your_app, :module_to_mock, YourApp.Module.Sandbox

# config/test.exs
config :your_app, :module_to_mock, YourApp.Module.InMemory

# config/prod.exs
config :your_app, :module_to_mock, YourApp.Module.RealHTTP
然后,当你想使用这个模块时,你只需用

Application.get_env(:your_app, :module_to_mock)
在这个例子中,上述模块的行为将沿着

  • YourApp.Module.Sandbox
    -Hit是您与之交互的任何API的开发沙盒,如果有的话。在开发过程中简单地使用app.Module.InMemory也很常见。这取决于你用它做什么
  • YourApp.Module.InMemory
    -此模块中的所有API交互将只返回静态内联数据。例如一个结构列表,它表示实际API将发送回的内容
  • YourApp.Module.RealHTTP
    -真正的HTTP交互
  • 文章还指出,上述每个模块都将实现相同的行为(即通过
    @behavior
    实现Elixir behavior),这确保了每个模块都实现了必要的功能,因此您知道您的
    InMemory
    模块将与
    RealHTTP
    模块一样可靠地工作


    我意识到我只是重复了这篇文章的一部分,但除此之外,我并不真正理解你的问题。

    我能够理解以下几点:

    ExUnit.start
    
    defmodule MockUtil do
      defmacro __using__(_opts) do
        quote do
          defmacro __using__(_env) do
            test_module = __MODULE__
            mock_module = __CALLER__.module
                          |> Atom.to_string
                          |> String.downcase
                          |> String.split(".")
                          |> tl
            name = "#{mock_module}_functions_attr" |> String.to_atom
            quote do
              unquote(test_module).unquote(name)()
            end
          end
        end
      end
    
      defmacro add_mock_function( module, do: block ) do
        mock_module = Macro.expand_once( module, __CALLER__)
                      |> Atom.to_string
                      |> String.downcase
                      |> String.split(".")
                      |> tl
    
        test_module = __CALLER__.module
        functions_attribute = "#{mock_module}_functions_attr" |> String.downcase |> String.to_atom
    
        first_time? = Module.get_attribute test_module, functions_attribute
    
        Module.register_attribute test_module,
            functions_attribute,
            accumulate: true, persist: false
    
        Module.put_attribute test_module, functions_attribute, block
    
        if first_time? == nil do
          ast = {:@, [], [{functions_attribute, [], test_module}]}
          name = "#{mock_module}_functions_attr" |> String.to_atom
          quote do
            defmacro unquote(name)(), do: unquote(ast)
          end
        end
      end
    end
    
    defmodule Test do
      use ExUnit.Case
      use MockUtil
    
    
      MockUtil.add_mock_function Mock do
        def foo do
          "Inside foo."
        end
      end
    
      test "Register function adds foo function" do
        assert  "Inside foo." == Mock.foo
      end
    
      MockUtil.add_mock_function Mock do
        def bar do
          "Inside bar."
        end
      end
    
      test "Register function adds bar function" do
        assert  "Inside bar." == Mock.bar
      end
    
      MockUtil.add_mock_function MockAgain do
        def baz do
          "Inside bar."
        end
      end
    
      test "Register function adds baz function" do
        assert  "Inside bar." == MockAgain.baz
      end
    end
    
    defmodule Mock do
      use Test
    end
    
    defmodule MockAgain do
      use Test
    end
    

    我最初是想避免打电话“使用”,但我需要它们,以便编译顺序正确,而且我认为无论如何都没有办法将代码注入其他模块。

    我已经在我编写的其他代码中使用了use。我可能会重构我的第一次尝试,并添加use关键字以在某种程度上清理我的第一次尝试。我想知道是否有更优雅的方法在我的第一个方法中,ch直接向模块添加函数,而无需迂回地使用钩子。Yw。在与注释进行斗争后,我决定编辑我的答案更容易。诀窍是我想使用宏在另一个模块上定义函数,而不是在调用宏的位置。这就是我正在做的。当我在InMemo中编写函数时ry模块我有足够多的内存,无法跟踪哪些函数对应于哪些测试。我使用模式匹配为同一个函数调用获取不同的返回值。我后来意识到我可以使用inmemory模块中的注释进行跟踪。我刚开始想知道是否有一种方法可以实现与相同ruby mocking类似的语法。抱歉,我没有我不明白。你能给我发一个
    InMemory
    模块的例子,让我看看是什么让事情变得复杂到“迷失方向”吗,以及您围绕它进行的测试也会很好。作为工作代码,我无法共享它。我们正在模拟一个http库,我们的应用程序向多个第三方服务发出大量请求,因此我们最终得到了100行模拟响应。我们对同一个服务有很多不同的响应,因此我们无法真正将其分解为不同模块
    ExUnit.start
    
    defmodule MockUtil do
      defmacro __using__(_opts) do
        quote do
          defmacro __using__(_env) do
            test_module = __MODULE__
            mock_module = __CALLER__.module
                          |> Atom.to_string
                          |> String.downcase
                          |> String.split(".")
                          |> tl
            name = "#{mock_module}_functions_attr" |> String.to_atom
            quote do
              unquote(test_module).unquote(name)()
            end
          end
        end
      end
    
      defmacro add_mock_function( module, do: block ) do
        mock_module = Macro.expand_once( module, __CALLER__)
                      |> Atom.to_string
                      |> String.downcase
                      |> String.split(".")
                      |> tl
    
        test_module = __CALLER__.module
        functions_attribute = "#{mock_module}_functions_attr" |> String.downcase |> String.to_atom
    
        first_time? = Module.get_attribute test_module, functions_attribute
    
        Module.register_attribute test_module,
            functions_attribute,
            accumulate: true, persist: false
    
        Module.put_attribute test_module, functions_attribute, block
    
        if first_time? == nil do
          ast = {:@, [], [{functions_attribute, [], test_module}]}
          name = "#{mock_module}_functions_attr" |> String.to_atom
          quote do
            defmacro unquote(name)(), do: unquote(ast)
          end
        end
      end
    end
    
    defmodule Test do
      use ExUnit.Case
      use MockUtil
    
    
      MockUtil.add_mock_function Mock do
        def foo do
          "Inside foo."
        end
      end
    
      test "Register function adds foo function" do
        assert  "Inside foo." == Mock.foo
      end
    
      MockUtil.add_mock_function Mock do
        def bar do
          "Inside bar."
        end
      end
    
      test "Register function adds bar function" do
        assert  "Inside bar." == Mock.bar
      end
    
      MockUtil.add_mock_function MockAgain do
        def baz do
          "Inside bar."
        end
      end
    
      test "Register function adds baz function" do
        assert  "Inside bar." == MockAgain.baz
      end
    end
    
    defmodule Mock do
      use Test
    end
    
    defmodule MockAgain do
      use Test
    end