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?

questionAnswers(5)

yourAnswerToTheQuestion