使用 shoulda 重构 Rails 模型上的 rspec 测试 [英] Using shoulda to refactor rspec tests on Rails models
问题描述
通过回答shoulda-matchers后stackoverflow.com/a/11849608/567863">另一个关于属性可访问性测试的 StackOverflow 问题(并认为它们非常棒),我决定尝试重构我在 Rails 教程 试图使它们更加简洁和彻底.我这样做要感谢模块文档的一些灵感 Shoulda::Matchers::ActiveRecord
和 应该::Matchers::ActiveModel
,以及这个 StackOverflow 答案关于在模型中构建 shoulda 测试.但是,仍有一些我不确定的事情,我想知道如何使这些测试变得更好.
After learning about shoulda-matchers by answering another StackOverflow question on attribute accessibility tests (and thinking they were pretty awesome), I decided to try refactoring the model tests I did in The Rails Tutorial in an attempt to make them even more concise and thorough. I did this thanks to some inspiration from the documentation for modules Shoulda::Matchers::ActiveRecord
and Shoulda::Matchers::ActiveModel
, as well as this StackOverflow answer on structuring shoulda tests in models. However, there's still a few things I am not sure about, and I am wondering how these tests could be made better.
我将使用 Rails 教程中的用户规范作为我的示例,因为它是最详细的,涵盖了许多可以改进的领域.以下代码示例已从原始 user_spec 更改.rb,并向下替换代码,直到 describe "micropost associations"
行.针对 user.rb 模型,其工厂定义在 工厂.rb.
I will use the User spec in the Rails Tutorial as my example as it is the most detailed, and covers lots of areas that could be improved. The following code example has been changed from the original user_spec.rb, and replaces the code down until the describe "micropost associations"
line. The spec tests against the user.rb model, and its factory is defined in factories.rb.
spec/models/user_spec.rb
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# name :string(255)
# email :string(255)
# created_at :datetime not null
# updated_at :datetime not null
# password_digest :string(255)
# remember_token :string(255)
# admin :boolean default(FALSE)
#
# Indexes
#
# index_users_on_email (email) UNIQUE
# index_users_on_remember_token (remember_token)
#
require 'spec_helper'
describe User do
let(:user) { FactoryGirl.create(:user) }
subject { user }
describe "database schema" do
it { should have_db_column(:id).of_type(:integer)
.with_options(null: false) }
it { should have_db_column(:name).of_type(:string) }
it { should have_db_column(:email).of_type(:string) }
it { should have_db_column(:created_at).of_type(:datetime)
.with_options(null: false) }
it { should have_db_column(:updated_at).of_type(:datetime)
.with_options(null: false) }
it { should have_db_column(:password_digest).of_type(:string) }
it { should have_db_column(:remember_token).of_type(:string) }
it { should have_db_column(:admin).of_type(:boolean)
.with_options(default: false) }
it { should have_db_index(:email).unique(true) }
it { should have_db_index(:remember_token) }
end
describe "associations" do
it { should have_many(:microposts).dependent(:destroy) }
it { should have_many(:relationships).dependent(:destroy) }
it { should have_many(:followed_users).through(:relationships) }
it { should have_many(:reverse_relationships).class_name("Relationship")
.dependent(:destroy) }
it { should have_many(:followers).through(:reverse_relationships) }
end
describe "model attributes" do
it { should respond_to(:name) }
it { should respond_to(:email) }
it { should respond_to(:password_digest) }
it { should respond_to(:remember_token) }
it { should respond_to(:admin) }
it { should respond_to(:microposts) }
it { should respond_to(:relationships) }
it { should respond_to(:followed_users) }
it { should respond_to(:reverse_relationships) }
it { should respond_to(:followers) }
end
describe "virtual attributes and methods from has_secure_password" do
it { should respond_to(:password) }
it { should respond_to(:password_confirmation) }
it { should respond_to(:authenticate) }
end
describe "accessible attributes" do
it { should_not allow_mass_assignment_of(:password_digest) }
it { should_not allow_mass_assignment_of(:remember_token) }
it { should_not allow_mass_assignment_of(:admin) }
end
describe "instance methods" do
it { should respond_to(:feed) }
it { should respond_to(:following?) }
it { should respond_to(:follow!) }
it { should respond_to(:unfollow!) }
end
describe "initial state" do
it { should be_valid }
it { should_not be_admin }
its(:remember_token) { should_not be_blank }
its(:email) { should_not =~ /\p{Upper}/ }
end
describe "validations" do
context "for name" do
it { should validate_presence_of(:name) }
it { should_not allow_value(" ").for(:name) }
it { should ensure_length_of(:name).is_at_most(50) }
end
context "for email" do
it { should validate_presence_of(:email) }
it { should_not allow_value(" ").for(:email) }
it { should validate_uniqueness_of(:email).case_insensitive }
context "when email format is invalid" do
addresses = %w[user@foo,com user_at_foo.org example.user@foo.]
addresses.each do |invalid_address|
it { should_not allow_value(invalid_address).for(:email) }
end
end
context "when email format is valid" do
addresses = %w[user@foo.COM A_US-ER@f.b.org frst.lst@foo.jp a+b@baz.cn]
addresses.each do |valid_address|
it { should allow_value(valid_address).for(:email) }
end
end
end
context "for password" do
it { should ensure_length_of(:password).is_at_least(6) }
it { should_not allow_value(" ").for(:password) }
context "when password doesn't match confirmation" do
it { should_not allow_value("mismatch").for(:password) }
end
end
context "for password_confirmation" do
it { should validate_presence_of(:password_confirmation) }
end
end
# ...
end
关于这些测试的一些具体问题:
Some specific questions about these tests:
- 是否值得测试数据库架构?上面提到的 StackOverflow 答案 中的评论说:我只测试与行为相关的东西,我不考虑列或索引行为的存在.除非有人故意删除它们,否则数据库列不会消失,但您可以通过代码审查和信任来防止这种情况发生",我同意这一点,但是有什么正当理由说明将测试数据库模式,从而证明
Shoulda::Matchers::ActiveRecord
模块的存在是合理的?也许只是重要的指标值得测试......? "associations"
下的should have_many
测试是否替换"model attributes"
下相应的should respond_to
测试>?我不知道should have_many
测试是否只是在模型文件中查找相关的has_many
声明,还是实际执行与should respond_to
.- 您是否还有其他意见/建议使这些测试在内容和结构上更加简洁/可读/彻底?
- Is it worth testing the database schema at all? A comment in the StackOverflow answer mentioned above says "I only test things that are related to behavior and I don't consider the presence of a column or an index behavior. Database columns don't just disappear unless someone intentionally removes them, but you can protect against that with code reviews and trust", which I agree with, but is there any valid reason why the structure of the database schema would be tested for, and thus justifying the existence of the
Shoulda::Matchers::ActiveRecord
module? Perhaps just the important indexes are worth testing...? - Do the
should have_many
tests under"associations"
replace their correspondingshould respond_to
tests under"model attributes"
? I can't tell whether theshould have_many
test just looks for the relevanthas_many
declaration in a model file or actually performs the same function asshould respond_to
. - Do you have any other comments/suggestions to make these tests more concise/readable/thorough, both in content and structure?
推荐答案
1) Shoulda::Matchers::ActiveRecord 模块中包含的不仅仅是列和索引匹配器.我会在 包含的类中挖掘看看你能找到什么.这就是 have_many
、belong_to
等的来源.不过,作为记录,我认为其中的大部分内容都没有什么价值.
1) The Shoulda::Matchers::ActiveRecord module has a lot more in it than just column and index matchers. I would dig around in the included classes a little and see what you can find. This is where the have_many
, belong_to
etc come from. For the record though, I see little value in most of what is in there.
2) 是的,诸如 have_many
之类的宏比模型是否响应方法要测试得多.来自 源代码,你可以准确地看到它正在测试什么:
2) Yes, macros such as have_many
test a lot more than whether or not a model responds to a method. From the source code, you can see exactly what it is testing:
def matches?(subject)
@subject = subject
association_exists? &&
macro_correct? &&
foreign_key_exists? &&
through_association_valid? &&
dependent_correct? &&
class_name_correct? &&
order_correct? &&
conditions_correct? &&
join_table_exists? &&
validate_correct?
end
3) 使测试更具可读性和/或简洁性绝对是一个需要回答的主观问题.每个人都会根据他们的背景和经验给你不同的答案.我个人会摆脱所有 respond_to
测试并用有价值的测试替换它们.当有人查看您的测试时,他们应该能够理解该类的公共 API.当我看到你的对象对诸如跟随?"之类的东西做出响应时,我可以做出假设,但并不真正知道这意味着什么.它需要争论吗?它是否返回布尔值?是物体跟随某物还是某物跟随物体?
3) Making the tests more readable and/or concise is definitely a subjective question to answer. Everyone will give you a different answer to this depending on their background and experience. I would personally get rid of all of the respond_to
tests and replace them with tests that have value. When someone looks at your tests, they should be able to understand the public API for that class. When I see that your objects respond_to something like "following?", I can make assumptions, but don't really know what it means. Does it take an argument? Does it return a boolean value? Is the object following something or is something following the object?
这篇关于使用 shoulda 重构 Rails 模型上的 rspec 测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!