Когда программа ожидает ввода в stin, тесты ExUnit не
завершить, пока я не нажму Ctrl + D (конец ввода). Я хотел бы запустить
тесты отдельных функций внутри моего модуля без запуска
актуальное приложение.
Думай издевается .
Скрипт содержит модуль, а также одну функцию во внешней области видимости.
который начинает принимать входные данные от стандартного ввода и отправляет его в модуль
функции.
Не думаю, что это хорошая структура для тестирования. Вместо этого вы должны расположить такие вещи:
Foo / Библиотека / 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 / Библиотека / 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()
, которая читает из stdin. Функция mock имитирует чтение из стандартного ввода, просто возвращая случайную строку. Это может показаться большой работой для чего-то такого упрощенного, но это тестирование! Если это слишком сбивает с толку, то вы можете просто создать свой собственный модуль:
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
модуля.