Fork me on GitHub

Тестирование

Тестирование — важная часть разработки. В этом уроке мы узнаем, как тестировать наш Elixir код с помощью ExUnit, и познакомимся с некоторыми отличными приёмами.

Содержание

ExUnit

В Elixir есть встроенный фреймворк для тестирования — ExUnit и он содержит всё необходимое для тщательного тестирования нашего кода. Перед тем как двигаться дальше, важно отметить, что тесты реализованы в виде скриптов Elixir, поэтому нам надо использовать расширение .exs. Для того, чтобы мы могли выполнять наши тесты, нужно запустить ExUnit с помощью ExUnit.start(), обычно это делается в test/test_helper.exs.

Когда мы сгенерировали проект-пример из прошлого урока, mix сделал для нас тест, который можно найти в test/example_test.exs:

defmodule ExampleTest do
  use ExUnit.Case
  doctest Example

  test "the truth" do
    assert 1 + 1 == 2
  end
end

Тесты проекта можно запустить с помощью mix test. Если мы сделаем это, то увидим примерно следующее:

Finished in 0.03 seconds (0.02s on load, 0.01s on tests)
1 tests, 0 failures

assert

Если вы когда-либо ранее писали тесты, вы должны быть знакомы с assert; в некоторых фреймворках роль assert выполняют should или expect.

Макрос assert используется, чтобы проверить, что выражение истинно. В случае, если это не так, возникнет ошибка, а тесты завершатся с ошибкой. Давайте изменим наш пример и запустим mix test, чтобы протестировать ошибку:

defmodule ExampleTest do
  use ExUnit.Case
  doctest Example

  test "the truth" do
    assert 1 + 1 == 3
  end
end

Сейчас мы увидим другой результат:

  1) test the truth (ExampleTest)
     test/example_test.exs:5
     Assertion with == failed
     code: 1 + 1 == 3
     lhs:  2
     rhs:  3
     stacktrace:
       test/example_test.exs:6

......

Finished in 0.03 seconds (0.02s on load, 0.01s on tests)
1 tests, 1 failures

ExUnit покажет, какое именно утверждение было ошибочным, какое значение ожидалось и какое было получено на самом деле.

refute

refute относится к assert также, как unless к if. Используйте refute, если вы хотите убедиться, что выражение всегда ложно.

assert_raise

Иногда может понадобиться заявить, что возникла ошибка, и мы можем сделать это с помощью assert_raise. Мы столкнёмся с примером применения assert_raise в следующем уроке о Plug.

assert_receive

В Elixir приложения состоят из процессов-акторов, которые отправляют сообщения друг другу. Довольно часто нужно протестировать, что сообщения отправляются. Так как ExUnit работает в собственном процессе, он может получать сообщения. Именно получение сообщений этим процессом можно проверить с помощью assert_received:

defmodule SendingProcess do
  def run(pid) do
    send pid, :ping
  end
end

defmodule TestReceive do
  use ExUnit.Case

  test "receives ping" do
    SendingProcess.run(self)
    assert_received :ping
  end
end

assert_received не ждет сообщений по умолчанию, но можно указать время ожидания.

capture_io и capture_log

Получение вывода приложения возможно с использованием ExUnit.captureIO без изменения кода приложения. Просто передайте функцию, генерирующую вывод в качестве параметра:

defmodule OutputTest do
  use ExUnit.Case
  import ExUnit.CaptureIO

  test "outputs Hello World" do
    assert capture_io(fn -> IO.puts "Hello World" end) == "Hello World\n"
  end
end

ExUnit.CaptureLog - эквивалент для отправки вывода приложения в Logger.

Настройка теста

В некоторых случаях перед тестами необходимо произвести настройку. Сделать это можно с помощью макросов setup и setup_all. setup вызывается перед каждым тестом, а setup_all — однажды перед всем набором. Ожидается, что они вернут кортеж вида {:ok, state}, state будет доступен для наших тестов.

В качестве примера изменим наш код и воспользуемся setup_all:

defmodule ExampleTest do
  use ExUnit.Case
  doctest Example

  setup_all do
    {:ok, number: 2}
  end

  test "the truth", state do
    assert 1 + 1 == state[:number]
  end
end

Использование “заглушек”

Простой совет касательно использования заглушек в Elixir: не делайте этого. Возможно, Вам по привычке захочется воспользоваться “заглушкой”, но это крайне не приветствуется сообществом Elixir по веским причинам.

Эта тема раскрыта подробнее в отличной статье. Вкратце, вместо того, чтобы подменять методы для тестирования, создавайте интерфейсы в коде вне приложения и используйте объекты-заглушки, которые будут имплементировать этот интерфейс в процессе тестирования.

Для переключения между имплементациями в коде предложения рекомендуется передавать модули в качестве аргументов функции и использовать значения по умолчанию. Если этот вариант не подходит, можно использовать встроенные механизмы конфигурации. Для создания такого подхода к заглушкам не нужна специальная библиотека. Достаточно функционала языка: поведений и функций обратного вызова.


Поделиться