Tip nhanh: Không lặp code khi viết Test Validations Model
Tôi đã làm việc trên các ứng dụng và cần viết test cho các model. Tôi đã phải lặp lại các bài test validation cho mỗi trường, mỗi model dẫn đến việc code bị trùng lặp. Vì vậy, tôi sẽ chia sẻ giải pháp cho vấn đề này, sẽ giúp chúng ta tránh lặp lại test cho các validation tương tự nhau trong mỗi ...
Tôi đã làm việc trên các ứng dụng và cần viết test cho các model. Tôi đã phải lặp lại các bài test validation cho mỗi trường, mỗi model dẫn đến việc code bị trùng lặp. Vì vậy, tôi sẽ chia sẻ giải pháp cho vấn đề này, sẽ giúp chúng ta tránh lặp lại test cho các validation tương tự nhau trong mỗi model.
Tôi dùng Ruby 2.3.0, Rails 4.2.4. , Minitest và FactoryGirl trong vài viết này. Tôi đã viết một đoạn test cho model Site như sau:
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
Kết quả test:
$ 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
Quá nhiều code giống nhau cho tính năng test đơn giản. Hãy tưởng tượng nếu bạn phải lặp lại nó trong tất cả các model cho tất cả các trường bạn muốn validation.
Tôi không biết liệu có Gem nào DRY điều này hay không, vì tôi không tìm giải pháp cho vấn đề này. Thay vào đó, tôi bắt đầu sử dụng Ruby để khắc phục vấn đề này. Và đây là những gì tôi đã đưa ra:
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
Bạn có thể đặt file này trong test/support/ và require tất cả file trong thư mục support.Trước khi bắt đầu test thêm dòng này vào file test/test_helper.rb:
Dir[Rails.root.join(‘test’, ‘support’, ‘*.rb’)].each { |f| require f }
Bạn chỉ cần include module này trong mỗi file test để sử dụng phiên bản DRYed up của các bài test validation:
class ActiveSupport::TestCase ActiveRecord::Migration.check_pending! end
Mở class ActiveSupport :: TestCase và extends nó, đây là class mà mỗi class test model kế thừa. Thêm dòng này vào đó:
include TestModelValidations
Bây giờ class trông như sau:
class ActiveSupport::TestCase ActiveRecord::Migration.check_pending! include TestModelValidations end
Bây giờ chúng tôi đã sẵn sàng để trình bày phiên bản DRIed up:
class SiteTest < ActiveSupport::TestCase test_validates_presence_of :customer_email, :customer_name, :host, :theme, :user test_validates_uniqueness_of FactoryGirl.create(:site), :host end
Vâng, chính là nó