Тестирование контекстов
Исходный кодТребование: Предполагается, что вы ознакомились с вводными руководствами и запустили приложение Phoenix в рабочем состоянии.
Требование: Предполагается, что вы ознакомились с руководством «Введение в тестирование».
Требование: Предполагается, что вы ознакомились с руководствами по контекстам.
В конце руководства «Введение в тестирование» мы сгенерировали HTML-ресурс для публикаций с помощью следующей команды:
$ mix phx.gen.html Blog Post posts title body:text
Это бесплатно предоставило нам несколько модулей, включая контекст Blog и схему Post, а также соответствующие файлы тестов. Как мы узнали из руководства по контекстам, контекст Blog — это просто модуль с функциями для определённой области нашей бизнес-логики, а схема Post соответствует определённой таблице в нашей базе данных.
В этом руководстве мы рассмотрим тесты, сгенерированные для наших контекстов и схем. Прежде чем приступить к работе, запустим mix test, чтобы убедиться, что все наши тесты выполняются без ошибок.
$ mix test ................ Finished in 0.6 seconds 21 tests, 0 failures Randomized with seed 638414
Отлично. У нас двадцать один тест, и все они проходят!
Тестирование публикаций
Если открыть test/hello/blog_test.exs, вы увидите файл со следующим содержимым:
defmodule Hello.BlogTest do
use Hello.DataCase
alias Hello.Blog
describe "posts" do
alias Hello.Blog.Post
import Hello.BlogFixtures
@invalid_attrs %{body: nil, title: nil}
test "list_posts/0 returns all posts" do
post = post_fixture()
assert Blog.list_posts() == [post]
end
...
В начале файла мы импортируем Hello.DataCase, который, как мы скоро увидим, похож на HelloWeb.ConnCase. Если HelloWeb.ConnCase предоставляет вспомогательные функции для работы с соединениями, что полезно при тестировании контроллеров и представлений, то Hello.DataCase предоставляет функциональность для работы с контекстами и схемами.
Затем мы задаём псевдоним, чтобы обращаться к Hello.Blog просто как к Blog.
Затем мы начинаем блок describe "posts". Блок describe — это возможность ExUnit группировать похожие тесты. Мы сгруппировали все тесты, связанные с публикациями, потому что контексты в Phoenix могут объединять несколько схем. Например, если выполнить эту команду:
$ mix phx.gen.html Blog Comment comments post_id:references:posts body:text
В контексте Hello.Blog появится множество новых функций, а в файле тестов — целый новый блок describe "comments".
Тесты, определённые для нашего контекста, очень просты. Они вызывают функции контекста и проверяют их результаты. Как видите, некоторые из этих тестов даже создают записи в базе данных:
test "create_post/1 with valid data creates a post" do
valid_attrs = %{body: "some body", title: "some title"}
assert {:ok, %Post{} = post} = Blog.create_post(valid_attrs)
assert post.body == "some body"
assert post.title == "some title"
end
На этом этапе вы можете задаться вопросом: как Phoenix гарантирует, что данные, созданные в одном тесте, не повлияют на другие тесты? Рады, что вы спросили. Чтобы ответить на этот вопрос, поговорим о DataCase.
DataCase
Если открыть test/support/data_case.ex, вы увидите следующее:
defmodule Hello.DataCase do
use ExUnit.CaseTemplate
using do
quote do
alias Hello.Repo
import Ecto
import Ecto.Changeset
import Ecto.Query
import Hello.DataCase
end
end
setup tags do
Hello.DataCase.setup_sandbox(tags)
:ok
end
def setup_sandbox(tags) do
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Hello.Repo, shared: not tags[:async])
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
end
def errors_on(changeset) do
...
end
end
Hello.DataCase — это ещё один ExUnit.CaseTemplate. В блоке using мы видим все псевдонимы и импорты, которые DataCase добавляет в наши тесты. Фрагмент setup для DataCase очень похож на соответствующий фрагмент из ConnCase. Как видим, большая часть блока setup посвящена настройке SQL Sandbox.
Именно SQL Sandbox позволяет нашим тестам записывать данные в базу без влияния на другие тесты. Если вкратце, в начале каждого теста мы открываем транзакцию в базе данных. По завершении теста транзакция автоматически откатывается, фактически удаляя все данные, созданные в тесте.
Кроме того, SQL Sandbox позволяет одновременно запускать несколько тестов, даже если они взаимодействуют с базой данных. Эта возможность доступна для баз данных PostgreSQL и позволяет ускорить тесты контекстов и контроллеров, если добавить флаг async: true при их использовании:
use Hello.DataCase, async: true
При запуске асинхронных тестов с песочницей следует учитывать некоторые особенности. Подробнее см. в Ecto.Adapters.SQL.Sandbox.
Наконец, в конце модуля DataCase находится функция errors_on с примерами её использования. Она нужна для тестирования любых проверок, которые мы захотим добавить в наши схемы. Давайте попробуем добавить собственные проверки и протестировать их.
Тестирование схем
При генерации HTML-ресурса Post Phoenix создал контекст Blog и схему Post. Для контекста был сгенерирован файл тестов, но для схемы — нет. Однако это не значит, что схему не нужно тестировать, — просто до сих пор не было необходимости её тестировать.
Возможно, вы задаётесь вопросом: когда нужно тестировать непосредственно контекст, а когда — схему? Ответ на этот вопрос совпадает с ответом на вопрос, когда добавлять код в контекст, а когда — в схему.
Общее правило: весь код без побочных эффектов следует размещать в схеме. Иными словами, если вы просто работаете со структурами данных, схемами и наборами изменений, помещайте код в схему. В контексте обычно размещают код, который создаёт и обновляет схемы, а затем записывает их в базу данных или отправляет через API.
Мы собираемся добавить дополнительные проверки в модуль схемы — это отличный повод написать тесты для самой схемы. Откройте lib/hello/blog/post.ex и добавьте следующую проверку в def changeset:
def changeset(post, attrs) do post |> cast(attrs, [:title, :body]) |> validate_required([:title, :body]) |> validate_length(:title, min: 2) end
Новая проверка требует, чтобы заголовок содержал не менее двух символов. Давайте напишем для неё тест. Создайте файл test/hello/blog/post_test.exs со следующим содержимым:
defmodule Hello.Blog.PostTest do
use Hello.DataCase, async: true
alias Hello.Blog.Post
test "title must be at least two characters long" do
changeset = Post.changeset(%Post{}, %{title: "I"})
assert %{title: ["should be at least 2 character(s)"]} = errors_on(changeset)
end
end
Вот и всё. По мере развития нашей бизнес-логики у нас есть чётко определённые места для тестирования контекстов и схем.
© 2014 Chris McCord
Licensed under the MIT License.
https://phoenix.hexdocs.pm/testing_contexts.html