<% content_for :menu do %>
  • Introducción a las directrices de rspec
  • » Cómo describir tus métodos
  • » Usa contextos
  • » Mantén la descripción corta
  • » Prueba de expectativa única
  • » Prueba todos los casos posibles
  • » Sintaxis Expect vs Should
  • » Usa subject
  • » Usa let y let!
  • » Mock o no mock
  • » Crea sólo los datos que necesites
  • » Usa factories y no fixtures
  • » Matchers fáciles de leer
  • » Ejemplos compartidos
  • » Prueba lo que ves
  • » No uses should (debe)
  • » Pruebas automáticas con guard
  • » Pruebas más rápidas con Spork
  • » Stubbing de peticiones HTTP
  • » Formateador útil
  • Libros
  • Presentaciones
  • Recursos en la red
  • Screencasts
  • Bibliotecas
  • Guía de Estilo
  • Mejorando Better Specs
  • Créditos
  • Ayúdanos
  • <% end %>

    RSpec es una gran herramienta en el desarrollo guiado por comportamiento (BDD) en el proceso de escribir especificaciones legibles para humanos que dirijan y validen el desarrollo de la aplicación.

    En la web hay muchos recursos que dan un panorama completo de lo qué se puede hacer con RSpec. Pero hay muy pocos recursos dedicados a cómo crear un correcto conjunto de pruebas con RSpec.

    Better Specs trata de llenar este hueco recopilando muchas de las "mejores prácticas" que otros desarrolladores aprenden con años de experiencia.

    Cómo describir tus métodos

    Ser claro sobre qué método se está describiendo. Por ejemplo, utiliza la convención de la documentación de Ruby de . (o ::) cuando se refiere a nombres de métodos de clase y # cuando se refiere a un nombre de método de instancia.

    Incorrecto

    describe 'the authenticate method for User' do
    describe 'if the user is an admin' do
    

    Correcto

    describe '.authenticate' do
    describe '#admin?' do
    

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | how to describe your methods", url: "http://betterspecs.org/#describe" %>

    Utiliza contextos

    Los contextos son una gran forma de hacer que las pruebas sean claras y estén bien organizadas. A largo plazo, esta práctica mantendrá las pruebas fáciles de leer.

    Incorrecto

    it 'has 200 status code if logged in' do
      response.should respond_with 200
    end
    it 'has 401 status code if not logged in' do
      response.should respond_with 401
    end
    

    Correcto

    context 'when logged in' do
      it { is_expected.to respond_with 200 }
    end
    context 'when logged out' do
      it { is_expected.to respond_with 401 }
    end
    

    Al describir un contexto, comienza su descripción con "cuando"(when) o "con"(with).

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | use contexts", url: "http://betterspecs.org/#contexts" %>

    Mantén la descripción corta

    Una descripción no debe ser mayor a 40 caracteres. Si esto pasa se deben partir usando un contexto.

    Incorrecto

    it 'has 422 status code if an unexpected params will be added' do
    

    Correcto

    context 'when not valid' do
      it { should respond_with 422 }
    end
    

    En el ejemplo anterior, se eliminó la descripción relacionada al código de estatus, el cual ha sido reemplazado por la expectativa. it { should respond_with 422 }. Si se ejecuta esta prueba tecleando rspec filename se obtendrá una salida legible.

    Salida formateada

    when not valid
      it should respond with 422
    

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | keep your description short", url: "http://betterspecs.org/#short" %>

    Prueba de expectativa única

    El tip de 'expectativa única' es expresado más generalmente como 'cada prueba debe hacer sólo una aserción'. Esto ayuda a encontrar posibles errores, a ir directamente a la prueba fallida, y para hacer el código legible.

    En expectativas (specs) unitarias aisladas, se desea que cada ejemplo especifique un (y sólo un) comportamiento. Múltiples expectativas en el mismo ejemplo son una señal de que se podrian estar especificando múltiples comportamientos.

    De cualquier manera, en pruebas que no están aisladas (p.e. las que se integran con una BD, un webservice externo, o pruebas de extremo a extremo), el rendimiento se afectará gravemente por hacer la misma configuración una y otra vez, sólo por fijar una expectativa diferente en cada prueba. En este tipo de pruebas más lentas, creo que está bien especificar más de un comportamiento aislado.

    Correcto (aislado)

    it { should respond_with_content_type(:json) }
    it { should assign_to(:resource) }
    

    Correcto (no aislado)

    it 'creates a resource' do
      response.should respond_with_content_type(:json)
      response.should assign_to(:resource)
    end
    
    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | single expectation test", url: "http://betterspecs.org/#single" %>

    Prueba todos los casos posibles

    Probar es una buena práctica, pero si no se prueban los casos extremos, no será útil. Prueba casos válidos, extremos e inválidos. Por ejemplo, considera la siguiente accción.

    Acción de destruir

    before_action :find_owned_resources
    before_action :find_resource
    
    def destroy
      render 'show'
      @consumption.destroy
    end

    El error que normalmente veo radica en probar solamente que el recurso ha sido borrado. Pero hay al menos dos casos extremos: Cuando el recurso no se encuentra y cuando no nos pertenece. Como regla de oro, hay que pensar en todas las posibles entradas y probarlas.

    Incorrecto

    it 'shows the resource'
    

    Correcto

    describe '#destroy' do
    
      context 'when resource is found' do
        it 'responds with 200'
        it 'shows the resource'
      end
    
      context 'when resource is not found' do
        it 'responds with 404'
      end
    
      context 'when resource is not owned' do
        it 'responds with 404'
      end
    end
    

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | test all possible cases", url: "http://betterspecs.org/#all" %>

    Sintaxis Expect vs Should

    En proyectos nuevos usar la sintaxis expect.

    incorrecto

    it 'creates a resource' do
      response.should respond_with_content_type(:json)
    end

    correcto

    it 'creates a resource' do
      expect(response).to respond_with_content_type(:json)
    end

    Configura RSpec para que sólo acepte la nueva sintaxis en los proyectos nuevos, para evitar tener las 2 sintaxis por todas partes.

    correcto

    # spec_helper.rb
    RSpec.configure do |config|
      # ...
      config.expect_with :rspec do |c|
        c.syntax = :expect
      end
    end

    Más información sobre la nueva sintaxis de RSpec se puede encontrar aquí y aquí.

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | Expect vs Should syntax", url: "http://betterspecs.org/#expect" %>

    Usa subject

    Si se tienen muchas pruebas relacionadas al mismo objeto, usa subject{} para no repetir código.

    Incorrecto

    it { assigns('message').should match /it was born in Belville/ }
    

    Correcto

    subject { assigns('message') }
    it { should match /it was born in Billville/ }
    

    RSpec tiene la capacidad para usar un sujeto (subject) con nombre.

    Correcto

    subject(:hero) { Hero.first }
    it "carries a sword" do
      hero.equipment.should include "sword"
    end
    

    Aprender más sobre rspec subject.

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | use subject", url: "http://betterspecs.org/#subject" %>

    Utiliza let y let!

    Cuando se tiene que asignar una variable, en lugar de usar un bloque before para crear una variable de instancia, se puede usar let. Con let la variable se carga sólo cuando es usada la primera vez en la prueba y se mantiene en caché hasta que la prueba específica termina. Una descripción muy buena y detallada de let puede ser, se puede encontrar en este enlace stackoverflow answer.

    Incorrecto

    describe '#type_id' do
      before { @resource = FactoryBot.create :device }
      before { @type     = Type.find @resource.type_id }
    
      it 'sets the type_id field' do
        @resource.type_id.should == @type.id
      end
    end
    

    Correcto

    describe '#type_id' do
      let(:resource) { FactoryBot.create :device }
      let(:type)     { Type.find resource.type_id }
    
      it 'sets the type_id field' do
        resource.type_id.should == type.id
      end
    end
    

    Usa let para inicializar acciones que son "lazy loaded" para probar tus expectativas (specs).

    Correcto

    context 'when updates a not existing property value' do
      let(:properties) { { id: Settings.resource_id, value: 'on' } }
    
      def update
        resource.properties = properties
      end
    
      it 'raises a not found error' do
        expect { update }.to raise_error Mongoid::Errors::DocumentNotFound
      end
    end
    

    Utiliza let! si deseas definir la variable cuando el bloque es definido. Esto puede ser útil para poblar tu base de datos para probar consultas o "scopes".

    Un ejemplo de qué es realmente let.

    Correcto

    # this:
    let(:foo) { Foo.new }
    
    # is very nearly equivalent to this:
    def foo
      @foo ||= Foo.new
    end
    

    Aprender más sobre rspec let.

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | use let and let!", url: "http://betterspecs.org/#let" %>

    Mock o no mock

    Hay un debate actualmente: No (ab)usar mocks y probar comportamiento real cuando sea posible. Probar casos reales es útil cuando se actualiza el flujo de la aplicación.

    Correcto

    # simulate a not found resource
    context "when not found" do
      before { allow(Resource).to receive(:where).with(created_from: params[:id]).and_return(false) }
      it { should respond_with 404 }
    end
    

    'Mocking' hace las especificaciones más rápidas pero son difíciles de utilizar. Pero es necesario entenderlo bien para usarlo bien. Leer más acerca de esto.

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | mock or not to mock", url: "http://betterspecs.org/#mock" %>

    Crea sólo los datos que necesites

    Si has trabajado en un proyecto de mediano tamaño (aunque también en algunos pequeños), las suites de pruebas pueden ser pesadas de ejecutarse. Para resolver este problema, es importante no cargar más datos de los necesarios. Incluso si piensas que necesitas docenas de registros, probablemente estés equivocado.

    Correcto

    describe "User"
      describe ".top" do
        before { FactoryBot.create_list(:user, 3) }
        it { User.top(2).should have(2).item }
      end
    end
    

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | create only tha data you need", url: "http://betterspecs.org/#data" %>

    Utiliza factories y no fixtures

    Este es un tema antiguo, pero es bueno recordarlo. No uses fixtures porque son difíciles de controlar, en su lugar utiliza factories. Utilizar factories reduce la verbosidad durante la creación de nuevos datos.

    Incorrecto

    user = User.create(
      name: 'Genoveffa',
      surname: 'Piccolina',
      city: 'Billyville',
      birth: '17 Agoust 1982',
      active: true
    )
    

    Correcto

    user = FactoryBot.create :user
    

    Una nota importante. Cuando hablamos de pruebas unitarias, la mejor práctica sería no utilizar ni fixtures o factories. Es mejor poner la mayoría de la lógica de negocio en bibliotecas que puedan ser probadas sin necesidad de configuraciones lentas y complejas ya sea con factories o fixtures. Leer más en este artículo

    Aprender más sobre Factory Bot.

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | use factories and not fixtures", url: "http://betterspecs.org/#factories" %>

    Matchers fáciles de leer

    Usa matchers legibles y revisa los matchers de rspec disponibles.

    Incorrecto

    lambda { model.save! }.should raise_error Mongoid::Errors::DocumentNotFound
    

    Correcto

    expect { model.save! }.to raise_error Mongoid::Errors::DocumentNotFound
    

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | easy to read matcher", url: "http://betterspecs.org/#matchers" %>

    Ejemplos compartidos

    Crear pruebas es grandioso y brinda más seguridad en el día a día. Pero al final se verá código duplicado emergiendo de todas partes. Utiliza ejemplos compartidos para limpiar la suite de pruebas.

    Incorrecto

    describe 'GET /devices' do
      let!(:resource) { FactoryBot.create :device, created_from: user.id }
      let(:uri) { '/devices' }
    
      context 'when shows all resources' do
        let!(:not_owned) { FactoryBot.create factory }
    
        it 'shows all owned resources' do
          page.driver.get uri
          page.status_code.should == 200
          contains_owned_resource resource
          does_not_contain_resource not_owned
        end
      end
    
      describe '?start=:uri' do
        it 'shows the next page' do
          page.driver.get uri, start: resource.uri
          page.status_code.should == 200
          contains_resource resources.first
          page.should_not have_content resource.id.to_s
        end
      end
    end
    

    Correcto

    describe 'GET /devices' do
    
      let!(:resource) { FactoryBot.create :device, created_from: user.id }
      let(:uri)       { '/devices' }
    
      it_behaves_like 'a listable resource'
      it_behaves_like 'a paginable resource'
      it_behaves_like 'a searchable resource'
      it_behaves_like 'a filterable list'
    end
    

    En nuestra experiencia, los ejemplos compartidos son usados principalmente para controladores. Como los modelos son bastante diferentes entre sí, (generalmente) no comparten mucha lógica

    Aprender más sobre ejemplos compartidos de rspec.

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | shared examples", url: "http://betterspecs.org/#sharedexamples" %>

    Prueba lo que ves

    Prueba a profundidad tus modelos y el comportamiento de tu aplicación (integration tests). No agregues complejidad inútil probando controladores.

    Cuando inicié probando mis aplicaciones, probaba controladores. Ahora ya no. Ahora, sólo creo pruebas de integración usando RSpec y Capybara. ¿Por qué? Porque tengo la certeza que se debe probar lo que se ve y porque probar controladores es un paso extra que no se necesita. La mayoría de las pruebas van dentro de los modelos y las pruebas de integración pueden agruparse fácilmente en ejemplos compartidos, formando un conjunto de pruebas limpio y legible.

    Éste es un debate abierto en la comunidad Ruby y ambos puntos de vista tienen buenos argumentos para apoyar su idea. Las personas que apoyan la necesidad de probar controladores te dirán que tus pruebas de integración no cubren todos los casos de uso y que son lentas.

    Ambos argumentos son incorrectos. Puedes fácilmente cubrir todos los casos de uso (¿por qué no?) y ejecutar un único archivo de especificaciones usando herramientas automatizadas como Guard. De esta forma se ejecutarán sólo las especificaciones que necesites probar rápidamente sin parar tu flujo.

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | test what you see", url: "http://betterspecs.org/#integration" %>

    No uses should (debe)

    No uses la palabra should (debe) cuando describas tus pruebas. Usa la tercera persona en tiempo presente. Mejor aún, comienza a usar la nueva sintaxis con expect .

    Incorrecto

    it 'should not change timings' do
      consumption.occur_at.should == valid.occur_at
    end
    

    Correcto

    it 'does not change timings' do
      consumption.occur_at.should == valid.occur_at
    end
    

    Ver la gema should_not para una forma de forzar esto en RSpec y la gema should_clean para una forma de limpiar ejemplos RSpec que inicien con 'should'.

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | don't use should", url: "http://betterspecs.org/#should" %>

    Pruebas automáticas con guard

    Ejecutar toda la suite de pruebas cada vez que cambia la aplicación puede ser pesado. Toma mucho tiempo y puede romper el flujo. Con Guard se puede automatizar la suite de pruebas ejecutando sólo las pruebas relacionadas con la especificación (spec) modificada, modelo, controlador o archivo sobre el que estás trabajando.

    Correcto

    bundle exec guard
    
    Aquí se puede ver un ejemplo del archivo Guardfile con algunas reglas básicas de recarga.

    Correcto

    guard 'rspec', cli: '--drb --format Fuubar --color', version: 2 do
      # run every updated spec file
      watch(%r{^spec/.+_spec\.rb$})
      # run the lib specs when a file in lib/ changes
      watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
      # run the model specs related to the changed model
      watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
      # run the view specs related to the changed view
      watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
      # run the integration specs related to the changed controller
      watch(%r{^app/controllers/(.+)\.rb}) { |m| "spec/requests/#{m[1]}_spec.rb" }
      # run all integration tests when application controller change
      watch('app/controllers/application_controller.rb') { "spec/requests" }
    end
    

    Guard es una buena herramienta pero, como es normal, no siempre cubre todas las necesidades. Algunas veces el flujo de trabajo de TDD funciona mejor con un atajo de teclado que facilite la ejecución sólo de los ejemplos que se deseen y cuando se desee. Después, se puede usar una tarea de rake para ejecutar la suite de pruebas entera antes de subir el código. Aquí están unos ejemplos de atajos de teclado de vim.

    Aprender más sobre guard-rspec.

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | automatic tests with guard", url: "http://betterspecs.org/#guard" %>

    Pruebas más rápidas (precarga Rails)

    Cuando se ejecuta una prueba en Rails se carga la aplicación Rails completa. Esto puede tomar un tiempo y romper el flujo de desarrollo. Para resolver este problema se pueden usar soluciones como Zeus, Spin o Spork. Estas soluciones precargarán todas las bibliotecas que (usualmente) no se cambian y recargarán controladores, modelos, vistas, factories y todos los archivos que cambian más a menudo.

    Aquí se puede encontrar un spec helper y una configuración Guardfile basada en Spork. Con esta configuración se recarga toda la aplicación si un archivo precargado (como inicializadores) cambia y se ejecutarán las pruebas individuales realmente rápido.

    La desventaja de usar Spork es que agrega agresivamente "monkey-patches" a tu código y puedes perder varias horas tratando de entender por qué un archivo no es recargado. Si tienes algunos ejemplos de código usando Spin o alguna otra solución déjanos saberlo.

    Aquí se puede encontrar un archivo de configuración Guardfile para usar con Zeus. El archivo spec_helper no requiere modificarse, sin embargo, es necesario ejectutar `zeus start` en una terminal para iniciar el servidor zeus antes de ejecutar las pruebas.

    Aunque Zeus toma medidas menos agresivas que Spork, una gran desventaja son los estrictos requerimientos para usarlo; Ruby 1.9.3+ (recomendado usar la versión modificada GC de Ruby 2.0) además es requerido un sistema operativo que soporte FSEvents o inotify.

    Muchos críticos están moviéndose a estas soluciones. Estas bibliotecas son un parche a un problema que es mejor resolver con un diseño mejor, e intentando cargar sólo las dependencias que necesites. Aprende más leyendo la discusión sobre el tema.

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | faster tests with spork", url: "http://betterspecs.org/#spork" %>

    Stubbing de peticiones HTTP

    Algunas veces necesitas acceder a servicios externos. En estos casos no puedes confiar en los servicios reales, pero puedes "stubearlos" con soluciones como webmock.

    Correcto

    context "with unauthorized access" do
      let(:uri) { 'http://api.lelylan.com/types' }
      before    { stub_request(:get, uri).to_return(status: 401, body: fixture('401.json')) }
      it "gets a not authorized notification" do
        page.driver.get uri
        page.should have_content 'Access denied'
      end
    end
    

    Aprender más sobre webmock y VCR. Aquí una buena presentación explicando como combinarlos.

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | mocking http requests", url: "http://betterspecs.org/#http" %>

    Formateador útil

    Usa un formateador que brinde información útil sobre la suite de pruebas. Personalmente encuentro a fuubar muy bueno. Para hacer que funcione agrega la gema y activa fuubar como el formateador por default en tu archivo Guardfile.

    Correcto

    # Gemfile
    group :development, :test do
      gem 'fuubar'
    

    Correcto

    # Guardfile
    guard 'rspec' do
      # ...
    end
    

    Correcto

    # .rspec
    --drb
    --format Fuubar
    --color
    

    Aprender más sobre fuubar.

    Discute este punto →

    <%= render "partials/share", text: "betterspecs.org | useful formatter", url: "http://betterspecs.org/#formatter" %>

    Libros

    <%= render "partials/books" %>

    Presentaciones

    RSpec 2 Best practices from Andrea Reginato

    Recursos en internet

    1. Everyday Rails Spec
    2. Eggs on Bread Best Practices
    3. The Carbon Emitter Best Practices
    4. Andy Vanasse Best Practices
    5. Bitfluxx Best Practices
    6. Dmytro Shteflyuk Best Practices
    7. thoughtbot's RSpec Related Reading

    Screencasts

    1. RSpec en PeepCode
    2. Curso en Code School: Probando con RSpec
    3. Spork Railscast
    4. Usando Zeus para acelerar tus pruebas
    5. Code TV Screencast: Guard y Spork
    6. Muchos de los screencasts de Destroy All Software

    Bibliotecas (documentación)

    1. Documentación de RSpec
    2. Documentación de Capybara
    3. Documentación de Factory Bot
    4. Documentación de Webmock
    5. Documentación de Timecop
    6. Shoulda Matchers
    7. Fuubar Relea

      Guía de Estilo

      Hemos buscado las mejores pautas para escribir especificaciones "agradables de leer". Un buen punto de inicio es por supuesto la suite de pruebas de Mongoid. Usa especificaciones con un estilo limpio y fácil de leer, siguiendo la mayoría de las pautas aquí descritas aquí.

      Mejorando Better Specs

      Este es un proyecto de código abierto. Si algo falta o es incorrecto sólo agrega un issue para discutir el tema. También puedes checar los siguientes issues:

      • Multilenguaje (agrega un 'issue' si deseas traducir esta guía)

      Ayúdanos

      Si has encontrado útiles estos tips y han mejorado tu trabajo, considera hacer una donación de USD $9. Cualquier donación será usada para hacer de este sitio una referencia más completa de pruebas en Ruby.