RSpec: ¿Cuál es la diferencia entre let y un bloque anterior?

90

¿Cuál es la diferencia entre lety un beforebloque en RSpec?

¿Y cuándo usar cada uno?

¿Cuál será un buen enfoque (antes o antes) en el siguiente ejemplo?

let(:user) { User.make !}
let(:account) {user.account.make!}

before(:each) do
 @user = User.make!
 @account = @user.account.make!
end

Yo estudié esto publicación de stackoverflow

Pero, ¿es bueno definir let para cosas de asociación como las anteriores?

kriysna
fuente
1
Básicamente, las personas a las que no les gustan las variables de instancia usan 'dejar'. Como nota al margen, debería considerar el uso de FactoryGirl o una herramienta similar.
apneadiving

Respuestas:

146

La gente parece haber explicado algunas de las formas básicas en las que se diferencian, pero no before(:all) y no explican exactamente por qué deberían usarse.

Creo que las variables de instancia no tienen lugar en la gran mayoría de las especificaciones, en parte debido a las razones mencionadas en esta respuesta , por lo que no las mencionaré como una opción aquí.

dejar bloques

Código dentro de un let bloque solo se ejecuta cuando se hace referencia, la carga diferida significa que el orden de estos bloques es irrelevante. Esto le brinda una gran cantidad de energía para reducir la configuración repetida a través de sus especificaciones.

Un ejemplo (extremadamente pequeño y artificial) de esto es:

let(:person)     { build(:person) }
subject(:result) { Library.calculate_awesome(person, has_moustache) }

context 'with a moustache' do
  let(:has_moustache) { true } 
  its(:awesome?)      { should be_true }
end

context 'without a moustache' do
  let(:has_moustache) { false } 
  its(:awesome?)      { should be_false }
end

Puede ver que has_moustachese define de manera diferente en cada caso, pero no es necesario repetir la subjectdefinición. Algo importante a tener en cuenta es que letse utilizará el último bloque definido en el contexto actual. Esto es bueno para establecer un valor predeterminado que se utilizará para la mayoría de las especificaciones, que se pueden sobrescribir si es necesario.

Por ejemplo, verificar el valor de retorno de calculate_awesomesi se pasa un personmodelo con el top_hatvalor verdadero, pero sin bigote sería:

context 'without a moustache but with a top hat' do
  let(:has_moustache) { false } 
  let(:person)        { build(:person, top_hat: true) }
  its(:awesome?)      { should be_true }
end

Otra cosa a tener en cuenta sobre los bloques let, no deben usarse si está buscando algo que se ha guardado en la base de datos (es decir Library.find_awesome_people(search_criteria)), ya que no se guardarán en la base de datos a menos que ya se haya hecho referencia a ellos. let!o beforebloques son los que deberían usarse aquí.

Además, nunca lo utilice beforepara activar la ejecución de letbloques, ¡para esto let!está hecho!

¡dejar! bloques

let!los bloques se ejecutan en el orden en que están definidos (muy parecido a un bloque anterior). La única diferencia fundamental con los bloques before es que obtiene una referencia explícita a esta variable, en lugar de tener que recurrir a las variables de instancia.

Al igual que con los letbloques, si let!se definen varios bloques con el mismo nombre, el más reciente es el que se utilizará en la ejecución. La diferencia principal es que los let!bloques se ejecutarán varias veces si se usan así, mientras que el letbloque solo se ejecutará la última vez.

antes (: cada) bloques

before(:each)es el bloque anterior predeterminado y, por lo tanto, se puede hacer referencia a él en before {}lugar de especificar el completo before(:each) {}cada vez.

Es mi preferencia personal usar beforebloques en algunas situaciones básicas. Usaré antes de los bloques si:

  • Estoy usando burla, stubbing o dobles
  • Existe una configuración de tamaño razonable (generalmente, esto es una señal de que sus rasgos de fábrica no se han configurado correctamente)
  • Hay una serie de variables a las que no necesito hacer referencia directamente, pero son necesarias para la configuración
  • Estoy escribiendo pruebas de controlador funcional en rieles y quiero ejecutar una solicitud específica para probar (es decir before { get :index }). Aunque podría usar subjectpara esto en muchos casos, a veces se siente más explícito si no necesita una referencia.

Si se encuentra escribiendo beforebloques grandes para sus especificaciones, revise sus fábricas y asegúrese de comprender completamente los rasgos y su flexibilidad.

antes (: todos) bloques

Estos solo se ejecutan una vez, antes de las especificaciones en el contexto actual (y sus hijos). Estos se pueden utilizar con gran ventaja si se escriben correctamente, ya que hay ciertas situaciones que pueden reducir la ejecución y el esfuerzo.

Un ejemplo (que difícilmente afectaría el tiempo de ejecución) es simular una variable ENV para una prueba, que solo debería tener que hacer una vez.

Espero que ayude :)

Arrendajo
fuente
Para los usuarios de minitest, que soy, pero no noté la etiqueta RSpec de esta sesión de preguntas y respuestas, la before(:all)opción no existe en Minitest. Aquí hay algunas soluciones en los comentarios: github.com/seattlerb/minitest/issues/61
MrYoshiji
El artículo al que se hace referencia es un enlace muerto: \
Dylan Pierce
Gracias @DylanPierce. No puedo encontrar ninguna copia de ese artículo, así que hice referencia a una respuesta SO que aborda esto en su lugar :)
Jay
Gracias, de verdad
Dylan Pierce
1
itsya no está en rspec-core. Una forma idiomática más moderna es it { is_expected.to be_awesome }.
Zubin
26

Casi siempre, prefiero let. La publicación que enlaza especifica que lettambién es más rápida. Sin embargo, a veces, cuando hay que ejecutar muchos comandos, podría usarlo before(:each)porque su sintaxis es más clara cuando hay muchos comandos involucrados.

En su ejemplo, definitivamente preferiría usar en letlugar de before(:each). En términos generales, cuando solo se realiza una inicialización de variable, me gusta usar let.

Spyros
fuente
15

Una gran diferencia que no se ha mencionado es que las variables definidas con letno se instancian hasta que se llama por primera vez. Entonces, si bien un before(:each)bloque crearía una instancia de todas las variables, le letpermite definir una cantidad de variables que podría usar en múltiples pruebas, no las instancia automáticamente. Sin saber esto, sus pruebas podrían volver a morderse si espera que todos los datos se carguen de antemano. En algunos casos, es posible que incluso desee definir una cantidad de letvariables y luego usar un before(:each)bloque para llamar a cada letinstancia solo para asegurarse de que los datos estén disponibles para empezar.

mikeweber
fuente
20
Puede utilizar let!para definir métodos que se llaman antes de cada ejemplo. Consulte los documentos de RSpec .
Jim Stewart
3

Parece que está utilizando Machinist. ¡Cuidado, es posible que vea algunos problemas con make! dentro de let (la versión no explosiva) que ocurre fuera de la transacción global del accesorio (si también está utilizando accesorios transaccionales), corrompiendo los datos para sus otras pruebas.

Tim Connor
fuente