0

我正在编写单个exs文件 Elixir 脚本(不使用mix)。该脚本包含一个模块,以及一个在外部范围内开始接受输入stdin并将其发送到模块函数的函数调用。

我还有第二个文件,其中包含我所有的单元测试。但是,我有两个问题:

  1. 当程序在 ExUnit 中等待输入时stin,直到我按下 Ctrl+D(输入结束),测试才完成。我希望在不运行实际应用程序的情况下对模块内的各个函数运行测试。
  2. 我还想为 CLI 接口编写测试,检查它的输出stdoutstdin. 这可以用 ExUnit 完成吗?
4

3 回答 3

2

当程序在 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

换句话说:

  1. 您应该定义一个获取输入的函数——就是这样。
  2. 您应该定义另一个接受一些输入并执行某些操作的函数。

通常,您测试函数的返回值,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模块更有优势。

于 2019-07-10T21:00:03.133 回答
1

据我所知,您必须将代码转换为.ex文件。那是因为当您需要您的.exs文件以便对其运行测试时:

$ elixir -r my.exs my_tests.exs 

elixir 必须执行文件中的代码.exs——否则您在该文件中定义的模块将不存在。猜猜当你执行文件中的代码时会发生什么?您在文件的顶层有以下内容:

My.read_input()

read_input()函数调用会向标准输出发送提示并IO.gets/1等待用户输入。当您告诉 elixir 执行代码时,它会执行此操作。如果您不需要该文件,那么在您的测试文件中,您对模块中函数的所有引用都将导致:

(CompileError) my_tests.exs:11: 模块 My 未加载且找不到

于 2019-07-11T01:04:10.977 回答
0

好的,您的要求是:

  1. 你不想使用混合。
  2. 你想用一个.exs文件启动你的程序。
  3. 您需要在不运行脚本的情况下针对您的模块运行测试——因为您的脚本会暂停以向用户询问来自标准输入的输入。

  4. 奖励:并且,您想使用该mox模块进行测试。

开始了:

我的.exs:

My.go()

我的前夫:

#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

end

my_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

下一个:

  1. 转到 github 并单击 mox 的克隆或下载按钮
  2. 将 mox.zip文件移动到与脚本相同的目录并解压缩。
  3. 导航到lib目录下的mox-master目录并复制mox.ex到与脚本相同的目录中。

  4. 导航到该lib/mox目录并复制server.ex到与您的脚本相同的目录中。

  5. 编译mox.ex,server.exmy.ex:$ elixirc mox.ex server.ex my.ex

要运行您的脚本:

$ elixir my.exs

测试my.ex

$ elixir my_tests.ex

您可以对我的其他答案中演示的不同输入列表进行测试。

于 2019-07-11T03:00:33.940 回答