当程序在 stin 等待输入时,ExUnit 测试不会
完成,直到我按 Ctrl+D(输入结束)。我想运行
在不运行
实际应用。
想想mocks。
脚本包含一个模块,以及外部范围内的一个函数
开始接受来自标准输入的输入并将其发送到模块
功能。
我认为这不是一个好的测试结构。相反,您应该这样安排:
foo/lib/a.x:
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/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)
test/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 界面编写测试,检查一下
标准输出上的输出与标准输入上的各种输入
因为匿名函数(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 模块更有优势。