¿Cuál es la diferencia entre el sujeto de RSpec y dejar? ¿Cuándo deben usarse o no?

Respuestas:

186

Resumen: El sujeto de RSpec es una variable especial que se refiere al objeto que se está probando. Las expectativas se pueden establecer implícitamente, lo que admite ejemplos de una línea. Es claro para el lector en algunos casos idiomáticos, pero por lo demás es difícil de entender y debe evitarse. Las letvariables de RSpec son simplemente variables instanciadas (memorizadas) de manera perezosa. No son tan difíciles de seguir como el tema, pero aún pueden dar lugar a pruebas enredadas, por lo que deben usarse con discreción.

El tema

Cómo funciona

El sujeto es el objeto que se está probando. RSpec tiene una idea explícita del tema. Puede estar definido o no. Si es así, RSpec puede llamar a métodos sin hacer referencia a él explícitamente.

De forma predeterminada, si el primer argumento de un grupo ( describeo contextbloque) de ejemplo más externo es una clase, RSpec crea una instancia de esa clase y la asigna al sujeto. Por ejemplo, los siguientes pases:

class A
end

describe A do
  it "is instantiated by RSpec" do
    expect(subject).to be_an(A)
  end
end

Puede definir el tema usted mismo con subject:

describe "anonymous subject" do
  subject { A.new }
  it "has been instantiated" do
    expect(subject).to be_an(A)
  end
end

Puede darle un nombre al tema cuando lo defina:

describe "named subject" do
  subject(:a) { A.new }
  it "has been instantiated" do
    expect(a).to be_an(A)
  end
end

Incluso si nombra el tema, aún puede referirse a él de forma anónima:

describe "named subject" do
  subject(:a) { A.new }
  it "has been instantiated" do
    expect(subject).to be_an(A)
  end
end

Puede definir más de un tema con nombre. El sujeto nombrado definido más recientemente es el anónimo subject.

Independientemente de cómo se defina el tema,

  1. Se crea una instancia perezosamente. Es decir, la instanciación implícita de la clase descrita o la ejecución del bloque pasado subjectno ocurre hasta que subjecto se hace referencia al sujeto nombrado en un ejemplo. Si desea que su sujeto explícito sea instanciado con entusiasmo (antes de que se ejecute un ejemplo en su grupo), diga en subject!lugar de subject.

  2. Las expectativas se pueden establecer implícitamente (sin escribir subjectni el nombre de un sujeto con nombre):

    describe A do
      it { is_expected.to be_an(A) }
    end
    

    El tema existe para apoyar esta sintaxis de una línea.

Cuando usarlo

Un implícito subject(inferido del grupo de ejemplo) es difícil de entender porque

  • Está instanciado detrás de escena.
  • Ya sea que se use implícitamente (llamando is_expectedsin un receptor explícito) o explícitamente (como subject), no le da al lector información sobre el rol o la naturaleza del objeto en el que se llama a la expectativa.
  • La sintaxis de ejemplo de una sola línea no tiene una descripción de ejemplo (el argumento de cadena iten la sintaxis de ejemplo normal), por lo que la única información que tiene el lector sobre el propósito del ejemplo es la expectativa misma.

Por lo tanto, solo es útil usar un tema implícito cuando es probable que todos los lectores comprendan bien el contexto y realmente no haya necesidad de una descripción de ejemplo . El caso canónico es probar las validaciones de ActiveRecord con comparadores que deberían:

describe Article do
  it { is_expected.to validate_presence_of(:title) }
end

Un anónimo explícito subject(definido con subjectsin nombre) es un poco mejor, porque el lector puede ver cómo se instancia, pero

  • todavía puede poner la instanciación del sujeto lejos de donde se usa (por ejemplo, en la parte superior de un grupo de ejemplo con muchos ejemplos que lo usan), lo cual aún es difícil de seguir, y
  • tiene los otros problemas que tiene el sujeto implícito.

Un sujeto con nombre proporciona un nombre que revela la intención, pero la única razón para usar un sujeto con nombre en lugar de una letvariable es si desea usar el sujeto anónimo alguna vez, y acabamos de explicar por qué el sujeto anónimo es difícil de entender.

Por lo tanto, los usos legítimos de un subjectsujeto explícito anónimo o con nombre son muy raros .

let variables

Cómo trabajan ellos

let las variables son como sujetos nombrados excepto por dos diferencias:

  • están definidos con let/ en let!lugar de subject/subject!
  • no establecen lo anónimo subjectni permiten que se invoquen expectativas implícitamente.

Cuando usarlos

Es completamente legítimo usarlo letpara reducir la duplicación entre ejemplos. Sin embargo, hágalo solo cuando no sacrifique la claridad de la prueba. El momento más seguro para usar letes cuando el letpropósito de la variable está completamente claro a partir de su nombre (para que el lector no tenga que encontrar la definición, que podría estar a muchas líneas de distancia, para entender cada ejemplo) y se usa de la misma manera. en cada ejemplo. Si alguna de esas cosas no es cierta, considere la posibilidad de definir el objeto en una variable local antigua simple o llamar a un método de fábrica en el ejemplo.

let!es arriesgado, porque no es perezoso. Si alguien agrega un ejemplo al grupo de ejemplos que contiene el let!, pero el ejemplo no necesita la let!variable,

  • Ese ejemplo será difícil de entender, porque el lector verá la let!variable y se preguntará si afecta al ejemplo y cómo lo hace.
  • el ejemplo será más lento de lo necesario, debido al tiempo necesario para crear la let!variablle

Por lo tanto let!, use , si es que lo usa , solo en grupos de ejemplo pequeños y simples donde es menos probable que los futuros escritores de ejemplos caigan en esa trampa.

El fetiche de una sola expectativa por ejemplo

Existe un uso excesivo común de temas o letvariables que vale la pena discutir por separado. A algunas personas les gusta usarlos así:

describe 'Calculator' do
  describe '#calculate' do
    subject { Calculator.calculate }
    it { is_expected.to be >= 0 }
    it { is_expected.to be <= 9 }
  end
end

(Este es un ejemplo simple de un método que devuelve un número para el que necesitamos dos expectativas, pero este estilo puede tener muchos más ejemplos / expectativas si el método devuelve un valor más complicado que necesita muchas expectativas y / o tiene muchos efectos secundarios que todos necesitan expectativas.)

Las personas hacen esto porque han escuchado que uno debe tener solo una expectativa por ejemplo (que se confunde con la regla válida de que solo se debe probar una llamada a un método por ejemplo) o porque están enamorados de la astucia de RSpec. ¡No lo hagas, ya sea con un sujeto anónimo o nombrado o con una letvariable! Este estilo tiene varios problemas:

  • El sujeto anónimo no es el tema de los ejemplos, el método es el tema. Escribir el examen de esta manera arruina el idioma, lo que hace que sea más difícil pensar en ello.
  • Como siempre ocurre con los ejemplos de una línea, no hay espacio para explicar el significado de las expectativas.
  • Hay que construir el tema para cada ejemplo, lo cual es lento.

En su lugar, escribe un solo ejemplo:

describe 'Calculator' do
  describe '#calculate' do
    it "returns a single-digit number" do
      result = Calculator.calculate
      expect(result).to be >= 0
      expect(result).to be <= 9
    end
  end
end
Dave Schweisguth
fuente
17
¡Guauu! + 💯! "El fetiche de una sola expectativa por ejemplo" vale su peso en oro, por así decirlo. Nunca sentí la necesidad de seguir muy de cerca esa (mala) regla, pero ahora estoy bien armado contra las personas que intentan imponerlo al resto de nosotros. ¡Gracias!
iconoclasta
2
Además, si desea que sus bloques de múltiples expectativas ejecuten todas las líneas esperadas (en lugar de no ejecutarse si la primera falla), puede usar la :aggregate_failuresetiqueta en una línea como it ​"marks a task complete"​, ​:aggregate_failures​ ​do(tomada del libro Rails 5 Test Prescriptions)
labyrinth
1
También estoy en contra de las exageraciones y los fetiches, la "expectativa única por ejemplo" es principalmente para aquellos a los que les gusta agrupar muchas expectativas no relacionadas en un solo ejemplo. Hablando semánticamente, su ejemplo no es el ideal, porque no valida para un número de un solo dígito, valida para números reales en [0, 9] - que, sorpresa sorpresa, podría codificarse en una única expectativa mucho más legible expect(result).to be_between(0, 9).
Andre Figueiredo
Si desea validar solo para un número de un solo dígito (Int [0,9]), sería más adecuado hacerlo expect(result.to_s).to match(/^[0-9]$/); sé que es feo, pero realmente prueba lo que está diciendo, o tal vez, use between+ is_a? Integer, pero aquí está probando el tipo también. Y simplemente let... no debería ser motivo de preocupación, y en realidad puede ser mejor reevaluar los valores entre ejemplos. De lo contrario +1 para el puesto
Andre Figueiredo
Me encanta tu explicación sobre los peligros let!y no he sido para convencer a mis compañeros por mi cuenta. Voy a enviar esta respuesta.
Damon Aw
5

Subjecty letson solo herramientas para ayudarlo a ordenar y acelerar sus pruebas. La gente de la comunidad rspec los usa, así que no me preocuparía si está bien usarlos o no. Se pueden usar de manera similar pero tienen propósitos ligeramente diferentes.

Subjectle permite declarar un sujeto de prueba y luego reutilizarlo para cualquier número de casos de prueba siguientes. Esto reduce la repetición del código (SECANDO su código)

Letes una alternativa a los before: eachbloques, que asignan datos de prueba a variables de instancia. Lette da un par de ventajas. Primero, almacena en caché el valor sin asignarlo a una variable de instancia. En segundo lugar, se evalúa de forma perezosa, lo que significa que no se evalúa hasta que una especificación lo solicita. Por lo tanto, le letayuda a acelerar sus pruebas. También creo que letes más fácil de leer

Ren
fuente
1

subjectes lo que se está probando, generalmente una instancia o una clase. letes para asignar variables en sus pruebas, que se evalúan de forma perezosa en comparación con el uso de variables de instancia. Hay algunos buenos ejemplos en este hilo.

https://github.com/reachlocal/rspec-style-guide/issues/6

nikkypx
fuente