我正在编写一个exs文件Elixir脚本(不使用mix)。脚本包含一个模块,以及外部作用域中的一个函数调用,它开始接受来自stdin的输入并将其发送给模块函数。
我还有第二个文件,它包含我所有的单元测试。不过,我有两个问题:
stin上等待输入时,ExUnit测试直到我按下Ctrl+D (输入结束)才能完成。我希望在没有运行实际应用程序的情况下,在模块内的各个函数上运行测试。stdout上的输出和stdin上的各种输入。这能用ExUnit来完成吗?发布于 2019-07-10 21:00:03
当程序在stin等待输入时,ExUnit测试不会完成,直到我按下Ctrl+D (输入结束)。我希望在不运行实际应用程序的情况下,对模块中的inividual函数进行测试。
想想嘲弄吧。
脚本包含一个模块,以及外部作用域中的一个函数,该函数开始接受来自stdin的输入并将其发送给模块函数。
我不认为这是一个很好的测试结构。相反,你应该安排这样的事情:
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()发送给stdout的输出。ExUnit有一个函数:io。
这是我第一次尝试使用mox。要用mox模拟一个函数,您的模块需要实现一个behaviour。行为只说明模块必须定义的函数。下面是一个行为定义,它指定了我想要模拟的函数:
foo/lib/my_io.ex:
defmodule Foo.MyIO do
@callback start() :: String.t()
endString.t()是字符串的类型规范,::右边的术语是函数的返回值,因此start()不接受args并返回一个字符串。
然后指定模块实现该行为:
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)指定从stdin读取的start()函数的模拟或模拟。模拟函数只是通过返回一个随机字符串来模拟从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模块相比,这是一个优势。
发布于 2019-07-11 01:04:10
据我所知,您必须将代码转换为.ex文件。这是因为当您需要您的.exs文件来对它运行测试时:
$ elixir -r my.exs my_tests.exs 灵丹妙药必须执行.exs文件中的代码--否则您在该文件中定义的模块将不存在。猜猜在执行文件中的代码时会发生什么?在文件的顶层有以下内容:
My.read_input()read_input()函数调用IO.gets/1,它向stdout发送提示并等待用户输入。当您让灵丹妙药执行代码时,它就会这样做。如果您不需要该文件,那么在您的测试文件中,所有对模块中函数的引用都会导致:
(CompileError) my_tests.exs:11:模块My未加载且无法找到
发布于 2019-07-11 03:00:33
好吧,你的要求是:
.exs文件启动程序。mox模块进行测试。我们开始:
my.exs:
My.go()my.ex:
#Define a behavior for mox testing:
defmodule MyIO do
@callback read_input() :: String.t()
end
# Adopt the behaviour in your module:
defmodule My do
@behaviour MyIO
def go do
read_input()
|> other_func()
end
def read_input do
IO.gets("enter: ")
end
def other_func(str) do
IO.puts("You entered: #{str}")
end
endmy_tests.exs:
ExUnit.start()
Mox.Server.start_link([])
defmodule MyTests do
use ExUnit.Case, async: true
import ExUnit.CaptureIO
import Mox
defmock(MyIOMock, for: MyIO)
setup :verify_on_exit!
test "stdin/stdout is correct" do
MyIOMock
|> expect(:read_input, fn -> "hello" end)
assert MyIOMock.read_input() == "hello"
#Doesn't use mox:
assert capture_io(fn -> My.other_func("hello") end)
== "You entered: hello\n"
end
end下一步:
.zip文件移到与脚本相同的目录中,并解压缩它。lib目录下的mox-master目录,并将mox.ex复制到与脚本相同的目录中。lib/mox目录,并将server.ex复制到与脚本相同的目录中。mox.ex、server.ex和my.ex:$ elixirc mox.ex server.ex my.ex要运行您的脚本:
$ elixir my.exs测试my.ex
$ elixir my_tests.ex您可以对不同输入的列表进行测试,如我的另一个答案所示。
https://stackoverflow.com/questions/56975005
复制相似问题