Spec-Zone.ru › Phoenix

Введение в тестирование

Исходный код

Требование: предполагается, что вы изучили вводные руководства и запустили приложение Phoenix в рабочем состоянии.

Тестирование стало неотъемлемой частью процесса разработки программного обеспечения, а возможность легко писать содержательные тесты — незаменимая функция любой современной веб-платформы. Phoenix относится к этому серьёзно и предоставляет вспомогательные файлы, упрощающие тестирование всех основных компонентов платформы. Кроме того, вместе с каждым сгенерированным модулем он создаёт тестовые модули с примерами из реальной практики, чтобы помочь нам начать работу.

Elixir поставляется со встроенной платформой тестирования под названием ExUnit. ExUnit стремится быть понятной и явной, сводя магию к минимуму. Phoenix использует ExUnit для всех тестов, и здесь мы тоже будем использовать её.

Запуск тестов

Когда Phoenix генерирует для нас веб-приложение, он также добавляет тесты. Чтобы запустить их, просто введите mix test:

$ mix test
....

Finished in 0.09 seconds
5 tests, 0 failures

Randomized with seed 652656

У нас уже есть пять тестов!

Более того, у нас уже полностью настроена структура каталогов для тестирования, включая вспомогательный файл тестов и файлы поддержки.

test
├── hello_web
│   └── controllers
│       ├── error_html_test.exs
│       ├── error_json_test.exs
│       └── page_controller_test.exs
├── support
│   ├── conn_case.ex
│   └── data_case.ex
└── test_helper.exs

В число тестов, которые мы получаем бесплатно, входят тесты из test/hello_web/controllers/. Они проверяют наши контроллеры и представления. Если вы ещё не читали руководства по контроллерам и представлениям, сейчас самое время это сделать.

Знакомство с тестовыми модулями

В следующих разделах мы познакомимся со структурой тестирования в Phoenix. Начнём с трёх тестовых файлов, созданных Phoenix.

Первый тестовый файл, который мы рассмотрим, — test/hello_web/controllers/page_controller_test.exs.

defmodule HelloWeb.PageControllerTest do
  use HelloWeb.ConnCase

  test "GET /", %{conn: conn} do
    conn = get(conn, ~p"/")
    assert html_response(conn, 200) =~ "Peace of mind from prototype to production"
  end
end

Здесь происходит несколько интересных вещей.

Наши тестовые файлы просто определяют модули. В начале каждого модуля вы найдёте строку вида:

use HelloWeb.ConnCase

Если бы вы писали библиотеку на Elixir вне Phoenix, то вместо use HelloWeb.ConnCase написали бы use ExUnit.Case. Однако Phoenix уже поставляется с набором функций для тестирования контроллеров, а HelloWeb.ConnCase расширяет возможности ExUnit.Case, добавляя эти функции. Скоро мы рассмотрим модуль HelloWeb.ConnCase.

Затем мы определяем каждый тест с помощью макроса test/3. Макрос test/3 принимает три аргумента: имя теста, контекст тестирования, сопоставляемый с образцом, и содержимое теста. В этом тесте мы обращаемся к корневой странице приложения с помощью макроса get/2, выполняя HTTP-запрос «GET» по пути «/». Затем мы проверяем, что отрисованная страница содержит строку "Peace of mind from prototype to production".

При написании тестов на Elixir мы используем проверки, чтобы убедиться, что что-то истинно. В нашем случае assert html_response(conn, 200) =~ "Peace of mind from prototype to production" выполняет несколько действий:

  • Проверяет, что conn отрисовал ответ
  • Проверяет, что ответ имеет код состояния 200 (что на языке HTTP означает OK)
  • Проверяет, что тип ответа — HTML
  • Проверяет, что результат html_response(conn, 200), представляющий собой ответ HTML, содержит строку "Peace of mind from prototype to production"

Но откуда берётся conn, который мы используем в get и html_response? Чтобы ответить на этот вопрос, рассмотрим HelloWeb.ConnCase.

ConnCase

Если открыть test/support/conn_case.ex, вы увидите следующее (без комментариев):

defmodule HelloWeb.ConnCase do
  use ExUnit.CaseTemplate

  using do
    quote do
      # The default endpoint for testing
      @endpoint HelloWeb.Endpoint

      use HelloWeb, :verified_routes

      # Import conveniences for testing with connections
      import Plug.Conn
      import Phoenix.ConnTest
      import HelloWeb.ConnCase
    end
  end
  
  setup tags do
    Hello.DataCase.setup_sandbox(tags)
    {:ok, conn: Phoenix.ConnTest.build_conn()}
  end
end

Здесь многое нужно разобрать.

Во второй строке указано, что это шаблон тестового модуля. Эта функция ExUnit позволяет разработчикам заменить встроенный use ExUnit.Case собственным тестовым модулем. Именно эта строка позволяет нам писать use HelloWeb.ConnCase в начале тестов контроллеров.

После того как мы сделали этот модуль шаблоном тестового модуля, можно определить обратные вызовы, вызываемые в определённых ситуациях. Обратный вызов using определяет код, который будет внедрён в каждый модуль, вызывающий use HelloWeb.ConnCase. В данном случае он начинает с установки атрибута модуля @endpoint, указав в нём имя нашей конечной точки.

Затем он подключает :verified_routes, чтобы в тестах можно было использовать пути на основе ~p, как и в остальной части приложения, и легко создавать пути и URL-адреса.

Наконец, мы импортируем Plug.Conn, чтобы все помощники для работы с подключением, доступные в контроллерах, были доступны и в тестах, а затем импортируем Phoenix.ConnTest. Ознакомьтесь с этими модулями, чтобы узнать обо всех доступных функциях.

Затем шаблон тестового модуля определяет блок setup. Блок setup вызывается перед тестом. Большая часть кода настройки в этом блоке посвящена настройке SQL Sandbox, о которой мы поговорим позже. В последней строке блока setup мы найдём следующее:

{:ok, conn: Phoenix.ConnTest.build_conn()}

Последняя строка setup может возвращать метаданные теста, доступные в каждом тесте. Здесь мы передаём вновь созданный объект Plug.Conn. В начале теста мы извлекаем подключение из этих метаданных:

test "GET /", %{conn: conn} do

Вот откуда берётся подключение! Поначалу структура тестирования может показаться немного сложной из-за косвенных связей, но по мере роста набора тестов это окупается, поскольку позволяет сократить объём шаблонного кода.

Тесты представлений

Остальные тестовые файлы нашего приложения предназначены для проверки представлений.

Тестовый модуль представления ошибок, test/hello_web/controllers/error_html_test.exs, демонстрирует несколько интересных особенностей.

defmodule HelloWeb.ErrorHTMLTest do
  use HelloWeb.ConnCase, async: true

  # Bring render_to_string/4 for testing custom views
  import Phoenix.Template

  test "renders 404.html" do
    assert render_to_string(HelloWeb.ErrorHTML, "404", "html", []) == "Not Found"
  end

  test "renders 500.html" do
    assert render_to_string(HelloWeb.ErrorHTML, "500", "html", []) == "Internal Server Error"
  end
end

HelloWeb.ErrorHTMLTest задаёт async: true, что означает, что этот тестовый модуль будет выполняться параллельно с другими тестовыми модулями. Хотя отдельные тесты внутри модуля по-прежнему выполняются последовательно, это может значительно увеличить общую скорость тестирования.

Он также импортирует Phoenix.Template, чтобы использовать функцию render_to_string/4. Благодаря этому все проверки могут быть простыми проверками на равенство строк.

Запуск тестов по каталогам и файлам

Теперь, когда мы знаем, что делают наши тесты, рассмотрим разные способы их запуска.

Как мы видели в начале этого руководства, весь набор тестов можно запустить с помощью mix test.

$ mix test
....

Finished in 0.2 seconds
5 tests, 0 failures

Randomized with seed 540755

Чтобы запустить все тесты в определённом каталоге, например test/hello_web/controllers, можно передать путь к этому каталогу в mix test.

$ mix test test/hello_web/controllers/
.

Finished in 0.2 seconds
5 tests, 0 failures

Randomized with seed 652376

Чтобы запустить все тесты в конкретном файле, можно передать путь к нему в mix test.

$ mix test test/hello_web/controllers/error_html_test.exs
...

Finished in 0.2 seconds
2 tests, 0 failures

Randomized with seed 220535

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

Допустим, мы хотим запустить только тест, проверяющий, как HelloWeb.ErrorHTML отображает 500.html. Тест начинается на 11-й строке файла, поэтому запустить его можно так:

$ mix test test/hello_web/controllers/error_html_test.exs:11
Including tags: [line: "11"]
Excluding tags: [:test]

.

Finished in 0.1 seconds
2 tests, 0 failures, 1 excluded

Randomized with seed 288117

Мы указали первую строку теста, но подойдёт любая строка внутри него. Например, подойдут :11, :12 или :13.

Запуск тестов с помощью тегов

ExUnit позволяет присваивать теги отдельным тестам или всему модулю. После этого можно запускать только тесты с определённым тегом или исключить тесты с этим тегом и запустить все остальные.

Давайте посмотрим, как это работает.

Сначала добавим @moduletag в test/hello_web/controllers/error_html_test.exs.

defmodule HelloWeb.ErrorHTMLTest do
  use HelloWeb.ConnCase, async: true

  @moduletag :error_view_case
  ...
end

Если в качестве тега модуля указать только атом, ExUnit предполагает, что его значение равно true. При желании можно указать и другое значение.

defmodule HelloWeb.ErrorHTMLTest do
  use HelloWeb.ConnCase, async: true

  @moduletag error_view_case: "some_interesting_value"
  ...
end

Пока оставим простой атом @moduletag :error_view_case.

Чтобы запустить только тесты из тестового модуля представления ошибок, передайте --only error_view_case в mix test.

$ mix test --only error_view_case
Including tags: [:error_view_case]
Excluding tags: [:test]

...

Finished in 0.1 seconds
5 tests, 0 failures, 3 excluded

Randomized with seed 125659

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

$ mix test test/hello_web/controllers/error_html_test.exs:11
Including tags: [line: "11"]
Excluding tags: [:test]

.

Finished in 0.2 seconds
2 tests, 0 failures, 1 excluded

Randomized with seed 364723

Если указать значение true для error_view_case, результат будет таким же.

$ mix test --only error_view_case:true
Including tags: [error_view_case: "true"]
Excluding tags: [:test]

...

Finished in 0.1 seconds
5 tests, 0 failures, 3 excluded

Randomized with seed 833356

Однако если указать false в качестве значения для error_view_case, ни один тест не запустится, поскольку ни один тег в нашей системе не соответствует error_view_case: false.

$ mix test --only error_view_case:false
Including tags: [error_view_case: "false"]
Excluding tags: [:test]



Finished in 0.1 seconds
5 tests, 0 failures, 5 excluded

Randomized with seed 622422
The --only option was given to "mix test" but no test executed

Аналогичным образом можно использовать флаг --exclude. Он запустит все тесты, кроме тестов из модуля представления ошибок.

$ mix test --exclude error_view_case
Excluding tags: [:error_view_case]

.

Finished in 0.2 seconds
5 tests, 0 failures, 2 excluded

Randomized with seed 682868

Указание значений для тега работает с --exclude так же, как и с --only.

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

defmodule HelloWeb.ErrorHTMLTest do
  use HelloWeb.ConnCase, async: true

  @moduletag :error_view_case

  # Bring render/4 and render_to_string/4 for testing custom views
  import Phoenix.Template

  @tag individual_test: "yup"
  test "renders 404.html" do
    assert render_to_string(HelloWeb.ErrorView, "404", "html", []) ==
           "Not Found"
  end

  @tag individual_test: "nope"
  test "renders 500.html" do
    assert render_to_string(HelloWeb.ErrorView, "500", "html", []) ==
           "Internal Server Error"
  end
end

Чтобы запустить только тесты с тегом individual_test, независимо от его значения, выполните следующее:

$ mix test --only individual_test
Including tags: [:individual_test]
Excluding tags: [:test]

..

Finished in 0.1 seconds
5 tests, 0 failures, 3 excluded

Randomized with seed 813729

Можно также указать значение и запустить только тесты с ним.

$ mix test --only individual_test:yup
Including tags: [individual_test: "yup"]
Excluding tags: [:test]

.

Finished in 0.1 seconds
5 tests, 0 failures, 4 excluded

Randomized with seed 770938

Аналогичным образом можно запустить все тесты, кроме тех, которым назначен тег с определённым значением.

$ mix test --exclude individual_test:nope
Excluding tags: [individual_test: "nope"]

...

Finished in 0.2 seconds
5 tests, 0 failures, 1 excluded

Randomized with seed 539324

Можно задать более точные условия и исключить все тесты из модуля представления ошибок, кроме теста с тегом individual_test, имеющим значение "yup".

$ mix test --exclude error_view_case --include individual_test:yup
Including tags: [individual_test: "yup"]
Excluding tags: [:error_view_case]

..

Finished in 0.2 seconds
5 tests, 0 failures, 1 excluded

Randomized with seed 61472

Наконец, можно настроить ExUnit так, чтобы теги исключались по умолчанию. Настройка ExUnit по умолчанию выполняется в файле test/test_helper.exs:

ExUnit.start(exclude: [error_view_case: true])

Ecto.Adapters.SQL.Sandbox.mode(Hello.Repo, :manual)

Теперь при запуске mix test выполняются только тесты из page_controller_test.exs и error_json_test.exs.

$ mix test
Excluding tags: [error_view_case: true]

.

Finished in 0.2 seconds
5 tests, 0 failures, 2 excluded

Randomized with seed 186055

Это поведение можно изменить с помощью флага --include, указав mix test включить тесты с тегом error_view_case.

$ mix test --include error_view_case
Including tags: [:error_view_case]
Excluding tags: [error_view_case: true]

....

Finished in 0.2 seconds
5 tests, 0 failures

Randomized with seed 748424

Этот приём может быть очень полезен для управления длительными тестами, которые нужно запускать только в CI или в определённых сценариях.

Случайный порядок

Запуск тестов в случайном порядке — хороший способ убедиться, что тесты действительно изолированы. Если мы замечаем периодические сбои в определённом тесте, возможно, предыдущий тест изменяет состояние системы, не восстанавливая его после выполнения, и тем самым влияет на последующие тесты. Такие сбои могут возникать только при определённом порядке запуска тестов.

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

$ mix test --seed 401472
....

Finished in 0.2 seconds
5 tests, 0 failures

Randomized with seed 401472

Параллельное выполнение и разбиение на части

Как мы уже видели, ExUnit позволяет запускать тесты параллельно. Это позволяет разработчикам использовать всю вычислительную мощность своих машин и выполнять наборы тестов как можно быстрее. В сочетании с высокой производительностью Phoenix это позволяет большинству наборов тестов компилироваться и выполняться за долю времени, требуемого другими платформами.

Во время разработки у разработчиков обычно есть доступ к мощным машинам, но на серверах непрерывной интеграции это не всегда так. Поэтому ExUnit также из коробки поддерживает разбиение тестов на части в тестовых средах. Если открыть config/test.exs, вы увидите, что имя базы данных задано так:

database: "hello_test#{System.get_env("MIX_TEST_PARTITION")}",

По умолчанию переменная окружения MIX_TEST_PARTITION не имеет значения и поэтому ни на что не влияет. Но на сервере CI, например, можно распределить набор тестов между машинами с помощью четырёх отдельных команд:

$ MIX_TEST_PARTITION=1 mix test --partitions 4
$ MIX_TEST_PARTITION=2 mix test --partitions 4
$ MIX_TEST_PARTITION=3 mix test --partitions 4
$ MIX_TEST_PARTITION=4 mix test --partitions 4

Это всё, что нужно сделать: ExUnit и Phoenix позаботятся обо всём остальном, включая настройку отдельной базы данных с уникальным именем для каждой части.

Что дальше

Хотя ExUnit — простая платформа тестирования, команда mix test предоставляет гибкий и надёжный инструмент для запуска тестов. Рекомендуем выполнить команду mix help test или прочитать документацию онлайн

Мы рассмотрели возможности, которые Phoenix предоставляет в только что созданном приложении. Кроме того, при создании нового ресурса Phoenix сгенерирует для него все необходимые тесты. Например, можно создать полный каркас со схемой, контекстом, контроллерами и представлениями, выполнив следующую команду в корневом каталоге приложения:

$ mix phx.gen.html Blog Post posts title body:text
* creating lib/hello_web/controllers/post_controller.ex
* creating lib/hello_web/controllers/post_html/edit.html.heex
* creating lib/hello_web/controllers/post_html/index.html.heex
* creating lib/hello_web/controllers/post_html/new.html.heex
* creating lib/hello_web/controllers/post_html/show.html.heex
* creating lib/hello_web/controllers/post_html/post_form.html.heex
* creating lib/hello_web/controllers/post_html.ex
* creating test/hello_web/controllers/post_controller_test.exs
* creating lib/hello/blog/post.ex
* creating priv/repo/migrations/20211001233016_create_posts.exs
* creating lib/hello/blog.ex
* injecting lib/hello/blog.ex
* creating test/hello/blog_test.exs
* injecting test/hello/blog_test.exs
* creating test/support/fixtures/blog_fixtures.ex
* injecting test/support/fixtures/blog_fixtures.ex

Add the resource to your browser scope in lib/demo_web/router.ex:

    resources "/posts", PostController


Remember to update your repository by running migrations:

    $ mix ecto.migrate

Теперь выполним указания, добавим маршрут нового ресурса в файл lib/hello_web/router.ex и запустим миграции.

Если снова выполнить команду mix test, мы увидим, что теперь у нас двадцать один тест!

$ mix test
................

Finished in 0.1 seconds
21 tests, 0 failures

Randomized with seed 537537

Теперь можно переходить к остальным руководствам по тестированию, в которых мы подробно рассмотрим эти тесты и добавим несколько собственных.

← Предыдущая страница Presence
Следующая страница → Контексты тестирования

Пакет Hex Предпросмотр Hex (текущий файл) Скачать версию ePub

Создано с помощью ExDoc (v0.40.3) для языка программирования Elixir

© 2014 Chris McCord
Licensed under the MIT License.
https://phoenix.hexdocs.pm/testing.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API