Tiendo a usar bloques anteriores para establecer variables de instancia. Luego uso esas variables en mis ejemplos. Hace poco me encontré let()
. Según los documentos de RSpec, se utiliza para
... para definir un método auxiliar memorable. El valor se almacenará en caché en varias llamadas en el mismo ejemplo, pero no en todos los ejemplos.
¿Cómo es esto diferente de usar variables de instancia en bloques anteriores? ¿Y también cuándo deberías usar let()
vs before()
?
Respuestas:
Siempre prefiero
let
una variable de instancia por un par de razones:nil
, lo que puede conducir a errores sutiles y falsos positivos. Comolet
crea un método, obtendrás unNameError
error cuando lo escribes mal, lo cual me parece preferible. También facilita la refactorización de las especificaciones.before(:each)
ejecutará un enlace antes de cada ejemplo, incluso si el ejemplo no usa ninguna de las variables de instancia definidas en el enlace. Esto no suele ser un gran problema, pero si la configuración de la variable de instancia lleva mucho tiempo, está desperdiciando ciclos. Para el método definido porlet
, el código de inicialización solo se ejecuta si el ejemplo lo llama.@
).let
y mantener miit
bloque agradable y corto.Puede encontrar un enlace relacionado aquí: http://www.betterspecs.org/#let
fuente
let
para definir todos los objetos dependientes, y usarlobefore(:each)
para configurar la configuración necesaria o cualquier simulacro / trozo necesario por los ejemplos. Prefiero esto a un gancho grande antes que contenga todo esto. Además,let(:foo) { Foo.new }
es menos ruidoso (y más al punto) entoncesbefore(:each) { @foo = Foo.new }
. Aquí hay un ejemplo de cómo lo uso: github.com/myronmarston/vcr/blob/v1.7.0/spec/vcr/util/…NoMethodError
una advertencia, pero YMMV.foo = Foo.new(...)
y luego usuariosfoo
en líneas posteriores. Más tarde, escribe un nuevo ejemplo en el mismo grupo de ejemplo que también necesita unaFoo
instancia de la misma manera. En este punto, desea refactorizar para eliminar la duplicación. Puede eliminar lasfoo = Foo.new(...)
líneas de sus ejemplos y reemplazarlaslet(:foo) { Foo.new(...) }
sin cambiar el uso de los ejemplosfoo
. Pero si refactorizabefore { @foo = Foo.new(...) }
también debe actualizar las referencias en los ejemplos defoo
a@foo
.La diferencia entre usar variables de instancias y
let()
es quelet()
se evalúa de forma diferida . Esto significa quelet()
no se evalúa hasta que el método que define se ejecuta por primera vez.La diferencia entre
before
ylet
es quelet()
le brinda una buena manera de definir un grupo de variables en un estilo 'en cascada'. Al hacer esto, la especificación se ve un poco mejor al simplificar el código.fuente
let
si necesitas evaluar algo cada vez? Por ejemplo, necesito un modelo secundario para estar presente en la base de datos antes de que se active algún comportamiento en el modelo principal. No estoy necesariamente haciendo referencia a ese modelo secundario en la prueba, porque estoy probando el comportamiento de los modelos principales. En este momento estoy usando ellet!
método en su lugar, pero ¿tal vez sería más explícito poner esa configuraciónbefore(:each)
?He reemplazado completamente todos los usos de las variables de instancia en mis pruebas rspec para usar let (). He escrito un ejemplo rápido para un amigo que lo usó para enseñar una pequeña clase de Rspec: http://ruby-lambda.blogspot.com/2011/02/agile-rspec-with-let.html
Como algunas de las otras respuestas aquí dicen, let () se evalúa de manera diferida, por lo que solo cargará las que requieren carga. SECA la especificación y la hace más legible. De hecho, he portado el código Rspec let () para usar en mis controladores, en el estilo de la joya de herencia_recursos. http://ruby-lambda.blogspot.com/2010/06/stealing-let-from-rspec.html
Junto con la evaluación diferida, la otra ventaja es que, combinado con ActiveSupport :: Concern, y la especificación / soporte / comportamiento de cargar todo, puede crear su propia mini-DSL de especificación específica para su aplicación. He escrito algunos para probar contra Rack y recursos RESTful.
La estrategia que uso es Factory-everything (a través de Machinist + Forgery / Faker). Sin embargo, es posible usarlo en combinación con los bloques before (: each) para precargar las fábricas para un conjunto completo de grupos de ejemplo, lo que permite que las especificaciones se ejecuten más rápido: http://makandra.com/notes/770-taking-advantage -de-rspec-s-let-in-before-blocks
fuente
# spec/friendship_spec.rb
y# spec/comment_spec.rb
ejemplo, ¿no crees que lo hacen menos legible? No tengo idea de dóndeusers
vengo y necesitaré cavar más profundo.let()
los últimos días y personalmente no veo la diferencia, excepto por la primera ventaja que mencionó Myron. Y no estoy tan seguro de dejar ir y qué no, tal vez porque soy vago y me gusta ver el código por adelantado sin tener que abrir otro archivo. Gracias por tus comentarios.Es importante tener en cuenta que let es un vago evaluado y no incluye métodos de efectos secundarios; de lo contrario, no podrá cambiar fácilmente de let a before (: each) . Puedes usar let! en lugar de dejar que se evalúe antes de cada escenario.
fuente
En general,
let()
es una sintaxis más agradable y te ahorra escribir@name
símbolos por todas partes. Pero, advertencia emptor! Descubrí quelet()
también introduce errores sutiles (o al menos rascarse la cabeza) porque la variable realmente no existe hasta que intentas usarla ... Muestra el signo de cuento: si agregas unputs
despuéslet()
para ver que la variable es correcta, permite una especificación para aprobar, pero sin laputs
especificación falla: ha encontrado esta sutileza.¡También he descubierto que
let()
no parece estar en caché en todas las circunstancias! Lo escribí en mi blog: http://technicaldebt.com/?p=1242¿Tal vez sea sólo yo?
fuente
let
siempre memoriza el valor para la duración de un solo ejemplo. No memoriza el valor en varios ejemplos.before(:all)
, por el contrario, le permite reutilizar una variable inicializada en varios ejemplos.let!
está diseñado. relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/…let es funcional ya que es esencialmente un Proc. También está en caché.
Un problema que encontré de inmediato con let ... En un bloque Spec que está evaluando un cambio.
Deberá asegurarse de llamar
let
fuera de su bloque esperado. es decir, está llamandoFactoryGirl.create
en su bloque let. Normalmente hago esto verificando que el objeto persiste.De lo contrario, cuando
let
se llama al bloque por primera vez, se producirá un cambio en la base de datos debido a la instanciación diferida.Actualizar
Solo agrego una nota. Tenga cuidado jugando código golf o en este caso rspec golf con esta respuesta.
En este caso, solo tengo que llamar a algún método al que responda el objeto. Entonces invoco el
_.persisted?
método _ en el objeto como verdadero. Todo lo que intento hacer es crear una instancia del objeto. ¿Podrías llamar vacío? o nulo? también. El punto no es la prueba, sino traer el objeto a la vida llamándolo.Entonces no puedes refactorizar
ser - estar
como el objeto no ha sido instanciado ... es vago. :)
Actualización 2
aprovechar el let! sintaxis para la creación instantánea de objetos, lo que debería evitar este problema por completo. Tenga en cuenta, sin embargo, que derrotará mucho el propósito de la pereza del let no golpeado.
También, en algunos casos, es posible que desee aprovechar la sintaxis del tema en lugar de permitir, ya que puede ofrecerle opciones adicionales.
fuente
Nota para Joseph: si está creando objetos de base de datos en una
before(:all)
, no se capturarán en una transacción y es mucho más probable que se quede en la base de datos de prueba. Usar en subefore(:each)
lugar.La otra razón para usar let y su evaluación perezosa es para que pueda tomar un objeto complicado y probar piezas individuales anulando let en contextos, como en este ejemplo muy artificial:
fuente
"antes" por defecto implica
before(:each)
. Ref. The Rspec Book, copyright 2010, página 228.Utilizo
before(:each)
para sembrar algunos datos para cada grupo de ejemplo sin tener que llamar allet
método para crear los datos en el bloque "it". Menos código en el bloque "it" en este caso.Lo uso
let
si quiero algunos datos en algunos ejemplos pero no en otros.Tanto antes como let son excelentes para SECAR los bloques "it".
Para evitar confusiones, "let" no es lo mismo que
before(:all)
. "Let" reevalúa su método y valor para cada ejemplo ("it"), pero almacena en caché el valor en varias llamadas en el mismo ejemplo. Puede leer más sobre esto aquí: https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-letfuente
Voz disidente aquí: después de 5 años de rspec no me gusta
let
mucho.1. La evaluación perezosa a menudo confunde la configuración de la prueba
Se vuelve difícil razonar sobre la configuración cuando algunas cosas que se han declarado en la configuración no están afectando el estado, mientras que otras sí.
Con el tiempo, debido a la frustración alguien cambia
let
alet!
(lo mismo sin evaluación perezosa) con el fin de conseguir su trabajo de especificaciones. Si esto funciona para ellos, nace un nuevo hábito: cuando se agrega una nueva especificación a una suite anterior y no funciona, lo primero que intenta el escritor es agregar explosiones a laslet
llamadas aleatorias .Muy pronto, todos los beneficios de rendimiento se han ido.
2. La sintaxis especial es inusual para usuarios que no son rspec
Prefiero enseñar Ruby a mi equipo que los trucos de rspec. Las variables de instancia o las llamadas a métodos son útiles en todas partes en este proyecto y en otros, la
let
sintaxis solo será útil en rspec.3. Los "beneficios" nos permiten ignorar fácilmente los buenos cambios de diseño
let()
es bueno para las dependencias costosas que no queremos crear una y otra vez. También se combina biensubject
, lo que le permite secar llamadas repetidas a métodos de múltiples argumentosLas dependencias costosas se repiten en muchas ocasiones, y los métodos con grandes firmas son puntos en los que podríamos mejorar el código:
En todos estos casos, puedo abordar el síntoma de las pruebas difíciles con un bálsamo calmante de magia rspec, o puedo tratar de abordar la causa. Siento que pasé demasiado de los últimos años en el primero y ahora quiero un código mejor.
Para responder a la pregunta original: preferiría no hacerlo, pero todavía lo uso
let
. Lo uso principalmente para encajar con el estilo del resto del equipo (parece que la mayoría de los programadores de Rails en el mundo ahora están inmersos en su magia rspec, por lo que es muy frecuente). A veces lo uso cuando agrego una prueba a algún código del que no tengo control, o no tengo tiempo para refactorizar a una mejor abstracción: es decir, cuando la única opción es el analgésico.fuente
Utilizo
let
para probar mis respuestas HTTP 404 en mis especificaciones API usando contextos.Para crear el recurso, yo uso
let!
. Pero para almacenar el identificador de recursos, yo usolet
. Echa un vistazo a cómo se ve:Eso mantiene las especificaciones limpias y legibles.
fuente