Что такое doctest и как его использовать?
В Elixir doctest — это механизм тестирования, позволяющий писать тесты прямо в документации к функциям. Эти тесты извлекаются из комментариев и автоматически выполняются при запуске тестов. Это удобно для демонстрации использования функции и для одновременной проверки корректности её работы.
Как работает doctest
Elixir позволяет добавлять документацию к функциям с помощью макроса @doc. Внутри можно вставлять интерактивные примеры кода, начиная со строки iex>. Когда вы используете doctest, эти примеры интерпретируются как настоящие тесты и выполняются с помощью ExUnit.
Пример: простая функция с doctest
Файл lib/math.ex:
defmodule Math do
@moduledoc """
Простейшие арифметические функции.
"""
@doc """
Складывает два числа.
## Примеры
iex> Math.sum(1, 2)
3
iex> Math.sum(-5, 5)
0
"""
def sum(a, b), do: a + b
end
Затем в test/math_test.exs:
defmodule MathTest do
use ExUnit.Case
doctest Math
end
Теперь при запуске mix test все iex> строки в @doc будут интерпретироваться как реальные тесты.
Формат написания примеров
@doc """
Возвращает квадрат числа.
## Примеры
iex> MyMath.square(4)
16
iex> MyMath.square(-3)
9
"""
def square(x), do: x \* x
— Всё, что начинается с iex> — это выражение;
— Следующая строка — ожидаемый результат;
— Можно использовать несколько выражений подряд;
— Также поддерживается вывод ошибок.
Пример с ошибкой
@doc """
Делит число на другое.
## Примеры
iex> MyMath.divide(10, 2)
5.0
iex> MyMath.divide(10, 0)
\*\* (ArithmeticError) bad argument in arithmetic expression
"""
def divide(a, b), do: a / b
Doctest проверит, что при делении на ноль будет выброшено именно это исключение с ожидаемым сообщением.
Требования и ограничения
-
iex> должен находиться в начале строки.
-
В одном @doc можно писать несколько примеров.
-
Doctest не должен зависеть от состояния, внешних ресурсов или IO.
-
Пример должен быть полностью детерминированным.
-
Если вы меняете сигнатуру функции, не забудьте обновить примеры.
Структура проекта с doctest
-
lib/my_module.ex — основной код с документацией и doctest.
-
test/my_module_test.exs — подключает doctest MyModule.
Уточнение, как работает doctest
Когда запускается doctest MyModule, компилятор анализирует @moduledoc и @doc в MyModule и вытаскивает все строки с iex>, превращая их во внутренние вызовы test.
На низком уровне это трансформируется примерно так:
test "doctest for sum/2" do
assert Math.sum(1, 2) == 3
end
Советы по использованию
-
Делайте doctest только для простых и предсказуемых функций.
-
Используйте doctest в библиотеках, чтобы пользователи видели сразу пример использования.
-
Не пишите в doctest вызовов, зависящих от времени, случайных значений или сетевых ресурсов.
Как отключить отдельный doctest
Иногда не хочется включать doctest для конкретной функции. Для этого используется атрибут:
@doc false
def internal_function, do: ...
Или можно использовать фильтры через :except:
doctest MyModule, except: \[:moduledoc\]
Также можно указать only: [...], если нужно тестировать только определённые функции.
Использование в библиотеке
Если ты пишешь библиотеку и хочешь автоматически протестировать примеры из документации — doctest идеален. Он обеспечивает:
-
Синхронизацию кода и документации;
-
Надёжность и доверие к doc-примерам;
-
Проверку ошибок и исключений.
Комбинирование с обычными тестами
Ты можешь совмещать doctest и test в одном файле:
defmodule MyModuleTest do
use ExUnit.Case
doctest MyModule
test "ручной тест" do
assert MyModule.func() == :ok
end
end
Doctest в Elixir делает документацию живой, проверяемой и синхронизированной с реальным кодом, а сам механизм является частью стандартной библиотеки и работает без дополнительных зависимостей.