当程序在 stin 等待输入时,ExUnit 测试直到我按下 Ctrl+D(输入结束)才完成。我希望在不运行实际应用程序的情况下对模块内的单个函数运行测试。
想想嘲笑。
该脚本包含一个模块,以及外部范围内的一个函数,该函数开始接受来自标准输入的输入并将其发送到模块函数。
我认为这不是一个好的测试结构。相反,你应该安排这样的事情:
富/lib/ax:
defmodule Foo.A do
def go do
start()
|> other_func()
end
def start do
IO.gets("enter: ")
end
def other_func(str) do
IO.puts("You entered: #{str}")
end
end
换句话说:
- 您应该定义一个获取输入的函数——就是这样。
- 您应该定义另一个接受一些输入并执行某些操作的函数。
通常,您测试函数的返回值,start()
如上所示。但是,在您的情况下,您还需要测试other_func()
发送到标准输出的输出。ExUnit 有一个功能:capture_io。
这是我第一次尝试mox。要使用 模拟函数mox
,您的模块需要实现behaviour
. 行为只是说明模块必须定义的功能。这是一个行为定义,它指定了我要模拟的函数:
foo/lib/my_io.ex:
defmodule Foo.MyIO do
@callback start() :: String.t()
end
String.t()
是字符串的类型说明,右边的项::
是函数的返回值,因此start()
不接受任何参数并返回一个字符串。
然后您指定您的模块实现该行为:
defmodule Foo.A do
@behaviour Foo.MyIO
...
...
end
通过该设置,您现在可以模拟或模拟行为中指定的任何函数。
你说你没有使用混合项目,但我是。对不起。
测试/test_helpers.exs:
ExUnit.start()
Ecto.Adapters.SQL.Sandbox.mode(Foo.Repo, :manual)
Mox.defmock(Foo.MyIOMock, for: Foo.MyIO) #(random name, behaviour_definition_module)
测试/my_test.exs:
defmodule MyTest do
use ExUnit.Case, async: true
import Mox
import ExUnit.CaptureIO
setup :verify_on_exit! # For Mox.
test "stdin stdout io" do
Foo.MyIOMock
|> expect(:start, fn -> "hello" end)
assert Foo.MyIOMock.start() == "hello"
#Doesn't use mox:
assert capture_io(fn -> Foo.A.other_func("hello") end)
== "You entered: hello\n"
end
end
这部分:
Foo.MyIOMock
|> expect(:start, fn -> "hello" end)
start()
指定从标准输入读取的函数的模拟或模拟。mock 函数通过返回一个随机字符串来模拟从 stdin 读取。对于如此简单的事情,这似乎需要做很多工作,但那是测试!如果这太令人困惑,那么您可以创建自己的模块:
defmodule MyMocker do
def start() do
"hello"
end
end
然后在你的测试中:
test "stdin stdout io" do
assert Foo.MyMocker.start() == "hello"
assert capture_io(fn -> Foo.A.other_func("hello") end)
== "You entered: hello\n"
end
我还想为 CLI 接口编写测试,检查它在 stdout 上的输出与在 stdin 上的各种输入
因为匿名函数(fn args -> ... end
)是闭包,它们可以看到周围代码中的变量,所以你可以这样做:
input = "goodbye"
Foo.MyIOMock
|> expect(:start, fn -> input end)
assert Foo.MyIOMock.start() == input
assert capture_io(fn -> Foo.A.other_func(input) end)
== "You entered: #{input}\n"
你也可以这样做:
inputs = ["hello", "goodbye"]
Enum.each(inputs, fn input ->
Foo.MyIOMock
|> expect(:start, fn -> input end)
assert Foo.MyIOMock.start() == input
assert capture_io(fn -> Foo.A.other_func(input) end)
== "You entered: #{input}\n"
end)
请注意,这比创建自己的MyMocker
模块更有优势。