使用ExUnit进行测试时如何伪造IO输入?

al_*_*al_ 5 testing functional-programming elixir ex-unit

我有一个我想测试的Elixir程序,它可以通过IO.gets多次从用户那里获得输入.我如何在测试中伪造这个输入?

注意:我想为每个返回不同的值 IO.gets

tko*_*wal 7

这样做的首选方法是将代码分为纯(无副作用)和不纯(io).所以,如果您的代码如下所示:

IO.gets
...
...
...
IO.gets
...
...
Run Code Online (Sandbox Code Playgroud)

尝试将IO.gets之间的部分提取到可以单独测试的函数中IO.gets:

def fun_to_test do
  input1 = IO.gets
  fun1(input1)
  input2 = IO.gets
  fun2(input2)
end
Run Code Online (Sandbox Code Playgroud)

然后你可以单独测试这些功能.这并不总是最好的事情,尤其是不纯的部分是内心深处if,casecond语句.

另一种方法是将其IO作为显式依赖项传递:

def fun_to_test(io \\ IO) do
  io.gets
  ...
  ...
  ...
  io.gets
  ...
  ...
end
Run Code Online (Sandbox Code Playgroud)

这样您就可以在不做任何修改的情况下从生产代码中使用它,但在测试中,您可以将它传递给一些不同的模块fun_to_test(FakeIO).如果提示不同,您可以只对gets参数进行模式匹配.

defmodule FakeIO do
  def gets("prompt1"), do: "value1"
  def gets("prompt2"), do: "value2"
end
Run Code Online (Sandbox Code Playgroud)

如果它们始终相同,则需要保持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
Run Code Online (Sandbox Code Playgroud)

最后一个实现是一个完全工作的模拟及其内部状态.FakeIO.start_link在测试中使用它之前需要调用.如果这是你需要在许多地方做的事情,你可以考虑一些模拟库,但正如你所看到的 - 这不是太复杂.为了FakeIO做得更好,您可以打印提示.我在这里略过了这个细节.