我有一个 Elixir 程序我想测试它通过IO.gets
多次从用户那里获得输入。我将如何在测试中伪造这个输入?
注意:我想为每个返回不同的值IO.gets
我有一个 Elixir 程序我想测试它通过IO.gets
多次从用户那里获得输入。我将如何在测试中伪造这个输入?
注意:我想为每个返回不同的值IO.gets
首选的方法是将代码拆分为纯代码(无副作用)和不纯代码(io 是否)。因此,如果您的代码如下所示:
IO.gets
...
...
...
IO.gets
...
...
尝试将 IO.gets 之间的部分提取到可以单独测试的函数中IO.gets
:
def fun_to_test do
input1 = IO.gets
fun1(input1)
input2 = IO.gets
fun2(input2)
end
然后您可以单独测试这些功能。这并不总是最好的做法,特别是如果不纯的部分在内心深处if
,case
或cond
陈述。
另一种方法是将 传递IO
为显式依赖项:
def fun_to_test(io \\ IO) do
io.gets
...
...
...
io.gets
...
...
end
这样,您可以在生产代码中使用它而无需任何修改,但在您的测试中,您可以传递一些不同的模块fun_to_test(FakeIO)
。如果提示不同,您可以只对gets
参数进行模式匹配。
defmodule FakeIO do
def gets("prompt1"), do: "value1"
def gets("prompt2"), do: "value2"
end
如果它们始终相同,则需要保持gets
被调用次数的状态:
defmodule FakeIO do
def start_link do
Agent.start_link(fn -> 1 end, name: __MODULE__)
end
def gets(_prompt) do
times_called = Agent.get_and_update(__MODULE__, fn state ->
{state, state + 1}
end)
case times_called do
1 -> "value1"
2 -> "value2"
end
end
end
最后一个实现是具有内部状态的完整模拟。您需要FakeIO.start_link
在测试中使用它之前调用它。如果这是您在许多地方需要做的事情,您可能会考虑使用一些模拟库,但正如您所看到的 - 这并不太复杂。为了FakeIO
做得更好,您可以打印提示。我在这里跳过了这个细节。
在接受的答案中发现 FakeIO 解决方案非常有帮助。希望添加另一个清晰的示例,并在需要时指出从 FakeIO 到真实 IO 的委托
在这里,我有一个简单的要求,即编写一个执行少量 IO 的应用程序,从 STDIN 读取名称并在 STDOUT 上回复。
示例输出
你叫什么名字?灵药
你好,Elixir,很高兴认识你!
下面是“应用程序”,一个名为Ex1
:
defmodule Ex1 do
def sayHello(io \\ IO) do
"What is your name? "
|> input(io)
|> reply
|> output(io)
end
def input(message, io \\ IO) do
io.gets(message) |> String.trim
end
def reply(name) do
"Hello, #{name}, nice to meet you!"
end
def output(message, io \\ IO) do
io.puts(message)
end
end
和相关的测试:
defmodule FakeIO do
defdelegate puts(message), to: IO
def gets("What is your name? "), do: "Elixir "
def gets(value), do: raise ArgumentError, message: "invalid argument #{value}"
end
defmodule Ex1Test do
use ExUnit.Case
import ExUnit.CaptureIO
doctest Ex1
@tag runnable: true
test "input" do
assert Ex1.input("What is your name? ", FakeIO) == "Elixir"
end
@tag runnable: true
test "reply" do
assert Ex1.reply("Elixir") == "Hello, Elixir, nice to meet you!"
end
@tag runnable: true
test "output" do
assert capture_io(fn ->
Ex1.output("Hello, Elixir, nice to meet you!", FakeIO)
end) == "Hello, Elixir, nice to meet you!\n"
end
@tag runnable: true
test "sayHello" do
assert capture_io(fn ->
Ex1.sayHello(FakeIO)
end) == "Hello, Elixir, nice to meet you!\n"
end
end
有趣的部分是FakeIO
与参数模式匹配结合使用并defdelegate
委托给实际IO.puts
调用。gets
如果将预期的 get 参数以外的任何内容传递给 FakeIO,则会有一个“catchall”模式来引发 ArgumentError。
defmodule FakeIO do
defdelegate puts(message), to: IO
def gets("What is your name? "), do: "Elixir "
def gets(value), do: raise ArgumentError, message: "invalid argument #{value}"
end
无论如何,希望这能提供一些关于 FakeIO 使用的见解。