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