Это руководство раскрывает встроенные в Rails механизмы для тестирования вашего приложения.
После его прочтения, вы узнаете:
Rails предлагает писать тесты очень просто. Когда вы создаете свои модели и контроллеры, он начинает создавать скелет тестового кода.
Запуск тестов Rails позволяет убедиться, что ваш код придерживается нужной функциональности даже после большой переделки кода.
Тесты Rails также могут симулировать запросы браузера, таким образом, можно тестировать отклик своего приложения без необходимости тестирования с использованием браузера.
Поддержка тестирования встроена в Rails с самого начала. И это не было так: "О! Давайте внесем поддержку запуска тестов, это ново и круто!"
Rails создает директорию test
как только вы создаете проект Rails, используя rails new _application_name_
. Если посмотрите список содержимого этой папки, то увидите:
$ ls -F test
application_system_test_case.rb controllers/ helpers/ mailers/ system/
channels/ fixtures/ integration/ models/ test_helper.rb
Директории helpers
, mailers
и models
предназначены содержать тесты для хелперов вью, рассыльщиков и моделей соответственно. Директория channels
предназначена содержать тесты для соединений и каналов Action Cable. Директория controllers
предназначена содержать тесты для ваших контроллеров, маршрутов и вью. Директория integration
предназначена содержать тесты для взаимодействия между контроллерами.
Директория system
содержит системные тесты, используемые для полного браузерного тестирования вашего приложения. Системные тесты позволяют тестировать приложение так, как ваши пользователи взаимодействуют с ним, а также тестировать ваш JavaScript. Системные тесты наследуются от Capybara и выполняются в браузере.
Фикстуры это способ организации тестовых данных; они находятся в директории fixtures
.
Также будет создана директория jobs
, как только первый связанные тест будет сгенерирован.
Файл test_helper.rb
содержит конфигурацию по умолчанию для ваших тестов.
application_system_test_case.rb
содержит настройки по умолчанию для ваших системных тестов.
По умолчанию каждое приложение на Rails имеет три среды разработки: development, test и production. База данных для каждой из них настраивается в config/database.yml
.
Схожим образом можно модифицировать конфигурацию среды. В этом случае можно модифицировать тестовую среду, изменяя опции в config/environments/test.rb
.
Ваши тесты запускаются с RAILS_ENV=test
.
Если помните, мы использовали команду bin/rails generate model
в руководстве Rails для начинающих. Мы создали нашу первую модель, где, среди прочего, создались незаконченные тесты в папке test
:
$ bin/rails generate model article title:string body:text
...
create app/models/article.rb
create test/models/article_test.rb
create test/fixtures/articles.yml
...
Незаконченный тест по умолчанию в test/models/article_test.rb
выглядит так:
require "test_helper"
class ArticleTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end
Построчное изучение этого файла поможет ориентироваться в коде тестирования и терминологии Rails.
require "test_helper"
Требуя этот файл, загружается конфигурация по умолчанию test_helper.rb
для запуска наших тестов. Мы будем включать эту строку во все написанные тесты, таким образом, все методы, добавленные в этот файл, будут доступны во всех наших тестах.
class ArticleTest < ActiveSupport::TestCase
# ...
end
Класс ArticleTest
определяет тестовый случай, поскольку он унаследован от ActiveSupport::TestCase
. Поэтому ArticleTest
имеет все методы, доступные в ActiveSupport::TestCase
. Позже в этом руководстве мы увидим некоторые из методов, которые он нам дает.
Любой метод, определенный в классе, унаследованном от Minitest::Test
(который является суперклассом для ActiveSupport::TestCase
), начинающийся с test_
, просто вызывает тест. Таким образом, методы, определенные как test_password
и test_valid_password
, это правильные имена тестов, и запустятся автоматически при запуске тестового случая.
Rails также добавляет метод test
, который принимает имя теста и блок. Он генерирует обычный тест MiniTest::Unit
с именем метода, начинающегося с test_
, поэтому можно не беспокоиться об именовании методов, а просто писать так:
test "the truth" do
assert true
end
Это является приблизительно тем же, как если бы написали:
def test_the_truth
assert true
end
Хотя вы все еще можете использовать обычные определения методов, использование макроса test
позволяет получить более читаемое имя теста.
Имя метода генерируется, заменяя пробелы на подчеркивания. Впрочем, результат не должен быть валидным идентификатором Ruby - имя может содержать знаки пунктуации и т.д. Это связано с тем, что в Ruby технически любая строка может быть именем метода. Это может потребовать, чтобы вызовы define_method
и send
функционировали правильно, но формально есть только небольшое ограничение на имя.
Далее посмотрим на наше первое утверждение:
assert true
Утверждение (assertion) - это строчка кода, которая вычисляет объект (или выражение) для ожидаемых результатов. Например, утверждение может проверить:
nil
?
Каждый тест должен содержать одно или более утверждений, без ограничений на их максимальное количество. Только когда все утверждения успешны, тест проходит.
Чтобы увидеть, как сообщается провал теста, вы можете добавить проваливающийся тест в тестовый случай article_test.rb
.
test "should not save article without title" do
article = Article.new
assert_not article.save
end
Давайте запустим только что добавленный тест (где 6
- это номер строчки, где определен тест).
$ bin/rails test test/models/article_test.rb:6
Run options: --seed 44656
# Running:
F
Failure:
ArticleTest#test_should_not_save_article_without_title [/path/to/blog/test/models/article_test.rb:6]:
Expected true to be nil or false
bin/rails test test/models/article_test.rb:6
Finished in 0.023918s, 41.8090 runs/s, 41.8090 assertions/s.
1 runs, 1 assertions, 1 failures, 0 errors, 0 skips
В результате F
обозначает провал. Можете увидеть соответствующую трассировку под Failure
вместе с именем провалившегося теста. Следующие несколько строчек содержат трассировку стека, затем сообщение, где упомянуто фактическое значение и ожидаемое в утверждении значение. По умолчанию сообщение для утверждения предоставляет достаточно информации, чтобы помочь выявить ошибку. Чтобы сделать сообщение о провале для утверждения более читаемым, каждое утверждение предоставляет опциональный параметр для сообщения, как показано тут:
test "should not save article without title" do
article = Article.new
assert_not article.save, "Saved the article without a title"
end
Запуск этого теста покажет более дружелюбное сообщение для утверждения:
Failure:
ArticleTest#test_should_not_save_article_without_title [/path/to/blog/test/models/article_test.rb:6]:
Saved the article without a title
Теперь, чтобы этот тест прошел, можно добавить валидацию на уровне модели для поля title.
class Article < ApplicationRecord
validates :title, presence: true
end
Теперь тест пройдет. Давайте убедимся в этом, запустив его снова:
$ bin/rails test test/models/article_test.rb:6
Run options: --seed 31252
# Running:
.
Finished in 0.027476s, 36.3952 runs/s, 36.3952 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
Теперь, если вы заметили, мы сначала написали провальный тест для желаемой функциональности, затем мы написали некоторый код, добавляющий функциональность, и, наконец, мы убедились, что наш тест прошел. Этот подход к разработке программного обеспечения называют Разработка через тестирование, Test-Driven Development (TDD).
Чтобы увидеть, как сообщается об ошибке, вот тест, содержащий ошибку:
test "should report error" do
# переменная some_undefined_variable не определена в другом месте тестового случая
some_undefined_variable
assert true
end
Теперь вы увидите чуть больше результата в консоли от запуска тестов:
$ bin/rails test test/models/article_test.rb
Run options: --seed 1808
# Running:
.E
Error:
ArticleTest#test_should_report_error:
NameError: undefined local variable or method 'some_undefined_variable' for #<ArticleTest:0x007fee3aa71798>
test/models/article_test.rb:11:in 'block in <class:ArticleTest>'
bin/rails test test/models/article_test.rb:9
Finished in 0.040609s, 49.2500 runs/s, 24.6250 assertions/s.
2 runs, 1 assertions, 0 failures, 1 errors, 0 skips
Отметьте 'E' в результате. Она отмечает тест с ошибкой.
Выполнение каждого тестового метода останавливается, как только возникают какие-либо ошибки или провал утверждения, и тестовый набор продолжается со следующего метода. Все тестовые методы выполняются в случайном порядке. Для настройки порядка тестирования может быть использована опция config.active_support.test_order
.
Когда тест проваливается, вам показывается соответствующий бэктрейс. По умолчанию Rails фильтрует этот бэктрейс и печатает только строчки, относящиеся к вашему приложению. Это устраняет шум от фреймворка и помогает сфокусироваться на вашем коде. Однако, бывают ситуации, когда вам захочется увидеть полный бэктрейс. Установите аргумент -b
(или --backtrace
) для включения этого поведения:
$ bin/rails test -b test/models/article_test.rb
Если хотите, чтобы этот тест прошел, можно его модифицировать, используя assert_raises
следующим образом:
test "should report error" do
# переменная some_undefined_variable не определена в тесте
assert_raises(NameError) do
some_undefined_variable
end
end
Теперь этот тест должен пройти.
К этому моменту вы уже увидели некоторые из имеющихся утверждений. Утверждения - это рабочие лошадки тестирования. Они единственные, кто фактически выполняет проверки, чтобы убедиться, что все работает как задумано.
Ниже представлена выдержка утверждений, которые вы можете использовать с Minitest
, библиотекой тестирования, используемой Rails по умолчанию. Параметр [msg]
- это опциональное строковое сообщение, которое вы можете указать для того, чтобы сделать сообщение о провале более ясным.
Утверждение | Назначение |
---|---|
assert( test, [msg] ) |
Утверждает, что test истинно. |
assert_not( test, [msg] ) |
Утверждает, что test ложно. |
assert_equal( expected, actual, [msg] ) |
Утверждает, что expected == actual истинно. |
assert_not_equal( expected, actual, [msg] ) |
Утверждает, что expected != actual истинно. |
assert_same( expected, actual, [msg] ) |
Утверждает, что expected.equal?(actual) истинно. |
assert_not_same( expected, actual, [msg] ) |
Утверждает, что expected.equal?(actual) ложно. |
assert_nil( obj, [msg] ) |
Утверждает, что obj.nil? истинно. |
assert_not_nil( obj, [msg] ) |
Утверждает, что obj.nil? ложно. |
assert_empty( obj, [msg] ) |
Утверждает, что obj является empty? . |
assert_not_empty( obj, [msg] ) |
Утверждает, что obj не является empty? . |
assert_match( regexp, string, [msg] ) |
Утверждает, что строка соответствует регулярному выражению. |
assert_no_match( regexp, string, [msg] ) |
Утверждает, что строка не соответствует регулярному выражению. |
assert_includes( collection, obj, [msg] ) |
Утверждает, что obj находится в collection . |
assert_not_includes( collection, obj, [msg] ) |
Утверждает, что obj не находится в collection . |
assert_in_delta( expected, actual, [delta], [msg] ) |
Утверждает, что между числами expected и actual разницу delta . |
assert_not_in_delta( expected, actual, [delta], [msg] ) |
Утверждает, что между числами expected и actual разница, отличная от delta . |
assert_in_epsilon ( expected, actual, [epsilon], [msg] ) |
Утверждает, что между числами expected и actual относительная погрешность меньше, чем epsilon . |
assert_not_in_epsilon ( expected, actual, [epsilon], [msg] ) |
Утверждает, что между числами expected и actual относительная погрешность не меньше, чем epsilon . |
assert_throws( symbol, [msg] ) { block } |
Утверждает, что переданный блок бросает symbol. |
assert_raises( exception1, exception2, ... ) { block } |
Утверждает, что переданный блок генерирует одно из переданных исключений. |
assert_instance_of( class, obj, [msg] ) |
Утверждает, что obj является экземпляром class . |
assert_not_instance_of( class, obj, [msg] ) |
Утверждает, что obj не является экземпляром class . |
assert_kind_of( class, obj, [msg] ) |
Утверждает, что obj является экземпляром class или класса, наследуемого от него. |
assert_not_kind_of( class, obj, [msg] ) |
Утверждает, что obj не является экземпляром class или класса, наследуемого от него. |
assert_respond_to( obj, symbol, [msg] ) |
Утверждает, что obj отвечает на symbol . |
assert_not_respond_to( obj, symbol, [msg] ) |
Утверждает, что obj не отвечает на symbol . |
assert_operator( obj1, operator, [obj2], [msg] ) |
Утверждает, что obj1.operator(obj2) истинно. |
assert_not_operator( obj1, operator, [obj2], [msg] ) |
Утверждает, что obj1.operator(obj2) ложно. |
assert_predicate ( obj, predicate, [msg] ) |
Утверждает, что obj.predicate истинно, т.е. assert_predicate str, :empty? |
assert_not_predicate ( obj, predicate, [msg] ) |
Утверждает, что obj.predicate ложно, т.е. assert_not_predicate str, :empty? |
assert_error_reported(class) { block } |
Утверждает, что был сообщен класс ошибки, например assert_error_reported IOError { Rails.error.report(IOError.new("Oops")) } |
assert_no_error_reported { block } |
Утверждает, что ни одной ошибки не было сообщено, например assert_no_error_reported { perform_service } |
flunk( [msg] ) |
Утверждает провал. Это полезно для явного указания теста, который еще не закончен. |
Представленный выше список утверждений поддерживается minitest. Более полный и более актуальный список всех доступных утверждений смотрите в документации Minitest API, в частности Minitest::Assertions
В силу модульной природы фреймворка тестирования, возможно создать свои собственные утверждения. Фактически Rails так и делает. Он включает некоторые специализированные утверждения, чтобы сделать жизнь разработчика проще.
Создание собственных утверждений это особый разговор, которого мы касаться не будем.
Rails добавляет некоторые свои утверждения в фреймворк minitest
:
Утверждение | Назначение |
---|---|
assert_difference(expressions, difference = 1, message = nil) {...} |
Тестирует числовую разницу между возвращаемым значением expression и результатом вычисления в данном блоке. |
assert_no_difference(expressions, message = nil, &block) |
Утверждает, что числовой результат вычисления expression не изменяется до и после применения переданного в блоке. |
assert_changes(expressions, message = nil, from:, to:, &block) |
Тестирует, что результат вычисления expression изменится после применения переданного в блоке. |
assert_no_changes(expressions, message = nil, &block) |
Тестирует, что результат вычисления expression не изменится после применения переданного в блоке. |
assert_nothing_raised { block } |
Обеспечивает, что данный блок не вызывает какие-либо исключения. |
assert_recognizes(expected_options, path, extras={}, message=nil) |
Обеспечивает, что роутинг данного path был правильно обработан, и что проанализированные опции (заданные в хэше expected_options) соответствуют path. По существу он утверждает, что Rails распознает маршрут, заданный в expected_options. |
assert_generates(expected_path, options, defaults={}, extras = {}, message=nil) |
Утверждает, что предоставленные options могут быть использованы для генерации предоставленного пути. Это противоположность assert_recognizes. Параметр extras используется, чтобы сообщить запросу имена и значения дополнительных параметров запроса, которые могут быть в строке запроса. Параметр message позволяет определить свое сообщение об ошибке при провале утверждения. |
assert_response(type, message = nil) |
Утверждает, что отклик идет с определенным кодом статуса. Можете определить :success для обозначения 200-299, :redirect для обозначения 300-399, :missing для обозначения 404, или :error для соответствия интервалу 500-599. Можно передать явный номер статуса или его символьный эквивалент. Более подробно смотрите в полном списке кодов статуса и как работает их привязка. |
assert_redirected_to(options = {}, message=nil) |
Утверждает, что отклик - это перенаправление на URL, соответствующий заданным опциям. Также можно передать именованные маршруты, как в assert_redirected_to root_path , и объекты Active Record, как в assert_redirected_to @article . |
assert_queries_count(count = nil, include_schema: false, &block) |
Утверждает, что &block генерирует int раз запросы SQL. |
assert_no_queries(include_schema: false, &block) |
Утверждает, что &block не генерирует запросы SQL. |
assert_queries_match(pattern, count: nil, include_schema: false, &block) |
Утверждает, что &block генерирует запросы SQL, соответствующие образцу. |
assert_no_queries_match(pattern, &block) |
Утверждает, что &block не генерирует запросы SQL, соответствующие образцу. |
Вы увидите использование некоторых из этих утверждений в следующей части.
Все основные утверждения, такие как assert_equal
, определенные в Minitest::Assertions
, также доступны в классах, используемых в наших тестовых случаях. Фактически, Rails представляет вам следующие классы для наследования:
ActiveSupport::TestCase
ActionMailer::TestCase
ActionView::TestCase
ActiveJob::TestCase
ActionDispatch::IntegrationTest
ActionDispatch::SystemTestCase
Rails::Generators::TestCase
Каждый из этих классов включает Minitest::Assertions
, позволяя использовать все основные утверждения в наших тестах.
За подробностями о Minitest
обратитесь к его документации
Можно запустить все тесты за раз с помощью команды bin/rails test
.
Или можно запустить отдельный тест, передав команде bin/rails test
имя файла, содержащего тестовые случаи.
$ bin/rails test test/models/article_test.rb
Run options: --seed 1559
# Running:
..
Finished in 0.027034s, 73.9810 runs/s, 110.9715 assertions/s.
2 runs, 3 assertions, 0 failures, 0 errors, 0 skips
Это запустит все тестовые методы из тестового случая.
Также можете запустить определенный тестовый метод из тестового случая, предоставив флажок -n
или --name
и имя метода теста.
$ bin/rails test test/models/article_test.rb -n test_the_truth
Run options: -n test_the_truth --seed 43583
# Running:
.
Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
Также можно запустить тест в определенной строчке, предоставив номер строчки.
$ bin/rails test test/models/article_test.rb:6 # запускает определенный тест и строчку
Также можно запустить ряд тестов, предоставив ряд строк.
$ bin/rails test test/models/article_test.rb:6-20 # запускает тесты со строчки 6 до 20
Также можно запустить целую директорию тестов, предоставив путь к этой директории.
$ bin/rails test test/controllers # запускает все тесты из определенной директории
Для запуска тестов также имеется множество других особенностей, таких как падение при первой ошибке (failing fast), вывод результатов тестов после запуска тестов и так далее. Документацию по запуску тестов можно просмотреть так:
$ bin/rails test -h
Usage: rails test [options] [files or directories]
You can run a single test by appending a line number to a filename:
bin/rails test test/models/user_test.rb:27
You can run multiple tests with in a line range by appending the line range to a filename:
bin/rails test test/models/user_test.rb:10-20
You can run multiple files and directories at the same time:
bin/rails test test/controllers test/integration/login_test.rb
By default test failures and errors are reported inline during a run.
minitest options:
-h, --help Display this help.
--no-plugins Bypass minitest plugin auto-loading (or set $MT_NO_PLUGINS).
-s, --seed SEED Sets random seed. Also via env. Eg: SEED=n rake
-v, --verbose Verbose. Show progress processing files.
-n, --name PATTERN Filter run on /regexp/ or string.
--exclude PATTERN Exclude /regexp/ or string from run.
Known extensions: rails, pride
-w, --warnings Run with Ruby warnings enabled
-e, --environment ENV Run tests in the ENV environment
-b, --backtrace Show the complete backtrace
-d, --defer-output Output test failures and errors after the test run
-f, --fail-fast Abort test run on first failure or error
-c, --[no-]color Enable color in the output
-p, --pride Pride. Show your testing pride!
Для запуска всех тестов в среде CI достаточно одной команды:
$ bin/rails test
Однако, данная команда не запускает системные тесты (обычно более медленные), если вы их используете. Для включения системных тестов, добавьте еще один шаг CI, который выполняет команду bin/rails test:system
, либо измените свой начальный шаг на bin/rails test:all
, которая запускает все тесты, включая системные.
Параллельное тестирование позволяет распараллелить тестовый набор. Хотя форк процессов является методом по умолчанию, треды также поддерживаются. Запуск тестов параллельно сокращает время, затрачиваемое на запуск всего тестового набора.
Дефолтный метод распараллеливания - это форк процессов с помощью системы DRb. Процессы форкаются, основываясь на количестве предоставленных воркеров. Значение по умолчанию равно фактическому количеству ядер машины, но может быть изменено на число, переданное методу parallelize.
Чтобы включить распараллеливание, добавьте следующее в test_helper.rb
:
class ActiveSupport::TestCase
parallelize(workers: 2)
end
Количество переданных воркеров равно количеству раз, когда процесс будет форкнут. Возможно, может понадобиться распараллелить локальный тестовый набор отлично от CI, поэтому предоставляется переменная среды, позволяющая легко изменять количество воркеров, которые должны использоваться для запуска теста:
$ PARALLEL_WORKERS=15 bin/rails test
При распараллеливании тестов Active Record автоматически обрабатывает создание базы данных и загрузку схемы в базу данных для каждого процесса. Базы данных будут иметь суффикс с цифрой, соответствующей воркеру. Например, если есть 2 воркера, тесты создадут test-database-0
иtest-database-1
соответственно.
Если количество переданных воркеров равно 1 или менее, процессы не будут форкнуты, и тесты не будут распараллелены, и тесты будут использовать исходную базу данных test-database
.
Предоставляются два хука, один запускается, когда процесс был форкнут, и еще один запускается до закрытия форкнутого процесса. Они могут быть полезны, если приложение использует несколько баз данных или выполняет другие задачи, зависящие от количества воркеров.
Метод parallelize_setup
вызывается сразу после того, как процессы форкнуты. Метод parallelize_teardown
вызывается непосредственно до закрытия процессов.
class ActiveSupport::TestCase
parallelize_setup do |worker|
# настройки баз данных
end
parallelize_teardown do |worker|
# очистка баз данных
end
parallelize(workers: :number_of_processors)
end
Эти методы не нужны или не доступны при параллельном тестировании с помощью тредов.
Если предпочитаете использовать треды или используете JRuby, предоставляется тредовая опция распараллеливания. Тредовый распараллеливатель поддерживается Parallel::Executor
в Minitest.
Чтобы изменить метод распараллеливания для использования тредов над форками, необходимо добавить в test_helper.rb
class ActiveSupport::TestCase
parallelize(workers: :number_of_processors, with: :threads)
end
Приложения Rails, сгенерированные от JRuby или TruffleRuby автоматически включают опцию with: :threads
.
Число переданных воркеров в parallelize
определяет количество тредов, которые будут использоваться в тестах. Возможно, может понадобиться распараллелить локальный тестовый набор отлично от CI, поэтому предоставляется переменная среды, позволяющая легко изменять количество воркеров, которые должны использоваться для запуска теста:
$ PARALLEL_WORKERS=15 bin/rails test
Rails автоматически оборачивает тестовый случай в транзакцию базы данных, которая откатывается после завершения теста. Это обеспечивает независимость тестовых случаев друг от друга, и изменения в базе данных видны только в рамках отдельного теста.
когда нужно протестировать код, запускающий параллельные транзакции в тредах, транзакции могут блокировать друг друга, так как они уже вложены в транзакцию теста.
В классе тестового случая можно отключить транзакции, установив self.use_transactional_tests = false
:
class WorkerTest < ActiveSupport::TestCase
self.use_transactional_tests = false
test "parallel transactions" do
# запускайте несколько тредов, создающих транзакции
end
end
В тестах с отключенными транзакциями необходимо очищать любые созданные тестовые данные, так как изменения не будут автоматически откачены после завершения теста.
Параллельный запуск тестов добавляет дополнительную нагрузку в терминах настройки базы данных и загрузки фикстур. Поэтому Rails не будет распараллеливать запуски, вовлекающие менее, чем 50 тестов.
Этот порог можно сконфигурировать в вашем test.rb
:
config.active_support.test_parallelization_threshold = 100
А также настроить распараллеливание на уровне тестового случая:
class ActiveSupport::TestCase
parallelize threshold: 100
end
Почти каждое приложение на Rails сильно взаимодействует с базой данных, и, как результат, тестам также требуется база данных для работы. Чтобы писать эффективные тесты, следует понять, как настроить эту базу данных и наполнить ее образцом данных.
По умолчанию каждое приложение на Rails имеет три среды разработки: development, test и production. База данных для каждой из них настраивается в config/database.yml
.
Отдельная тестовая база данных позволяет настраивать и работать с данными в изоляции. Таким образом, тесты могут искажать тестовые данные с уверенностью, не беспокоясь о данных в базах данных development или production.
Чтобы запустить тесты, ваша тестовая база данных должна иметь текущую структуры. Тестовый хелпер проверяет, не имеет ли ваша тестовая база данных отложенных миграций. Он пытается загрузить ваши db/schema.rb
или db/structure.sql
в тестовую базу данных. Если есть отложенные миграции - будет вызвана ошибка. Обычно это указывает на то, что ваша схема не полностью мигрирована. Запуск миграций для базы данных development (bin/rails db:migrate
) приведет схему в актуальное состояние.
Если были модификации в существующих миграциях, нужно перестроить тестовую базу данных. Это делается с помощью выполнения bin/rails db:test:prepare
.
Для хороших тестов необходимо подумать о настройке тестовых данных. В Rails этим можно управлять, определяя и настраивая фикстуры. Подробности можно узнать в документации API фикстур.
Fixtures это выдуманное слово для образцов данных. Фикстуры позволяют заполнить вашу тестовую базу данных предопределенными данными до запуска тестов. Фикстуры независимы от типа базы данных и написаны на YAML. На каждую модель имеется отдельный файл.
Фикстуры не разработаны для создания каждого объекта, требуемого в ваших тестах, и они лучше всего подходят только при использовании для данных по умолчанию, которые применимы в общем случае.
Фикстуры расположены в директории test/fixtures
. Когда запускаете bin/rails generate model
для создания новой модели, Rails автоматически создаст незаконченные фикстуры в этой директории.
Фикстуры в формате YAML являются дружелюбным способом описать ваш образец данных. Этот тип фикстур имеет расширение файла .yml (как в users.yml
).
Вот образец файла фикстуры YAML:
# О чудо! Я комментарий YAML!
david:
name: David Heinemeier Hansson
birthday: 1979-10-15
profession: Systems development
steve:
name: Steve Ross Kellock
birthday: 1974-09-27
profession: guy with keyboard
Каждой фикстуре дается имя со следующим за ним списком с отступом пар ключ/значение, разделенных двоеточием. Записи обычно разделяются пустой строчкой. Можете помещать комментарии в файл фикстуры, используя символ # в первом столбце.
Если работаете со связями, можно определить ссылку между двумя различными фикстурами. Вот пример для связи belongs_to
/has_many
:
# test/fixtures/categories.yml
about:
name: About
# test/fixtures/articles.yml
first:
title: Welcome to Rails!
body: Hello world!
category: about
# test/fixtures/action_text/rich_texts.yml
first_content:
record: first (Article)
name: content
body: <div>Hello, from <strong>a fixture</strong></div>
Отметьте, что у ключа category
в статье first
из fixtures/articles.yml
значение about
, и что у ключа record
в позиции first_content
из fixtures/action_text/rich_texts.yml
значение first (Article)
. Это подсказывает Active Record загрузить категорию about
из fixtures/categories.yml
для первого, и Action Text загрузить статью first
из fixtures/articles.yml
для последнего.
При связи двух записей по имени в связанных фикстурах можно использовать имя фикстуры вместо атрибута id:
связанной фикстуры. Rails автоматически назначит первичный ключ, согласующийся между запусками. Подробнее об этом поведении связей можно прочитать в документации API фикстур.
Подобно другим моделям Active Record, записи вложений Active Storage наследуются от экземпляров ActiveRecord::Base и, поэтому, могут создаваться фикстурами.
Рассмотрим модель Article
со связанным изображением, вложенным как thumbnail
, вместе с данными фикстуры YAML:
class Article
has_one_attached :thumbnail
end
# test/fixtures/articles.yml
first:
title: An Article
Допустим, что у нас есть файл, кодированный как image/png, в test/fixtures/files/first.png
, следующие позиции фикстуры YAML сгенерируют соответствующие записи ActiveStorage::Blob
и ActiveStorage::Attachment
:
# test/fixtures/active_storage/blobs.yml
first_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob filename: "first.png" %>
# test/fixtures/active_storage/attachments.yml
first_thumbnail_attachment:
name: thumbnail
record: first (Article)
blob: first_thumbnail_blob
ERb позволяет встраивать код Ruby в шаблоны. Формат фикстур YAML предварительно обрабатывается с помощью ERb при загрузке фикстур. Это позволяет использовать Ruby для помощи в генерации некоторых образцов данных. Например, следующий код сгенерирует тысячу пользователей:
<% 1000.times do |n| %>
user_<%= n %>:
username: <%= "user#{n}" %>
email: <%= "user#{n}@example.com" %>
<% end %>
Rails по умолчанию автоматически загружает все фикстуры из директории test/fixtures
для ваших тестов моделей и контроллеров. Загрузка состоит из трех этапов:
Чтобы убрать существующие данные из базы, Rails пытается отключить триггеры ссылочной целостности (такие как внешние ключи и проверки ограничений). Если вы получаете надоедливые ошибки прав доступа при запуске тестов, убедитесь, что у пользователя базы данных есть права на отключение этих триггеров в тестовой среде. (В PostgreSQL только суперпользователи могут отключать все триггеры. Подробнее о правах доступа в PostgreSQL читайте здесь)
Фикстуры являются экземплярами Active Record. Как упоминалось в этапе №3 выше, Вы можете обращаться к объекту напрямую, поскольку он автоматически доступен как метод, область видимости которого локальна для тестового случая. Например:
# это возвратит объект User для фикстуры с именем david
users(:david)
# это возвратит свойство для david, названное id
users(:david).id
# он имеет доступ к методам, доступным для класса User
david = users(:david)
david.call(david.partner)
Чтобы получить несколько фикстур за раз, вы можете передать список имен фикстур. Например:
# это возвратит массив, содержащий фикстуры david и steve
users(:david, :steve)
Тесты моделей используются для тестирования различных моделей вашего приложения.
Тесты моделей Rails хранятся в директории test/models
directory. Rails предоставляет генератор для создания скелета теста модели.
$ bin/rails generate test_unit:model article title:string body:text
create test/models/article_test.rb
create test/fixtures/articles.yml
У тестов модели нет своего собственного суперкласса, такого как ActionMailer::TestCase
. Вместо этого они наследуются от ActiveSupport::TestCase
.
Системные тесты позволяют тестировать взаимодействие пользователя с вашим приложением, запускать тесты либо в реальном, либо в headless браузере. Системные тесты используют Capybara под капотом.
Для создания системных тестов Rails используют директорию приложения test/system
. Rails предоставляет генератор для создания скелета системного теста.
$ bin/rails generate system_test users
invoke test_unit
create test/system/users_test.rb
Вот как выглядит свежесгенерированный системный тест:
require "application_system_test_case"
class UsersTest < ApplicationSystemTestCase
# test "visiting the index" do
# visit users_url
#
# assert_selector "h1", text: "Users"
# end
end
По умолчанию системные тесты запускаются с помощью драйвера Selenium с использованием браузера Chrome и разрешения экрана 1400x1400. Следующий раздел объясняет, как изменить настройки по умолчанию.
По умолчанию Rails пытается обработать исключения, возникшие во время тестов, и отобразить страницы ошибок в формате HTML. Это поведение можно контролировать с помощью настройки config.action_dispatch.show_exceptions
.
В Rails изменить настройки по умолчанию для системных тестов очень просто. Все настройки абстрагированы, поэтому вы можете сфокусироваться на написании своих тестов.
При генерации нового приложения или скаффолда, в тестовой директории будет создан файл application_system_test_case.rb
. Это то самое место, где должны находиться все настройки для ваших системных тестов.
Если хотите изменить настройки по умолчанию, надо изменить то, с помощью чего "запускаются" ваши системные тесты. Скажем, вы хотите изменить драйвер с Selenium на Cuprite. Сначала добавьте гем cuprite
в свой Gemfile
. Затем сделайте в своем файле application_system_test_case.rb
следующее:
require "test_helper"
require "capybara/cuprite"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :cuprite
end
Имя драйвера — обязательный аргумент для driven_by
. Опциональные аргументы, который можно передать в driven_by
это :using
для браузера (используется только в Selenium), :screen_size, чтобы изменить размер экрана для скриншотов, и :options
, которые могут использоваться для установки опций, поддерживаемых драйвером.
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :firefox
end
Если необходимо использовать headless-браузер, можно использовать Headless Chrome или Headless Firefox, добавив headless_chrome
или headless_firefox
в аргумент :using
.
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :headless_chrome
end
Если хотите использовать удаленный браузер, например Headless Chrome in Docker, нужно добавить удаленный url
и установить удаленный browser
в options
.
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
url = ENV.fetch("SELENIUM_REMOTE_URL", nil)
options = if url
{ browser: :remote, url: url }
else
{ browser: :chrome }
end
driven_by :selenium, using: :headless_chrome, options: options
end
Теперь следует получить соединение с удаленным браузером.
$ SELENIUM_REMOTE_URL=http://localhost:4444/wd/hub bin/rails test:system
Если ваше приложение в тестах также запускается удаленно, например в контейнере Docker, Capybara нужно больше данных, как вызывать удаленные серверы.
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
def setup
Capybara.server_host = "0.0.0.0" # bind to all interfaces
Capybara.app_host = "http://#{IPSocket.getaddress(Socket.gethostname)}" if ENV["SELENIUM_REMOTE_URL"].present?
super
end
# ...
end
Теперь следует получить соединение к удаленным браузеру и серверу, независимо, запущен он в контейнере Docker или CI.
Если вашей конфигурации для Capybara требуется больше настроек, чем предоставлено Rails, то эту дополнительную конфигурацию можно поместить в файл application_system_test_case.rb
.
За дополнительными настройками обратитесь к документации Capybara.
ScreenshotHelper
- это хелпер, разработанный для захвата скриншотов ваших тестов.
Это полезно для просмотра браузера в момент, когда упал тест, или для просмотра скриншотов для отладки.
Предоставляются два метода: take_screenshot
и take_failed_screenshot
. take_failed_screenshot
автоматически включается в before_teardown
внутри Rails.
Вспомогательный метод take_screenshot
может быть включен где угодно в ваших тестах, чтобы снять скриншот браузера.
Теперь мы собираемся добавить системный тест в наше приложение блога. Мы продемонстрируем написание системного теста для посещения индексной страницы и для создания статьи в блоге.
При использовании генератора скаффолда, автоматически создавался скелет системного теста. Если не использовать генератор скаффолда, нужно начать с создания скелета системного теста.
$ bin/rails generate system_test articles
Это создаст файл теста. В результате выполнения предыдущей команды вы увидите:
invoke test_unit
create test/system/articles_test.rb
Теперь откройте этот файл и напишите первой утверждение:
require "application_system_test_case"
class ArticlesTest < ApplicationSystemTestCase
test "viewing the index" do
visit articles_path
assert_selector "h1", text: "Articles"
end
end
Тест должен увидеть, что на индексной странице статей есть h1
и пройти.
Запустите системные тесты.
bin/rails test:system
По умолчанию, выполнение bin/rails test
не будет запускать ваши системные тесты. Убедитесь, что вы выполняете bin/rails test:system
, чтобы на самом деле запустить их. Также можно запустить bin/rails test:all
для запуска всех тестов, включая системные тесты.
Теперь давайте протестируем процесс создания новой статьи в нашем блоге.
test "should create Article" do
visit articles_path
click_on "New Article"
fill_in "Title", with: "Creating an Article"
fill_in "Body", with: "Created this article successfully!"
click_on "Create Article"
assert_text "Creating an Article"
end
Первый шаг — это вызов visit articles_path
. Это приведет тест на индексную страницу статей.
Затем click_on "New Article"
найдет кнопку "New Article" на индексной странице. Это перенаправит браузер на /articles/new
.
Затем браузер заполнит title и body статьи представленным текстом. Как только поля будут заполнены, будет нажата "Create Article", что отправит запрос POST для создания новой статьи в базе данных.
Мы будем перенаправлены обратно на индексную страницу статей, где мы убедимся, что текст заголовка новой статьи присутствует на индексной странице статей.
Если необходимо протестировать размеры мобильных устройств поверх тестирования для десктопных, можно создать другой класс, который наследуется от ActionDispatch::SystemTestCase
и использовать в тестовом наборе. В этом примере файл, называемый mobile_system_test_case.rb
, создается в директории /test
со следующей конфигурацией.
require "test_helper"
class MobileSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :chrome, screen_size: [375, 667]
end
Чтобы использовать эту конфигурацию, нужно создать тест внутри test/system
, который наследуется от MobileSystemTestCase
. Теперь можно протестировать приложение, используя несколько разных конфигураций.
require "mobile_system_test_case"
class PostsTest < MobileSystemTestCase
test "visiting the index" do
visit posts_url
assert_selector "h1", text: "Posts"
end
end
Красота системного тестирования в том, что оно похоже на интеграционное тестирование в том, что оно тестирует взаимодействие пользователя с вашими контроллерами, моделями и вью, но системное тестирование более адекватное и фактически тестирует ваше приложение так, как его будет использовать реальный пользователь. Двигаясь дальше, можно протестировать все, что пользователь сможет делать в вашем приложении - комментирование, удаление статей, публикацию черновиков и так далее.
Интеграционные тесты используются для тестирования взаимодействия различных частей нашего приложения. Они в основном используются для тестирования важных рабочих процессов в нашем приложении.
Для создания интеграционных тестов Rails используется директория 'test/integration' нашего приложения. Rails предоставляет нам генератор для создания скелета интеграционного теста.
$ bin/rails generate integration_test user_flows
exists test/integration/
create test/integration/user_flows_test.rb
Вот как выглядит свежесгенерированный интеграционный тест:
require "test_helper"
class UserFlowsTest < ActionDispatch::IntegrationTest
# test "the truth" do
# assert true
# end
end
Здесь тест наследуется от ActionController::IntegrationTest
. Это делает доступным несколько дополнительных хелперов для использования в наших интеграционных тестах.
По умолчанию Rails пытается обработать исключения, возникшие во время тестов, и отобразить страницы ошибок в формате HTML. Это поведение можно контролировать с помощью настройки config.action_dispatch.show_exceptions
.
В дополнение к стандартным хелперам тестирования, наследование от ActionDispatch::IntegrationTest
дает несколько дополнительных хелперов для написания интеграционных тестов. Давайте для краткости представим три категории хелперов.
Для работы с runner-ом интеграционных тестов, смотрите ActionDispatch::Integration::Runner
.
Для выполнения запросов у нас есть ActionDispatch::Integration::RequestHelpers
.
Для загрузки файлов нам поможет ActionDispatch::TestProcess::FixtureFile
.
Если хотим модифицировать сессию или состояние вашего интеграционного теста, нам поможет ActionDispatch::Integration::Session
.
Давайте добавим интеграционный тест в наше приложение блога. Начнем с основного процесса создания новой статьи блога, чтобы убедиться, что все работает правильно.
Начнем с генерации скелета нашего интеграционного теста:
$ bin/rails generate integration_test blog_flow
Он должен создать файл для размещения теста, и в результате предыдущей команды мы должны увидеть:
invoke test_unit
create test/integration/blog_flow_test.rb
Теперь откроем этот файл и напишем наше первое утверждение:
require "test_helper"
class BlogFlowTest < ActionDispatch::IntegrationTest
test "can see the welcome page" do
get "/"
assert_select "h1", "Welcome#index"
end
end
Мы рассмотрим assert_select
для запрашивания результирующего HTML запроса в разделе "Тестирование вью" ниже. Он используется для тестирования отклика на наш запрос, убеждаясь в наличии ключевых элементов HTML и их содержимого.
При посещении корневого пути мы должны увидеть welcome/index.html.erb
, отрендеренную для представления. Таким образом, это утверждение должно пройти.
Как насчет тестирования возможности создавать новую статью в нашем блоге и просматривать полученную статью.
test "can create an article" do
get "/articles/new"
assert_response :success
post "/articles",
params: { article: { title: "can create", body: "article successfully." } }
assert_response :redirect
follow_redirect!
assert_response :success
assert_select "p", "Title:\n can create"
end
Давайте разобьем этот тест на кусочки, чтобы понять его.
Мы начинаем с вызова экшна :new
контроллера Articles. Этот запрос должен быть успешным.
После этого мы делаем запрос post к экшну :create
нашего контроллера Articles:
post "/articles",
params: { article: { title: "can create", body: "article successfully." } }
assert_response :redirect
follow_redirect!
Следующие две строчки — это обработка редиректа, который мы настроили при создании новой статьи.
Не забывайте вызвать follow_redirect!
Если планируете сделать последовательные запросы после выполнения редиректа.
Наконец, мы убеждаемся, что наш отклик был успешным, и нашу статью можно прочесть на странице.
У нас получилось протестировать маленький процесс посещения нашего блога и создания новой статьи. Если мы хотим идти дальше, мы можем добавить тесты для комментирования, удаления статей и редактирования комментариев. Интеграционные тесты — это отличное место для экспериментов с различными сценариями использования приложения.
В Rails тестирование различных экшнов контроллера — это форма написания функциональных тестов. Помните, что контроллеры обрабатывают входящие веб-запросы к вашему приложению и в конечном итоге откликаются отрендеренной вью. При написании функциональных тестов, вы тестируете, как ваши экшны обрабатывают запросы, ожидаемый результат или, в некоторых случаях, отклики вью HTML.
Следует протестировать такие вещи, как:
Самым простым способом увидеть функциональные тесты в действии является генерация контроллера с помощью генератора скаффолда:
$ bin/rails generate scaffold_controller article title:string body:text
...
create app/controllers/articles_controller.rb
...
invoke test_unit
create test/controllers/articles_controller_test.rb
...
Это сгенерирует код контроллера и тестов для ресурса Article
. Можете взглянуть на файл articles_controller_test.rb
в директории test/controllers
.
Если у вас уже есть контроллер и вы просто хотите сгенерировать код теста скаффолда для каждого из семи экшнов по умолчанию, можете использовать следующую команду:
$ bin/rails generate test_unit:scaffold article
...
invoke test_unit
create test/controllers/articles_controller_test.rb
...
Давайте взглянем на один такой тест, test_should_get_index
из файла articles_controller_test.rb
.
# articles_controller_test.rb
class ArticlesControllerTest < ActionDispatch::IntegrationTest
test "should get index" do
get articles_url
assert_response :success
end
end
В тесте test_should_get_index
, Rails имитирует запрос к экшну index, убеждается, что запрос был успешным, а также обеспечивает, что генерируется правильное тело отклика.
Метод get
стартует веб-запрос и заполняет результаты в @response
. Он может принимать до 6 аргументов:
articles_url
).
params
: опция с хэшем параметров запроса для передачи в экшн (например, параметры строки запроса или переменные для модели article).
headers
: для установки заголовков, которые будут переданы с запросом.
env
: для настройки среды запроса по необходимости.
xhr
: был ли запрос Ajax или нет. Можно установить true для пометки, что запрос является Ajax.
as
: для кодировки запроса другим типом запроса.
Все эти аргументы с ключевым словом опциональны.
Пример: Вызов экшна :show
для первого Article
, передавая заголовок HTTP_REFERER
:
get article_url(Article.first), headers: { "HTTP_REFERER" => "http://example.com/home" }
Другой пример: Вызов экшна :update
для последнего Article
, передавая новый текст для title
в params
, как запрос Ajax:
patch article_url(Article.last), params: { article: { title: "updated" } }, xhr: true
Еще один пример: Вызов экшна :create
для создания новой статьи, передавая текст для title
в params
, как запрос JSON:
post articles_path, params: { article: { title: "Ahoy!" } }, as: :json
Если попытаетесь запустить тест test_should_create_article
из articles_controller_test.rb
, он провалится из-за недавно добавленной валидации на уровне модели, и это правильно.
Давайте модифицируем тест test_should_create_article
в articles_controller_test.rb
так, чтобы все наши тесты проходили:
test "should create article" do
assert_difference("Article.count") do
post articles_url, params: { article: { body: "Rails is awesome!", title: "Hello Rails" } }
end
assert_redirected_to article_path(Article.last)
end
Теперь можете попробовать запустить все тесты, и они должны пройти.
Если выполнены шаги в разделе про базовую аутентификацию, то необходимо добавить авторизацию в заголовок каждого запроса, чтобы все тесты проходили:
post articles_url, params: { article: { body: "Rails is awesome!", title: "Hello Rails" } }, headers: { Authorization: ActionController::HttpAuthentication::Basic.encode_credentials("dhh", "secret") }
По умолчанию Rails пытается обработать исключения, возникшие во время тестов, и отобразить страницы ошибок в формате HTML. Это поведение можно контролировать с помощью настройки config.action_dispatch.show_exceptions
.
Если вы знакомы с протоколом HTTP, то знаете, что get
это тип запроса. Имеется 6 типов запросов, поддерживаемых в функциональных тестах Rails:
get
post
patch
put
head
delete
У всех типов запросов есть эквивалентные методы, которые можно использовать. В обычном приложении C.R.U.D. вы чаще будете использовать get
, post
, put
и delete
.
Функциональные тесты не проверяют, поддерживается ли определенный тип запроса экшном, мы больше беспокоимся о результате. Для этого случая существуют тесты запросов, чтобы сделать ваши тесты более целенаправленными.
Чтобы протестировать запросы Ajax, можно указать опцию xhr: true
в методах get
, post
, patch
, put
и delete
. Например:
test "ajax request" do
article = articles(:one)
get article_url(article), xhr: true
assert_equal "hello world", @response.body
assert_equal "text/javascript", @response.media_type
end
После того, как запрос был сделан и обработан, у вас будет 3 объекта Hash, готовых для использования:
cookies
- Любые установленные куки
flash
- Любые объекты, находящиеся во flash
session
- Любый объекты, находящиеся в переменных сессии
Как и в случае с обычными объектами Hash, можете получать доступ к значениям, указав ключ в строке. Также можете указать его именем символа. Например:
flash["gordon"] # или flash[:gordon]
session["shmession"] # или session[:shmession]
cookies["are_good_for_u"] # или cookies[:are_good_for_u]
После того как запрос был выполнен, В ваших функциональных тестах также доступны три переменные экземпляра:
@controller
- Контроллер, обрабатывающий запрос
@request
- Объект запроса
@response
- Объект отклика
class ArticlesControllerTest < ActionDispatch::IntegrationTest
test "should get index" do
get articles_url
assert_equal "index", @controller.action_name
assert_equal "application/x-www-form-urlencoded", @request.media_type
assert_match "Articles", @response.body
end
end
Заголовки HTTP и переменные CGI могут быть переданы как заголовки:
# устанавливаем заголовок HTTP
get articles_url, headers: { "Content-Type": "text/plain" } # имитировать запрос с пользовательским заголовком
# устанавливаем переменную CGI
get articles_url, headers: { "HTTP_REFERER": "http://example.com/home" } # имитировать запрос с пользовательской env переменной
flash
Как помните, одним из трех хэшей был flash
.
Мы хотим добавить сообщение flash
в наше приложение блога, всякий раз, когда кто-то успешно создает новый объект Article.
Давайте начнем с добавления этого утверждения в наш тест test_should_create_article
:
test_should_create_article do
assert_difference("Article.count") do
post articles_url, params: { article: { title: "Some title" } }
end
assert_redirected_to article_path(Article.last)
assert_equal "Article was successfully created.", flash[:notice]
end
Если запустить наш тест сейчас, мы увидим ошибку:
$ bin/rails test test/controllers/articles_controller_test.rb -n test_should_create_article
Run options: -n test_should_create_article --seed 32266
# Running:
F
Finished in 0.114870s, 8.7055 runs/s, 34.8220 assertions/s.
1) Failure:
ArticlesControllerTest#test_should_create_article [/test/controllers/articles_controller_test.rb:16]:
--- expected
+++ actual
@@ -1 +1 @@
-"Article was successfully created."
+nil
1 runs, 4 assertions, 1 failures, 0 errors, 0 skips
Теперь давайте реализуем сообщение flash в нашем контроллере. Наш экшн :create
теперь должен выглядеть так:
def create
@article = Article.new(article_params)
if @article.save
flash[:notice] = "Article was successfully created."
redirect_to @article
else
render "new"
end
end
Если теперь запустить наши тесты, мы увидим, что он проходит:
$ bin/rails test test/controllers/articles_controller_test.rb -n test_should_create_article
Run options: -n test_should_create_article --seed 18981
# Running:
.
Finished in 0.081972s, 12.1993 runs/s, 48.7972 assertions/s.
1 runs, 4 assertions, 0 failures, 0 errors, 0 skips
С этого момента в нашем контроллере Articles тестируются экшны :index
, :new
и :create
. Но как насчет работы с существующими данными?
Давайте напишем тест для экшна :show
:
test "should show article" do
article = articles(:one)
get article_url(article)
assert_response :success
end
Как помните из нашего обсуждения фикстур, что метод articles()
дает нам доступ к нашим фикстурам Articles.
Как насчет удаления существующего объекта Article?
test "should destroy article" do
article = articles(:one)
assert_difference("Article.count", -1) do
delete article_url(article)
end
assert_redirected_to articles_path
end
Также можно добавить тест для обновления существующего объекта Article.
test "should update article" do
article = articles(:one)
patch article_url(article), params: { article: { title: "updated" } }
assert_redirected_to article_path(article)
# Перезагрузим связь, чтобы извлечь обновленные данные и убедиться, что заголовок обновлен.
article.reload
assert_equal "updated", article.title
end
Отметьте, что у нас имеется некоторое дублирование в этих трех тестах, они все получают доступ к одним и тем же данным фикстуры Article. Можно убрать повторения с помощью методов setup
и teardown
, предоставленных ActiveSupport::Callbacks
.
Наш тест должен быть похож на следующее. Не обращайте внимания, что остальные тесты были убраны для краткости.
require "test_helper"
class ArticlesControllerTest < ActionDispatch::IntegrationTest
# вызывается перед каждым отдельным тестом
setup do
@article = articles(:one)
end
# вызывается после каждого отдельного теста
teardown do
# когда контроллер использует кэш, это может быть хорошей идеей сбросить его затем
Rails.cache.clear
end
test "should show article" do
# повторно используем переменную экземпляра @article из setup
get article_url(@article)}
assert_response :success
end
test "should destroy article" do
assert_difference("Article.count", -1) do
delete article_url(@article)
end
assert_redirected_to articles_path
end
test "should update article" do
patch article_url(@article), params: { article: { title: "updated" } }
assert_redirected_to article_path(@article)
# Перезагрузим связь, чтобы извлечь обновленные данные и убедиться, что заголовок обновлен.
article.reload
assert_equal "updated", article.title
end
end
Подобно другим колбэкам Rails, методы setup
и teardown
можно использовать, передав блок, lambda или имя метода символом для вызова.
Чтобы избежать дублирования кода, можно добавлять собственные тестовые хелперы. Хорошим примером может быть хелпер входа в систему:
# test/test_helper.rb
module SignInHelper
def sign_in_as(user)
post sign_in_url(email: user.email, password: user.password)
end
end
class ActionDispatch::IntegrationTest
include SignInHelper
end
require "test_helper"
class ProfileControllerTest < ActionDispatch::IntegrationTest
test "should show profile" do
# теперь хелпер можно повторно использовать в любом тестовом случае контроллера
sign_in_as users(:david)
get profile_url
assert_response :success
end
end
Если ваши хелперы загромоздили test_helper.rb
, их можно извлечь в отдельные файлы. Одним из хороших мест их хранения является lib/test
или test/test_helpers
.
# test/test_helpers/multiple_assertions.rb
module MultipleAssertions
def assert_multiple_of_forty_two(number)
assert (number % 42 == 0), "expected #{number} to be a multiple of 42"
end
end
Затем эти хелперы могут быть явно затребованы или включены по необходимости
require "test_helper"
require "test_helpers/multiple_assertions"
class NumberTest < ActiveSupport::TestCase
include MultipleAssertions
test "420 is a multiple of forty two" do
assert_multiple_of_forty_two 420
end
end
или их можно продолжить включать непосредственно в релевантные родительские классы
# test/test_helper.rb
require "test_helpers/sign_in_helper"
class ActionDispatch::IntegrationTest
include SignInHelper
end
Может быть удобным нетерпеливо загрузить хелперы в test_helper.rb
, таким образом файлы тестов неявно получат к ним доступ. Это можно выполнить с помощью подстановки, следующим образом
# test/test_helper.rb
Dir[Rails.root.join("test", "test_helpers", "**", "*.rb")].each { |file| require file }
В этом есть недостаток в виде увеличения времени загрузки, в противоположность ручного включения только необходимых файлов в отдельные тесты.
Как и все другое в вашем приложении Rails, ваши маршруты можно тестировать. Тесты маршрутов находятся в test/controllers/
или являются частью тестов контроллера.
Если в вашем приложении сложные маршруты, Rails предоставляет ряд полезных хелперов для их тестирования.
Подробности о тестировании маршрутов доступны в Rails, обратитесь к документации API для ActionDispatch::Assertions::RoutingAssertions
.
Тестирование отклика на ваш запрос с помощью подтверждения наличия ключевых элементов HTML и их содержимого, это хороший способ протестировать вью вашего приложения. Как и тесты маршрутов, тесты вью находятся в test/controllers/
или являются частью тестов контроллера. Метод assert_select
позволяет осуществить выборку элементов HTML отклика с помощью простого, но мощного синтаксиса.
Имеется две формы assert_select
:
assert_select(selector, [equality], [message])
обеспечивает, что условие equality выполняется для выбранных через selector элементов, selector может быть выражением селектора CSS (String) или выражением с заменяемыми значениями.
assert_select(element, selector, [equality], [message])
обеспечивает, что условие equality выполняется для всех элементов, выбранных через selector начиная с element (экземпляра Nokogiri::XML::Node
или Nokogiri::XML::NodeSet
) и его потомков.
Например, можно проверить содержимое в элементе title
отклика с помощью:
assert_select "title", "Welcome to Rails Testing Guide"
Также можно использовать вложенные блоки assert_select
для углубленного исследования.
В следующем примере, внутренний assert_select
для li.menu_item
запускается для полной коллекции элементов, выбранных во внешнем блоке:
assert_select "ul.navigation" do
assert_select "li.menu_item"
end
Коллекция выбранных элементов может быть перебрана, таким образом assert_select
может быть вызван отдельно для каждого элемента.
Например, если отклик содержит два упорядоченных списка, каждый из четырех элементов, тогда оба следующих теста пройдут.
assert_select "ol" do |elements|
elements.each do |element|
assert_select element, "li", 4
end
end
assert_select "ol" do
assert_select "li", 8
end
Это утверждение достаточно мощное. Для более продвинутого использования обратитесь к его документации.
В тестировании вью в основном используется такие утверждения:
Утверждение | Назначение |
---|---|
assert_select_email |
Позволяет сделать утверждение относительно тела e-mail. |
assert_select_encoded |
Позволяет сделать утверждение относительно закодированного HTML. Он делает это декодируя содержимое каждого элемента и затем вызывая блок со всеми декодированными элементами. |
css_select(selector) или css_select(element, selector) |
Возвращают массив всех элементов, выбранных через selector. Во втором варианте сначала проверяется соответствие базовому element, а затем пытается применить соответствие выражению selector на каждом из его детей. Если нет соответствий, оба варианта возвращают пустой массив. |
Вот пример использования assert_select_email
:
assert_select_email do
assert_select "small", "Please click the 'Unsubscribe' link if you want to opt-out."
end
Частичные шаблоны, также часто называемые просто партиалами, представляют собой еще один способ разделить процесс рендеринга на более управляемые блоки. С помощью партиалов вы можете извлекать фрагменты кода из ваших шаблонов в отдельные файлы и повторно использовать их в любых других шаблонах.
Тестирование вью позволяет проверить, что партиалы отображают содержимое так, как вы ожидаете. Тесты партиалов вью располагаются в test/views/
и наследуются от ActionView::TestCase
.
Для рендеринга партиала используйте метод render
так же, как и в обычном шаблоне. Содержимое доступно через локальный для теста метод #rendered
:
class ArticlePartialTest < ActionView::TestCase
test "renders a link to itself" do
article = Article.create! title: "Hello, world"
render "articles/article", article: article
assert_includes rendered, article.title
end
end
В тестах, унаследованных от ActionView::TestCase
также есть доступ к assert_select
и другим дополнительным утверждениям, основанным на вью, предоставленными rails-dom-testing:
test "renders a link to itself" do
article = Article.create! title: "Hello, world"
render "articles/article", article: article
assert_select "a[href=?]", article_url(article), text: article.title
end
Для интеграции с rails-dom-testing, тесты, наследующиеся от ActionView::TestCase
, объявляют метод document_root_element
, который возвращает отрисованное содержимое в виде экземпляра Nokogiri::XML::Node:
test "renders a link to itself" do
article = Article.create! title: "Hello, world"
render "articles/article", article: article
anchor = document_root_element.at("a")
assert_equal article.name, anchor.text
assert_equal article_url(article), anchor["href"]
end
Если ваше приложение использует Ruby >= 3.0, зависит от Nokogiri >= 1.14.0, а также зависит от Minitest >= >5.18.0, document_root_element
поддерживает сопоставление с образцом в Ruby:
test "renders a link to itself" do
article = Article.create! title: "Hello, world"
render "articles/article", article: article
anchor = document_root_element.at("a")
url = article_url(article)
assert_pattern do
anchor => { content: "Hello, world", attributes: [{ name: "href", value: url }] }
end
end
Если вы хотите использовать те же утверждения на основе Capybara, которые применяются в ваших функциональных и системных тестах, вы можете определить базовый класс, наследуемый от ActionView::TestCase
, и преобразовать метод document_root_element
в метод page
:
# test/view_partial_test_case.rb
require "test_helper"
require "capybara/minitest"
class ViewPartialTestCase < ActionView::TestCase
include Capybara::Minitest::Assertions
def page
Capybara.string(rendered)
end
end
# test/views/article_partial_test.rb
require "view_partial_test_case"
class ArticlePartialTest < ViewPartialTestCase
test "renders a link to itself" do
article = Article.create! title: "Hello, world"
render "articles/article", article: article
assert_link article.title, href: article_url(article)
end
end
Начиная с версии Action View 7.1, вспомогательный метод #rendered
возвращает объект, способный анализировать отрендеренное содержимое партиала.
Чтобы преобразовать содержимое в String
, возвращаемое методом #rendered
, в объект, необходимо определить парсер с помощью вызова .register_parser
. Вызов .register_parser :rss
определяет вспомогательный метод #rendered.rss
. Например, чтобы разобрать отрендеренный RSS-контент в объект с помощью #rendered.rss
, зарегистрируйте вызов RSS::Parser.parse
:
register_parser :rss, -> rendered { RSS::Parser.parse(rendered) }
test "renders RSS" do
article = Article.create!(title: "Hello, world")
render formats: :rss, partial: article
assert_equal "Hello, world", rendered.rss.items.last.title
end
По умолчанию ActionView::TestCase
определяет парсер для:
:html
- возвращает экземпляр Nokogiri::XML::Node
:json
- возвращает экземпляр ActiveSupport::HashWithIndifferentAccess
test "renders HTML" do
article = Article.create!(title: "Hello, world")
render partial: "articles/article", locals: { article: article }
assert_pattern { rendered.html.at("main h1") => { content: "Hello, world" } }
end
test "renders JSON" do
article = Article.create!(title: "Hello, world")
render formats: :json, partial: "articles/article", locals: { article: article }
assert_pattern { rendered.json => { title: "Hello, world" } }
end
Хелпер — это всего лишь простой модуль, в котором можно определять методы, которые будут доступны во вью.
Чтобы протестировать хелперы, нужно проверить, что результат метода хелпера соответствует тому, что вы ожидаете. Тесты, относящиеся к хелперам, расположены в директории test/helpers
.
Допустим, у нас имеется следующий хелпер:
module UsersHelper
def link_to_user(user)
link_to "#{user.first_name} #{user.last_name}", user
end
end
Мы можем протестировать результат этого метода хелпера следующим образом:
class UsersHelperTest < ActionView::TestCase
test "should return the user's full name" do
user = users(:david)
assert_dom_equal %{<a href="/user/#{user.id}">David Heinemeier Hansson</a>}, link_to_user(user)
end
end
Более того, так как этот класс теста расширяет ActionView::TestCase
, у вас есть доступ к методам хелпера Rails, таким как link_to
или pluralize
.
Тестирование классов рассыльщика требует несколько специфичных инструментов для тщательного выполнения задания.
Ваши классы рассыльщика - как и любая другая часть вашего приложения на Rails - должны быть протестированы, что они работают так, как ожидается.
Тестировать классы рассыльщика нужно, чтобы быть уверенным в том, что:
Есть два момента в тестировании рассыльщика, юнит-тесты и функциональные тесты. В юнит-тестах обособленно запускается рассыльщик с жестко заданными входящими значениями, и сравнивается результат с известным значением (фикстуры). В функциональных тестах не нужно тестировать мелкие детали, вместо этого мы тестируем, что наши контроллеры и модели правильно используют рассыльщик. Мы тестируем, чтобы подтвердить, что правильный email был послан в правильный момент.
Для того, чтобы протестировать, что ваш рассыльщик работает как надо, можете использовать юнит-тесты для сравнения фактических результатов рассыльщика с предварительно написанными примерами того, что должно быть получено.
Для целей юнит-тестирования рассыльщика фикстуры используются для предоставления примера, как результат должен выглядеть. Так как это примеры электронных писем, а не данные Active Record, как в других фикстурах, они должны храниться в своей поддиректории отдельно от других фикстур. Имя директории в test/fixtures
полностью соответствует имени рассыльщика. Таким образом, для рассыльщика с именем UserMailer
фикстуры должны располагаться в директории test/fixtures/user_mailer
.
Если вы генерируете свой рассыльщик, то генератор не создает незавершенные фикстуры для экшнов рассыльщиков. Вам следует создать эти файлы самостоятельно, как описано выше.
Вот юнит-тест для тестирования рассыльщика с именем UserMailer
, экшн invite
которого используется для рассылки приглашений друзьям. Это адаптированная версия исходного теста, созданного генератором для экшна invite
.
require "test_helper"
class UserMailerTest < ActionMailer::TestCase
test "invite" do
# Создайте email и сохраните его для будущих утверждений
email = UserMailer.create_invite("me@example.com",
"friend@example.com", Time.now)
# Отправить письмо, затем проверить, что оно попало в очередь
assert_emails 1 do
email.deliver_now
end
# Проверить тело отправленного письма, что оно содержит то, что мы ожидаем
assert_equal ["me@example.com"], email.from
assert_equal ["friend@example.com"], email.to
assert_equal "You have been invited by me@example.com", email.subject
assert_equal read_fixture("invite").join, email.body.to_s
end
end
В тесте мы создаем письмо и сохраняем возвращенный объект в переменной email
. Затем мы убеждаемся, что оно было послано (первое утверждение), затем, вот второй порции утверждений, мы убеждаемся, что email
содержит в точности то, что мы ожидаем. Хелпер read_fixture
используется для считывания содержимого из этого файла.
email.body.to_s
существует только когда присутствует одна часть (HTML или text). Если рассыльщик представляет обе, можно протестировать фикстуру для определенной части с помощью email.text_part.body.to_s
или email.html_part.body.to_s
.
Вот содержимое фикстуры invite
:
Hi friend@example.com,
You have been invited.
Cheers!
Сейчас самое время понять немного больше о написании тестов для ваших рассыльщиков. Строчка ActionMailer::Base.delivery_method = :test
в config/environments/test.rb
устанавливает метод доставки в тестовом режиме, таким образом, письмо не будет фактически доставлено (полезно во избежание спама для ваших пользователей во время тестирования), но вместо этого оно будет присоединено к массиву (ActionMailer::Base.deliveries
).
Массив ActionMailer::Base.deliveries
перезагружается автоматически только в тестах ActionMailer::TestCase
и ActionDispatch::IntegrationTest
. Если необходим чистый массив вне этих тестовых случаев, его можно перезагрузить вручную с помощью ActionMailer::Base.deliveries.clear
Можно использовать утверждение assert_enqueued_email_with
, чтобы убедиться, что письмо было поставлено в очередь со всеми ожидаемыми аргументами метода рассыльщика и/или параметризованными параметрами рассыльщика. Это позволяет сопоставить любое письмо, помещенное в очередь с помощью метода deliver_later
.
Как и в качестве базового тестового случая, мы создаем email и сохраняем возвращаемый объект в переменной email
. Следующие примеры включают вариации передачи аргументов и/или параметров.
Этот пример убеждается, что email был помещен в очередь с правильными аргументами:
require "test_helper"
class UserMailerTest < ActionMailer::TestCase
test "invite" do
# Создаем email и сохраняем его для будущих утверждений
email = UserMailer.create_invite("me@example.com", "friend@example.com")
# Тестируем, что email помещен в очередь с правильными аргументами
assert_enqueued_email_with UserMailer, :create_invite, args: ["me@example.com", "friend@example.com"] do
email.deliver_later
end
end
end
Этот пример убеждается, что рассыльщик был помещен в очередь с правильно названными аргументами рассыльщика, передавая хэш аргументов как args
:
require "test_helper"
class UserMailerTest < ActionMailer::TestCase
test "invite" do
# Создаем email и сохраняем его для будущих утверждений
email = UserMailer.create_invite(from: "me@example.com", to: "friend@example.com")
# Тестируем, что email помещен в очередь с правильными аргументами
assert_enqueued_email_with UserMailer, :create_invite, args: [{ from: "me@example.com",
to: "friend@example.com" }] do
email.deliver_later
end
end
end
Этот пример убеждается, что параметризованный рассыльщик был помещен в очередь с правильно названными параметрами и аргументами. Параметры рассыльщика передаются как params
, а аргументы методы рассыльщика как args
:
require "test_helper"
class UserMailerTest < ActionMailer::TestCase
test "invite" do
# Создаем email и сохраняем его для будущих утверждений
email = UserMailer.with(all: "good").create_invite("me@example.com", "friend@example.com")
# Тестируем, что email помещен в очередь с правильными параметрами и аргументами рассыльщика
assert_enqueued_email_with UserMailer, :create_invite, params: { all: "good" },
args: ["me@example.com", "friend@example.com"] do
email.deliver_later
end
end
end
Этот пример показывает альтернативный способ тестирования, что параметризованный рассыльщик был помещен в очередь с правильными параметрами:
require "test_helper"
class UserMailerTest < ActionMailer::TestCase
test "invite" do
# Создаем email и сохраняем его для будущих утверждений
email = UserMailer.with(to: "friend@example.com").create_invite
# Тестируем, что email помещен в очередь с правильными параметрами рассыльщика
assert_enqueued_email_with UserMailer.with(to: "friend@example.com"), :create_invite do
email.deliver_later
end
end
end
Юнит тестирование позволяет протестировать атрибуты письмо, в то время как функциональное и системное тестирование позволяет протестировать, инициируют ли пользовательские взаимодействия доставку письма подходящим образом. Например, можно проверить, что операция по приглашению друзей надлежаще рассылает письма:
# Интеграционный тест
require "test_helper"
class UsersControllerTest < ActionDispatch::IntegrationTest
test "invite friend" do
# Убеждаемся в различии в ActionMailer::Base.deliveries
assert_emails 1 do
post invite_friend_url, params: { email: "friend@example.com" }
end
end
end
# Системный тест
require "test_helper"
class UsersTest < ActionDispatch::SystemTestCase
driven_by :selenium, using: :headless_chrome
test "inviting a friend" do
visit invite_users_url
fill_in "Email", with: "friend@example.com"
assert_emails 1 do
click_on "Invite"
end
end
end
Метод assert_emails
не связан с определенным методом доставки и будет работать с письмами, доставленными либо методом deliver_now
. либо deliver_later
. Если мы хотим явно убедиться, что письмо было помещено в очередь, можно использовать методы assert_enqueued_email_with
(примеры выше) или assert_enqueued_emails
. Подробности можно узнать в документации.
Задания могут тестироваться в изоляции (фокусируясь на поведении задания) и в контексте (фокусируясь на поведении вызывающего кода).
При генерации задания, также будет сгенерирован связанный файл теста в директории test/jobs
.
Вот пример теста с заданием по выставлению счета:
require "test_helper"
class BillingJobTest < ActiveJob::TestCase
test "account is charged" do
perform_enqueued_jobs do
BillingJob.perform_later(account, product)
end
end
end
В среде тестирования задания по умолчанию не выполняются, пока не будет вызван метод perform_enqueued_jobs
. Кроме того, перед каждым тестом все задания в очереди будут очищены, чтобы исключить их взаимовлияние.
Тест использует perform_enqueued_jobs
и perform_later
вместо perform_now
, чтобы в случае, если настроены повторные попытки, ошибки при повторных попытках были перехвачены тестом, а не повторно поставлены в очередь и проигнорированы.
Хорошей практикой бывает убедиться, что задания были поставлены в очередь, например, в экшне контроллера. Модуль ActiveJob::TestHelper
предоставляет несколько методов, которые могут помочь с этим, таких как assert_enqueued_with
.
Вот пример, тестирующий метод модели счета:
require "test_helper"
class AccountTest < ActiveSupport::TestCase
include ActiveJob::TestHelper
test "#charge_for enqueues billing job" do
assert_enqueued_with(job: BillingJob) do
account.charge_for(product)
end
assert_not account.reload.charged_for?(product)
perform_enqueued_jobs
assert account.reload.charged_for?(product)
end
end
Тестирование ситуаций, когда задание должно вызывать исключение, может быть сопряжено с трудностями, особенно при использовании повторных попыток. Вспомогательный метод perform_enqueued_jobs
помечает тест как неудачный, если задание вызывает исключение, поэтому, чтобы тест завершился успешно, когда возникает исключение, необходимо напрямую вызвать метод perform
задания.
require "test_helper"
class BillingJobTest < ActiveJob::TestCase
test "does not charge accounts with insufficient funds" do
assert_raises(InsufficientFundsError) do
BillingJob.new(empty_account, product).perform
end
refute account.reload.charged_for?(product)
end
end
Однако, не рекомендуется использовать такой подход повсеместно, поскольку он обходит некоторые части фреймворка, например, сериализацию аргументов.
Поскольку Action Cable используется на различных уровнях вашего приложения, необходимо протестировать как каналы, сами классы соединений, так и другие сущности, транслирующие правильные сообщения.
По умолчанию, при генерации нового приложения Rails с Action Cable, также генерируется тест для базового класса соединения (ApplicationCable::Connection
) в директории test/channels/application_cable
.
Тесты соединения нацелены на проверку, правильно ли назначаются идентификаторы соединения, или что любые неподходящие соединения отвергаются. Вот пример:
class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
test "connects with params" do
# Симулируем открытие соединения, вызывая метод `connect`
connect params: { user_id: 42 }
# В тестах можно получить объект Connection с помощью `connection`
assert_equal connection.user_id, "42"
end
test "rejects connection without params" do
# Используйте метод соответствия `assert_reject_connection` для проверки,
# что соединение отвергнуто
assert_reject_connection { connect }
end
end
Также можно указать куки запроса, тем же способом, что и в интеграционных тестах:
test "connects with cookies" do
cookies.signed[:user_id] = "42"
connect
assert_equal connection.user_id, "42"
end
Подробности смотрите в документации API по ActionCable::Connection::TestCase
.
По умолчанию, при генерации канала также будет сгенерирован связанный тест в директории test/channels
. Вот пример теста для канала чата:
require "test_helper"
class ChatChannelTest < ActionCable::Channel::TestCase
test "subscribes and stream for room" do
# Симулируется подписка, вызывая `subscribe`
subscribe room: "15"
# В тестах можно получить объект Channel с помощью `subscription`
assert subscription.confirmed?
assert_has_stream "chat_15"
end
end
Этот тест очень простой и только убеждается, что канал подписывает соединение на определенный поток.
Также можно указать идентификаторы лежащего в основе соединения. Вот пример теста канала веб уведомлений:
require "test_helper"
class WebNotificationsChannelTest < ActionCable::Channel::TestCase
test "subscribes and stream for user" do
stub_connection current_user: users(:john)
subscribe
assert_has_stream_for users(:john)
end
end
Подробности смотрите в документации API по ActionCable::Channel::TestCase
.
Action Cable поставляется с рядом пользовательских утверждений, которые можно использовать для уменьшения кода тестов. Полный список доступных утверждений смотрите в документации API для ActionCable::TestHelper
.
Хорошей практикой является убедиться, что внутри других компонент (например, внутри контроллеров) было транслировано правильное сообщение. Именно тут очень полезны пользовательские утверждения, предоставленные Action Cable. К примеру, в модели:
require "test_helper"
class ProductTest < ActionCable::TestCase
test "broadcast status after charge" do
assert_broadcast_on("products:#{product.id}", type: "charged") do
product.charge(account)
end
end
end
Если хотите протестировать трансляции, выполненные с помощью Channel.broadcast_to
, следует использовать Channel.broadcasting_for
для генерации имени потока, лежащего в основе:
# app/jobs/chat_relay_job.rb
class ChatRelayJob < ApplicationJob
def perform(room, message)
ChatChannel.broadcast_to room, text: message
end
end
# test/jobs/chat_relay_job_test.rb
require "test_helper"
class ChatRelayJobTest < ActiveJob::TestCase
include ActionCable::TestHelper
test "broadcast message to room" do
room = rooms(:all)
assert_broadcast_on(ChatChannel.broadcasting_for(room), text: "Hi!") do
ChatRelayJob.perform_now(room, "Hi!")
end
end
end
Обычно приложения не загружают нетерпеливо в средах development
или test
, чтобы ускорить процесс. Но они так делают в среде production
.
Если некоторый файл в проекте не может быть загружен по какой-либо причине, это лучше обнаружить до развертывания на production, так?
Если у вашего проекта настроен CI, нетерпеливая загрузка в CI является простым способом убедиться, что приложение загружается нетерпеливо.
CI обычно устанавливает некоторую переменную окружения, для обозначения, что тестовый случай запущен там. Например, это может быть CI
:
# config/environments/test.rb
config.eager_load = ENV["CI"].present?
Начиная с Rails 7, новые сгенерированные приложения конфигурируются таким способом по умолчанию.
Если в вашем проекте нет непрерывной интеграции, все еще можно нетерпеливо загрузить в тестовом случае, вызвав Rails.application.eager_load!
:
require "test_helper"
class ZeitwerkComplianceTest < ActiveSupport::TestCase
test "eager loads all files without errors" do
assert_nothing_raised { Rails.application.eager_load! }
end
end
require "rails_helper"
RSpec.describe "Zeitwerk compliance" do
it "eager loads all files without errors" do
expect { Rails.application.eager_load! }.not_to raise_error
end
end
Rails предоставляет встроенные вспомогательные методы, позволяющие убеждаться, что ваш зависимый от времени код работает, как ожидается.
Следующий пример использует хелпер travel_to
:
# Допустим, что пользователю можно сделать подарок через месяц после регистрации.
user = User.create(name: "Gaurish", activation_date: Date.new(2004, 10, 24))
assert_not user.applicable_for_gifting?
travel_to Date.new(2004, 11, 24) do
# Внутри блока `travel_to` `Date.current` зафиксирована
assert_equal Date.new(2004, 10, 24), user.activation_date
assert user.applicable_for_gifting?
end
# Изменение было видно только внутри блока `travel_to`.
assert_equal Date.new(2004, 10, 24), user.activation_date
Обратитесь к документации API ActiveSupport::Testing::TimeHelpers
за подробностями о доступных хелперах времени.