Я работал над приложением и должен был протестировать модели, что является довольно обычной практикой. Мне пришлось повторять проверочные тесты для каждого поля и каждой модели, что приводило к большому количеству дублированного тестового кода. Итак, я собираюсь поделиться своим решением этой проблемы, которое поможет нам избежать повторения подобных проверочных тестов для каждой модели.
Я использую Ruby 2.3.0, Rails 4.2.4. И Минитест, и FactoryGirl в моем тестовом наборе. У меня был этот тест, написанный для модели сайта, такой как:
class SiteTest < ActiveSupport::TestCase
def test_should_require_customer_name
site = Site.new
refute site.valid?
refute site.save
assert_operator site.errors.count, :>, 0
assert site.errors.messages.include?(:customer_name)
assert site.errors.messages[:customer_name].include?("can't be blank")
end
def test_should_require_customer_email
site = Site.new
refute site.valid?
refute site.save
assert_operator site.errors.count, :>, 0
assert site.errors.messages.include?(:customer_email)
assert site.errors.messages[:customer_email].include?("can't be blank")
end
def test_should_require_host
site = Site.new
refute site.valid?
refute site.save
assert_operator site.errors.count, :>, 0
assert site.errors.messages.include?(:host)
assert site.errors.messages[:host].include?("can't be blank")
end
def test_should_require_host_to_be_unique
theme = FactoryGirl.create(:theme)
Site.skip_callback(:create, :after, :setup_components)
existing_site = FactoryGirl.create(:site, theme: theme)
Site.after_create(:setup_components)
site = Site.new(host: existing_site.host)
refute site.valid?
refute site.save
assert_operator site.errors.count, :>, 0
assert site.errors.messages.include?(:host)
assert site.errors.messages[:host].include?("has already been taken")
end
def test_should_require_theme
site = Site.new
refute site.valid?
refute site.save
assert_operator site.errors.count, :>, 0
assert site.errors.messages.include?(:theme)
assert site.errors.messages[:theme].include?("can't be blank")
end
def test_should_require_user
site = Site.new
refute site.valid?
refute site.save
assert_operator site.errors.count, :>, 0
assert site.errors.messages.include?(:user)
assert site.errors.messages[:user].include?("can't be blank")
end
end
В результате чего:
$ ruby -Ilib:test test/models/site_test.rb
SiteTest
test_should_require_user PASS (0.45s)
test_should_require_host PASS (0.01s)
test_should_require_customer_email PASS (0.01s)
test_should_require_host_to_be_unique PASS (0.09s)
test_should_require_theme PASS (0.01s)
test_should_require_customer_name PASS (0.01s)
Finished in 0.58104s
6 tests, 30 assertions, 0 failures, 0 errors, 0 skips
Слишком много кода для простой функции для тестирования, а? Правильно. Представьте, нужно ли вам повторять это во всех моделях для всех полей, которые вы хотите проверить.
Я не знаю, есть ли какие-нибудь драгоценные камни, чтобы высушить эту вещь или нет, так как я не удосужился найти существующие решения этой проблемы. Вместо этого я начал использовать доброту Руби, чтобы найти собственное решение. И вот что я придумал:
module TestModelValidations
def self.included(klass)
klass.class_eval do
def self.test_validates_presence_of(*args)
args.each do |field_name|
define_method("test_should_require_#{field_name.to_s}") do
model = self.class.model_klass.new
assert_validation(model, field_name, "can't be blank")
end
end
end
def self.test_validates_uniqueness_of(existing_model, *args)
args.each do |field_name|
define_method("test_should_require_#{field_name.to_s}_to_be_unique") do
params_hash = {}
params_hash[field_name] = existing_model.send(field_name)
model = self.class.model_klass.new(params_hash)
assert_validation(model, field_name, "has already been taken")
end
end
end
private
def assert_validation(model, field_name, error_message)
refute model.valid?
refute model.save
assert_operator model.errors.count, :>, 0
assert model.errors.messages.include?(field_name)
assert model.errors.messages[field_name].include?(error_message)
end
end
end
def model_klass
self.class.name.underscore.split("_test").first.camelize.constantize
end
end
Вы можете поместить этот файл в test / support / и запросить все файлы в каталоге поддержки до запуска тестов, добавив эту строку в test / test_helper.rb :
Dir[Rails.root.join(‘test’, ‘support’, ‘*.rb’)].each { |f| require f }
Вам просто нужно включить этот модуль в каждый тестовый файл, чтобы использовать эту СУХУЮ версию для проверочных тестов. Кроме того, вы можете пойти дальше и посмотреть этот блок в test / test_helper.rb :
class ActiveSupport::TestCase
ActiveRecord::Migration.check_pending!
end
Это открывает класс ActiveSupport :: TestCase и расширяет его, это класс, от которого наследуется каждый класс тестирования модели. Добавьте эту строку туда:
include TestModelValidations
Теперь блок должен выглядеть примерно так:
class ActiveSupport::TestCase
ActiveRecord::Migration.check_pending!
include TestModelValidations
end
Теперь мы готовы продемонстрировать нашу сухую версию тестов, которые мы видели ранее:
class SiteTest < ActiveSupport::TestCase
test_validates_presence_of :customer_email, :customer_name, :host, :theme, :user
test_validates_uniqueness_of FactoryGirl.create(:site), :host
end
Да это оно :). Это все, что нужно для проверки правильности моделей. Теперь у нас есть макросы классов для наших тестовых классов, как и в наших реальных моделях для объявления проверок. Это почти половина кода, который мы ранее должны были написать, чтобы добиться того же самого. Представьте себе использование этого во всех ваших модельных тестах, которые, надеюсь, сэкономят вам сотни строк кода и усилия по копированию / вставке. Давайте снова запустим тест, чтобы убедиться, что все работает так, как было раньше:
$ ruby -Ilib:test test/models/site_test.rb
SiteTest
test_should_require_customer_name PASS (0.34s)
test_should_require_user PASS (0.01s)
test_should_require_host PASS (0.01s)
test_should_require_host_to_be_unique PASS (0.01s)
test_should_require_theme PASS (0.01s)
test_should_require_customer_email PASS (0.01s)
Finished in 0.39483s
6 tests, 30 assertions, 0 failures, 0 errors, 0 skips
Boom! Все работает как положено, но гораздо чище и удобнее. Это облегчает жизнь для проверки модели.
Я не знаю, как глубоко я должен объяснить объяснение своей реализации и скольким из вас будет интересно узнать, как эта реализация работает. Так что я собираюсь обернуть это здесь, и если возникнут какие-либо вопросы, я буду рад ответить на них в разделе комментариев или любом другом средстве связи.
Одна вещь, которую я чувствую, выиграет от небольшого объяснения, это макрос / строка:
test_validates_uniqueness_of FactoryGirl.create(:site), :host
Этот макрос проверяет уникальность модельного поля. Первый аргумент — это постоянный экземпляр модели, из которой будет продублировано поле. После этого первого аргумента вы можете указать любое количество аргументов в качестве полей, по которым вы хотите проверить уникальность.
Вот и все. Ждем ваших комментариев и вопросов. Я действительно надеюсь, что это поможет вам в некотором роде.