litbaza книги онлайнРазная литератураCrystal Programming. Введение на основе проекта в создание эффективных, безопасных и читаемых веб-приложений и приложений CLI - Джордж Дитрих

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 61 62 63 64 65 66 67 68 69 ... 75
Перейти на страницу:
компонентов Athena::Spec. Обновите spec/spec_helper.cr, чтобы он выглядел так:

require "spec"

require "../src/blog"

require "athena/spec"

ASPEC.run_all

Помимо модуля Spec и исходного кода нашего блога, нам также требуются помощники по спецификациям, предоставляемые компонентом Framework. Наконец, нам нужно вызвать ASPEC.run_all, чтобы убедиться, что эти типы тестов действительно выполняются. Однако, поскольку компонент Athena Spec не является обязательным, нам необходимо добавить его в качестве зависимости разработки, добавив следующий код в ваш файл shard.yml с последующей установкой шардов:

development_dependencies:

  athena-spec:

    github: athena-framework/spec

      version: ~> 0.2.3

Запуск crystal spec выявил проблему с нашей тестовой установкой. Ответ на запрос полностью зависит от состояния вашей базы данных разработки. Например, если у вас нет созданной/работающей базы данных, вы получите HTTP-ответ 500. Если у вас есть статья с идентификатором 10, вы получите ответ 200, поскольку все работает как положено.

Смешивание данных базы данных разработки с данными тестирования — не лучшая идея, поскольку это усложняет управление и приводит к менее надежным тестам. Чтобы облегчить эту проблему, мы воспользуемся тестовой схемой, созданной еще в Главе 9 «Создание веб-приложения с помощью Athena». Файл настройки языка структурированных запросов (SQL) устанавливает владельцем того же пользователя, что и наша база данных разработки, чтобы мы могли повторно использовать одного и того же пользователя. Поскольку мы также настроили использование переменной среды, нам не нужно менять какой-либо код для поддержки этого. Просто export DATABASE_URL=postgres://blog_user:mYAw3s0meB!log@ localhost:5432/postgres?currentSchema=test, и все должно работать. Еще одна вещь, которую нам нужно будет сделать, — это создать таблицы, а также создать/удалить данные о приборах. Мы собираемся немного схитрить и использовать для этого необработанный API Crystal DB, поскольку он немного выходит за рамки нашего типа EntityManager.

Как упоминалось ранее в этой главе, для решения этой проблемы мы можем использовать некоторые обратные вызовы модуля Crystal Spec. Давайте начнем с добавления следующего кода в файл spec/spec_helper.cr:

DATABASE = DB.open ENV["DATABASE_URL"]

Spec.before_suite do

  DATABASE.exec File.read "#{__DIR__}/../db/000_setup.sql"

  DATABASE.exec "ALTER DATABASE "postgres" SET

    SEARCH_PATH TO "test";"

  DATABASE.exec File.read "#{__DIR__}/../db/001_articles.sql"

end

Spec.after_suite do

  DATABASE.exec "ALTER DATABASE "postgres"

    SET SEARCH_PATH TO "public";"

  DATABASE.close

end

Spec._each do

end

Здесь мы создаем константу для представления пула соединений с нашей базой данных. Затем мы определяем обратный вызов, который запускается один раз перед выполнением любого теста. В рамках этого обратного вызова мы запускаем файлы миграции базы данных, чтобы убедиться в наличии схемы и таблиц перед запуском тестов. Мы также выполняем запрос, чтобы гарантировать, что наши таблицы/запросы будут выполняться в соответствии с нашей тестовой схемой. Наконец, у нас есть еще один обратный вызов, который запускается после выполнения всех тестов, чтобы немного подчистить путем сброса пути поиска обратно к общедоступной схеме и закрытия пула соединений.

Теперь, когда у нас есть таблицы для хранения наших данных, нам нужно выполнить очистку, и мы уже определили, где мы будем это делать. Обновите блок Spec.before_each, чтобы он выглядел следующим образом:

Spec.before_each do

  DATABASE.exec "TRUNCATE TABLE "articles" RESTART IDENTITY;"

end

Здесь мы удаляем все статьи, которые могли быть созданы в рамках каждого интеграционного теста. Сделав это здесь, мы можем гарантировать, что наши тесты не будут мешать друг другу.

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

Чтобы сохранить целенаправленность и простоту, мы просто собираемся выполнять вставки необработанного SQL для целей этой главы. Не стесняйтесь определять некоторые абстракции и вспомогательные методы, а также использовать стороннюю библиотеку приборов — или что-то еще — если хотите.

Поскольку мы автоматически очищаем нашу таблицу после каждого тестового примера, мы можем свободно вставлять любые данные, которые требуются для нашего конкретного тестового примера. В нашем случае нам нужно вставить статью с идентификатором 10. Нам также следует сделать некоторые утверждения против ответа, чтобы убедиться, что это то, что мы ожидаем. Обновите наш тест статьи GET, чтобы он выглядел так:

def test_get_article : Nil

  DATABASE.exec <<-SQL

    INSERT INTO "articles" (id, title, body, created_at,

      updated_at) OVERRIDING SYSTEM VALUE

    VALUES (10, 'TITLE', 'BODY', timezone('utc', now()),

      timezone('utc', now()));

  SQL

  response = self.get "/article/10"

  response.status.should eq HTTP::Status::OK

  article = JSON.parse response.body

  article["title"].as_s.should eq "TITLE"

  article["body"].as_s.should eq "BODY"

end

Поскольку в наших таблицах для первичного ключа (PK) используется GENERATED ALWAYS AS IDENTITY, нам необходимо включить OVERRIDING SYSTEM VALUE в наши инструкции INSERT, чтобы мы могли указать нужный идентификатор.

В нашем тесте статьи GET мы утверждаем, что запрос прошел успешно и возвращает ожидаемые данные. Мы также можем протестировать поток языка гипертекстовой разметки (HTML), установив заголовок принятия как часть запроса. Давайте определим для этого еще один тестовый пример:

def test_get_article_html : Nil

  DATABASE.exec <<-SQL

    INSERT INTO "articles" (id, title, body, created_at,

      updated_at) OVERRIDING SYSTEM VALUE

    VALUES (10, 'TITLE', 'BODY', timezone('utc', now()),

      timezone('utc', now()));

  SQL

  response = self.get "/article/10", headers: HTTP::Headers

    {"accept" => "text/html"}

  response.status.should eq HTTP::Status::OK

  response.body.should contain "<p>BODY</p>"

end

Мы также могли бы легко протестировать создание статьи, например:

def test_post_article : Nil

  response = self.post "/article", body: %({"title":"TITLE",

    "body":"BODY"})

  article = JSON.parse response.body

  article["title"].as_s.should eq "TITLE"

  article["body"].as_s.should eq "BODY"

  article["created_at"].as_s?.should_not be_nil

  article["id"].raw.should be_a Int64

end

Независимо от того, как вы это сделаете, в конечном итоге наши интеграционные тесты контроллера статьи оказались довольно простыми и мощными. Они предоставляют средства для тестирования всего потока запроса, включая прослушиватели, преобразователи параметров и обработчики форматов. Он также позволяет тестировать любую пользовательскую логику сериализации или проверки как часть полезной нагрузки запроса/ответа.

Резюме

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

В этой

1 ... 61 62 63 64 65 66 67 68 69 ... 75
Перейти на страницу:

Комментарии
Минимальная длина комментария - 20 знаков. Уважайте себя и других!
Комментариев еще нет. Хотите быть первым?