首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用ExUnit测试脚本

使用ExUnit测试脚本
EN

Stack Overflow用户
提问于 2019-07-10 16:29:37
回答 3查看 667关注 0票数 0

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

我还有第二个文件,它包含我所有的单元测试。不过,我有两个问题:

  1. 当程序在stin上等待输入时,ExUnit测试直到我按下Ctrl+D (输入结束)才能完成。我希望在没有运行实际应用程序的情况下,在模块内的各个函数上运行测试。
  2. 我还想为CLI接口编写测试,检查它在stdout上的输出和stdin上的各种输入。这能用ExUnit来完成吗?
EN

回答 3

Stack Overflow用户

发布于 2019-07-10 21:00:03

当程序在stin等待输入时,ExUnit测试不会完成,直到我按下Ctrl+D (输入结束)。我希望在不运行实际应用程序的情况下,对模块中的inividual函数进行测试。

想想嘲弄吧。

脚本包含一个模块,以及外部作用域中的一个函数,该函数开始接受来自stdin的输入并将其发送给模块函数。

我不认为这是一个很好的测试结构。相反,你应该安排这样的事情:

foo/lib/a.x:

代码语言:javascript
复制
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有一个函数:io

这是我第一次尝试使用mox。要用mox模拟一个函数,您的模块需要实现一个behaviour。行为只说明模块必须定义的函数。下面是一个行为定义,它指定了我想要模拟的函数:

foo/lib/my_io.ex:

代码语言:javascript
复制
defmodule Foo.MyIO do
  @callback start() :: String.t()
end

String.t()是字符串的类型规范,::右边的术语是函数的返回值,因此start()不接受args并返回一个字符串。

然后指定模块实现该行为:

代码语言:javascript
复制
defmodule Foo.A do
  @behaviour Foo.MyIO

  ...
  ...
end

使用该设置,您现在可以模拟或模拟行为中指定的任何函数。

你说你不使用混合项目,但我抱歉的。

test/test_helpers.exs:

代码语言:javascript
复制
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:

代码语言:javascript
复制
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

本部分:

代码语言:javascript
复制
  Foo.MyIOMock
  |> expect(:start, fn -> "hello" end)

指定从stdin读取的start()函数的模拟或模拟。模拟函数只是通过返回一个随机字符串来模拟从stdin读取数据。对于如此简单的事情来说,这似乎是一项很大的工作,但这是测试!如果这太令人困惑,那么您可以创建自己的模块:

代码语言:javascript
复制
defmodule MyMocker do
  def start() do
    "hello"
  end    
end

然后在你的测试中:

代码语言:javascript
复制
 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)是闭包,它们可以看到周围代码中的变量,因此您可以这样做:

代码语言:javascript
复制
    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"

您也可以这样做:

代码语言:javascript
复制
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模块相比,这是一个优势。

票数 2
EN

Stack Overflow用户

发布于 2019-07-11 01:04:10

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

代码语言:javascript
复制
$ elixir -r my.exs my_tests.exs 

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

代码语言:javascript
复制
My.read_input()

read_input()函数调用IO.gets/1,它向stdout发送提示并等待用户输入。当您让灵丹妙药执行代码时,它就会这样做。如果您不需要该文件,那么在您的测试文件中,所有对模块中函数的引用都会导致:

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

票数 1
EN

Stack Overflow用户

发布于 2019-07-11 03:00:33

好吧,你的要求是:

  1. 你不想用混音。
  2. 您希望使用.exs文件启动程序。
  3. 您需要在不运行脚本的情况下对您的模块运行测试--因为您的脚本停止向用户请求stdin的输入。
  4. 奖励:而且,您希望使用mox模块进行测试。

我们开始:

my.exs:

代码语言:javascript
复制
My.go()

my.ex:

代码语言:javascript
复制
#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:

代码语言:javascript
复制
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.exserver.exmy.ex$ elixirc mox.ex server.ex my.ex

要运行您的脚本:

代码语言:javascript
复制
$ elixir my.exs

测试my.ex

代码语言:javascript
复制
$ elixir my_tests.ex

您可以对不同输入的列表进行测试,如我的另一个答案所示。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/56975005

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档