Usando o shoulda para refatorar testes de rspec em modelos Rails
Depois de aprender sobredevedores respondendooutra pergunta do StackOverflow em testes de acessibilidade de atributos (e pensando que eles eram bem legais), eu decidi tentar refatorar os testes de modelo que fiz emO tutorial do Rails em uma tentativa de torná-los ainda mais concisos e completos. Eu fiz isso graças a alguma inspiração da documentação para módulosShoulda::Matchers::ActiveRecord
eShoulda::Matchers::ActiveModel
, assim comoesta resposta StackOverflow na estruturação deve-se testar em modelos. No entanto, ainda há algumas coisas que não tenho certeza, e estou me perguntando como esses testes poderiam ser melhorados.
Usarei a especificação User no Tutorial Rails como meu exemplo, pois é o mais detalhado e abrange várias áreas que poderiam ser melhoradas. O exemplo de código a seguir foi alterado do originaluser_spec.rb, e substitui o código até que odescribe "micropost associations"
linha. Os testes de especificações contra ouser.rb modelo, e sua fábrica é definida emfactories.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[[email protected] [email protected] [email protected] [email protected]]
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
Algumas perguntas específicas sobre esses testes:
Vale a pena testar o esquema do banco de dados? Um comentário noResposta do StackOverflow mencionada acima diz "Só testo coisas relacionadas a comportamento e não considero a presença de uma coluna ou um comportamento de índice. As colunas de banco de dados não desaparecem a menos que alguém as remova intencionalmente, mas você pode proteger contra isso com revisões de código e confiança ", com a qual eu concordo, mas existe algum motivo válido pelo qual a estrutura do esquema de banco de dados seria testada, e assim justificando a existência doShoulda::Matchers::ActiveRecord
módulo? Talvez apenas os índices importantes valem a pena testar ...?Faça oshould have_many
testes sob"associations"
substituir seus correspondentesshould respond_to
testes sob"model attributes"
? Eu não posso dizer se oshould have_many
teste apenas procura o relevantehas_many
declaração em um arquivo de modelo ou realmente executa a mesma funçãoshould respond_to
.Você tem outros comentários / sugestões para tornar esses testes mais concisos / legíveis / detalhados, tanto em conteúdo quanto em estrutura?