使用ExUnit测试CLI Elixir脚本

时间:2019-07-10 16:29:37

标签: elixir ex-unit

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

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

  1. 当程序在stin等待输入时,直到我按Ctrl + D(输入结束),ExUnit测试才会完成。我希望在模块中的各个功能上运行测试,而无需运行实际的应用程序。
  2. 我还想为CLI界面编写测试,检查它在stdout上的输出与stdin上的各种输入。可以用ExUnit完成吗?

3 个答案:

答案 0 :(得分:1)

  

当程序等待输入时,ExUnit测试不会   完成,直到我按Ctrl + D(输入结束)。我想要跑步   在我的模块中测试单个函数而不运行   实际的应用程序。

思考mocks

  

该脚本包含一个模块以及外部作用域中的一个函数   开始接受来自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

换句话说:

  1. 您应该定义一个获取输入的函数,仅此而已。
  2. 您应该定义另一个函数,该函数接受一些输入并执行某些操作。

通常,您要测试函数的返回值,例如上面的start()。但是,根据您的情况,您还需要测试other_func()发送到stdout的输出。 ExUnit具有以下功能:capture_io

这是我第一次尝试mox。要使用mox模拟功能,您的模块需要实现behaviour。行为仅说明模块必须定义的功能。这是一个行为定义,用于指定我要模拟的功能:

foo / lib / my_io.ex:

defmodule Foo.MyIO do
  @callback start() :: String.t()
end

String.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)

指定start()函数的模拟或仿真,该函数从stdin读取。模拟功能通过返回随机字符串来模拟从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模块相比,这有何优势。

答案 1 :(得分:1)

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

$ elixir -r my.exs my_tests.exs 

elixir必须执行.exs文件中的代码-否则,您在该文件中定义的模块将不存在。猜猜执行文件中的代码会发生什么?您在文件的顶层具有以下条件:

My.read_input()

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

  

(CompileError)my_tests.exs:11:模块My未加载且无法   被发现

答案 2 :(得分:0)

好的,您的要求是:

  1. 您不想使用混音。
  2. 您要使用.exs文件启动程序。
  3. 您需要在不运行脚本的情况下针对您的模块运行测试-因为您的脚本暂停了,要求用户从stdin中输入内容。

  4. 奖金:而且,您想使用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

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并单击Clone or Download button获取Mox。
  2. 将mox .zip文件移动到脚本所在的目录并解压缩。
  3. 导航至lib目录下的mox-master目录,并将mox.ex复制到脚本所在的目录。

  4. 导航到lib/mox目录,并将server.ex复制到脚本所在的目录。

  5. 编译mox.exserver.exmy.ex$ elixirc mox.ex server.ex my.ex

运行脚本:

$ elixir my.exs

测试my.ex

$ elixir my_tests.ex

您可以按照我的其他答案中的说明对一系列不同的输入进行测试。