El segundo artículo de esta breve serie le enseña cómo usar varios emparejadores que vienen con RSpec. También le muestra cómo dividir su conjunto de pruebas a través del etiquetado, cómo funcionan las devoluciones de llamada y cómo extraer algunos datos. Ampliamos un poco el kit de supervivencia básico del primer artículo y te mostramos lo suficiente para ser peligroso sin demasiada cuerda para colgarte..
En el primer artículo pasamos bastante tiempo tratando de responder el "por qué" de las pruebas. Sugiero que regresemos al "cómo" y nos ahorramos más contexto. Ya cubrimos esa parte extensivamente. Veamos qué más tiene que ofrecer RSpec que usted, como principiante, puede manejar de inmediato..
Así que esto va a acercarse al corazón de las cosas. RSpec le proporciona una tonelada de supuestos comparadores. Estos son tu pan y mantequilla cuando escribes tus expectativas. Hasta ahora has visto .a eq
y .no_a eq
. Pero hay un arsenal mucho más grande para escribir sus especificaciones. Puede realizar pruebas para generar errores, valores verdaderos y falsos, o incluso para clases específicas. Analicemos algunas opciones para comenzar:
.a eq
.no_a eq
Esta prueba de equivalencia.
... es 'alguna descripción inteligente' esperar (agent.enemy). Hasta eq 'Ernst Stavro Blofeld' expect (agent.enemy) .not_to eq 'Winnie Pooh' final ...
Para abreviar, empaqué dos declaraciones esperadas dentro de una eso
bloquear. Sin embargo, es una buena práctica probar solo una cosa por prueba. Esto mantiene las cosas mucho más enfocadas y sus pruebas terminarán siendo menos frágiles cuando las cambie..
.para ser verdad
.a decir verdad
... es 'una descripción inteligente' que se espera (agent.hero?). Be_truthy expect (enemy.megalomaniac?).
La diferencia es que be_truthy
es verdad cuando no lo es nulo
o falso
. Así que pasará si el resultado no es ninguno de estos dos tipos de "verdaderos".. .a decir verdad
por otro lado solo acepta un valor que sea cierto
y nada más.
.ser_falsía
.ser falso
... es 'una descripción inteligente' que se espera (agent.coward?). Be_falsy expect (enemy.megalomaniac?). False End ...
Similar a los dos ejemplos anteriores, .ser_falsía
espera o bien un falso
o un nulo
valor, y .ser falso
sólo hará una comparación directa en falso
.
.ser
.to_not be_nil
Y por último, pero no menos importante, esto prueba exactamente para nulo
sí mismo. Te guardo el ejemplo.
.coincidir ()
Espero que ya hayas tenido el placer de mirar expresiones regulares. De lo contrario, esta es una secuencia de caracteres con la que puede definir un patrón que coloca entre dos barras diagonales para buscar cadenas. Una expresión regular puede ser muy útil si desea buscar patrones más amplios que pueda generalizar en esa expresión.
... es 'alguna descripción inteligente' esperar (agent.number.to_i) .to match (/ \ d 3 /) final ...
Supongamos que estamos tratando con agentes como James Bond, 007, a quienes se les asignan números de tres dígitos. Entonces podríamos probarlo de esta manera, primitivamente aquí, por supuesto..
>
<
<=
> =
Las comparaciones son útiles más a menudo de lo que uno podría pensar. Supongo que los ejemplos a continuación cubrirán lo que necesita saber.
... es 'alguna descripción inteligente' do ... espera (agent.number) .para ser < quartermaster.number expect(agent.number).to be > m.number expect (agent.kill_count) .to sea = = 25 expect (quartermaster.number_of_gadgets) .to sea <= 5 end…
Ahora, estamos llegando a un lugar menos aburrido. También puedes probar por clases y tipos:
.be_an_instance_of
.ser un
.ser un
... es 'alguna descripción inteligente' do mission = Mission.create (nombre: 'Moonraker') agent = Agent.create (nombre: 'James Bond') mission.agents << agent expect(@mission.agents).not_to be_an_instance_of(Agent) expect(@mission.agents).to be_a(ActiveRecord::Associations::CollectionProxy) end…
En el ejemplo ficticio anterior, puede ver que una lista de agentes asociados con una misión no son de clase Agente
pero de ActiveRecord :: Asociaciones :: CollectionProxy
. Lo que debe quitar de este es que podemos evaluar fácilmente las clases mientras nos mantenemos altamente expresivos. .ser un
y .ser un
hacer una y la misma cosa. Tienes ambas opciones disponibles para mantener las cosas legibles.
La prueba de errores también es muy conveniente en RSpec. Si está súper fresco en Rails y no está seguro todavía de los errores que el marco puede lanzarle, es posible que no sienta la necesidad de usar estos, por supuesto, eso tiene mucho sentido. Sin embargo, en una etapa posterior de su desarrollo, los encontrará muy útiles. Tienes cuatro formas de lidiar con ellos:
.a raise_error
Esta es la forma más genérica. Cualquier error que se levante será lanzado en su red.
.a raise_error (ErrorClass)
De esa manera puede especificar exactamente de qué clase debe provenir el error.
.a raise_error (ErrorClass, "Algunos mensajes de error")
Esto es aún más minucioso, ya que no solo menciona la clase del error, sino un mensaje específico que se debe lanzar con el error..
.a raise_error ("Algún mensaje de error)
O simplemente menciona el mensaje de error sin la clase de error. Sin embargo, la parte esperada debe escribirse de forma un poco diferente, sin embargo, debemos envolver la parte debajo del texto en un bloque de código en sí mismo:
... es 'alguna descripción inteligente' do agent = Agent.create (nombre: 'James Bond') espera agent.lady_killer?. Raise_error (NoMethodError) espera double_agent.name .to raise_error (NameError) expect double_agent. nombre .para raise_error ("Error: No hay agentes dobles alrededor") espere double_agent.name .para raise_error (NombreError, "Error: No hay agentes dobles alrededor") final ...
.para empezar
.para terminar con
Como a menudo nos ocupamos de las colecciones cuando creamos aplicaciones web, es bueno tener una herramienta para mirarlas. Aquí agregamos dos agentes, Q y James Bond, y solo queríamos saber quién es el primero y el último en la colección de agentes para una misión en particular. Aquí Moonraker.
... es 'alguna descripción inteligente' do moonraker = Mission.create (nombre: 'Moonraker') bond = Agent.create (nombre: 'James Bond') q = Agent.create (nombre: 'Q') moonraker.agents << bond moonraker.agents << q expect(moonraker.agents).to start_with(bond) expect(moonraker.agents).to end_with(q) end…
.para incluir
Éste también es útil para comprobar el contenido de las colecciones..
... es 'alguna descripción inteligente' do mission = Mission.create (nombre: 'Moonraker') bond = Agent.create (nombre: 'James Bond') mission.agents << bond expect(mission.agents).to include(bond) end…
Estos emparejadores de predicados son una característica de RSpec para crear dinámicamente emparejadores para usted. Si tiene métodos de predicado en sus modelos, por ejemplo (que terminan con un signo de interrogación), RSpec sabe que debe crear comparadores para usted que pueda usar en sus pruebas. En el siguiente ejemplo, queremos comprobar si un agente es James Bond:
agente de clase < ActiveRecord::Base def bond? name == 'James Bond' && number == '007' && gambler == true end… end
Ahora, podemos usar esto en nuestras especificaciones así:
... es 'alguna descripción inteligente' do agent = Agent.create (nombre: 'James Bond', número: '007', jugador: verdadero) esperar (agent). Para ser_bond finalizarla 'alguna descripción inteligente' do agent = Agent. create (nombre: 'James Bond') espera (agente) .not_to be_bond end ...
RSpec nos permite usar el nombre del método sin el signo de interrogación, para formar una mejor sintaxis, supongo. Genial no lo es?
dejar
y dejar!
Puede que parezcan variables al principio, pero en realidad son métodos de ayuda. El primero se evalúa perezosamente, lo que significa que solo se ejecuta y evalúa cuando una especificación realmente lo utiliza, y el otro deja que se ejecute el bang (!) Independientemente de que la especifique o no. Ambas versiones están memorizadas y sus valores se almacenarán en caché dentro del mismo ámbito de ejemplo.
describe Mission, '#prepare',: let do let (: mission) Mission.create (name: 'Moonraker') let! (: bond) Agent.create (name: 'James Bond') agrega ' agentes para una misión 'do mission.prepare (bond) espera (mission.agents). para incluir bond end end end
La versión de bang que no se evalúa perezosamente puede llevar mucho tiempo y, por lo tanto, costosa si se convierte en tu nuevo y elegante amigo. ¿Por qué? Debido a que configurará estos datos para cada prueba en cuestión, sin importar qué, y eventualmente terminará siendo una de estas cosas desagradables que ralentizarán significativamente su conjunto de pruebas..
Debe conocer esta característica de RSpec ya que dejar
Es ampliamente conocido y utilizado. Dicho esto, el siguiente artículo le mostrará algunos problemas con los que debe tener en cuenta. Utilice estos métodos de ayuda con precaución, o al menos en pequeñas dosis por ahora.
RSpec le ofrece la posibilidad de declarar el tema a prueba muy explícitamente. Hay mejores soluciones para esto, y analizaremos las desventajas de este enfoque en el próximo artículo cuando muestre algunas cosas que generalmente desea evitar. Pero por ahora, echemos un vistazo a lo que tema
puede hacer por usted:
describe Agente, '#status' do subject Agent.create (name: 'Bond') it 'devuelve el estado de los agentes' do expect (subject.status) .not_para ser el final de 'MIA'
Este enfoque puede, por un lado, ayudarlo a reducir la duplicación de código, tener un protagonista declarado una vez en cierto alcance, pero también puede llevar a algo que se llama un invitado misterioso. Esto simplemente significa que podríamos terminar en una situación en la que usamos datos para uno de nuestros escenarios de prueba, pero ya no tenemos idea de de dónde proviene realmente ni de qué se compone. Más sobre eso en el próximo artículo..
En caso de que todavía no esté al tanto de las devoluciones de llamadas, permítame darle un breve resumen. Las devoluciones de llamada se ejecutan en ciertos puntos específicos del ciclo de vida del código. En términos de Rails, esto significaría que tiene un código que se está ejecutando antes de que los objetos se creen, actualicen, destruyan, etc..
En el contexto de RSpec, es el ciclo de vida de las pruebas que se están ejecutando. Eso simplemente significa que puede especificar enlaces que deben ejecutarse antes o después de que cada prueba se ejecute en el archivo de especificaciones, por ejemplo, o simplemente alrededor de cada prueba. Hay algunas opciones más detalladas disponibles, pero recomiendo que evitemos perdernos en los detalles por ahora. Lo primero es lo primero:
antes (: cada uno)
Esta devolución de llamada se ejecuta antes de cada ejemplo de prueba.
describe el Agente, '#favorite_gadget' do antes (: cada uno) do @gagdet = Gadget.create (nombre: 'Walther PPK') finaliza 'un elemento, el gadget favorito del agente' do agent = Agent.create (nombre : 'James Bond') agent.favorite_gadgets << @gadget expect(agent.favorite_gadget).to eq 'Walther PPK' end… end
Digamos que necesitaría un determinado gadget para cada prueba que ejecute en un determinado ámbito. antes de
le permite extraer esto en un bloque y prepara este pequeño fragmento para usted convenientemente. Cuando configura los datos de esa manera, tiene que usar variables de instancia, por supuesto, para tener acceso a ellos entre varios ámbitos.
No te dejes engañar por la conveniencia en este ejemplo. Solo porque puedas hacer este tipo de cosas no significa que debas hacerlo. Quiero evitar entrar en el territorio de AntiPattern y confundirte, pero por otro lado, quiero explicar las desventajas de estos simples ejercicios ficticios también..
En el ejemplo anterior, sería mucho más expresivo si configurara los objetos necesarios en una base de prueba por prueba. Especialmente en archivos de especificaciones más grandes, puedes perder rápidamente de vista estas pequeñas conexiones y dificultar que otros armen estos rompecabezas..
antes de todo)
Esta antes de
el bloque se ejecuta solo una vez antes de todos los demás ejemplos en un archivo de especificaciones.
Describa al Agente, '#enemy' do antes (: all) do @main_villain = Villain.create (nombre: 'Ernst Stavro Blofeld') @mission = Mission.create (nombre: 'Moonraker') @ mission.villains << @main_villain end it 'returns the main enemy Bond has to face in his mission' do agent = Agent.create(name: 'James Bond') @mission.agents << agent expect(agent.enemy).to eq 'Ernst Stavro Blofeld' end… end
Cuando recuerdas las cuatro fases del test., antes de
los bloques a veces son útiles para configurar algo que necesita ser repetido de forma regular, probablemente cosas que son un poco más de meta en la naturaleza.
después de cada)
y después de todo)
tiene el mismo comportamiento pero simplemente se ejecuta después de que se hayan ejecutado las pruebas. después
Se usa a menudo para limpiar sus archivos, por ejemplo. Pero creo que es un poco pronto para abordar eso. Así que memorícelo, sepa que está ahí en caso de que lo necesite y avancemos para explorar otras cosas más básicas..
Todas estas devoluciones de llamada se pueden colocar estratégicamente para satisfacer sus necesidades. Colocarlos en cualquier describir
bloque el alcance que necesita para ejecutarlos, no necesariamente tienen que estar ubicados en la parte superior de su archivo de especificaciones. Se pueden anidar fácilmente dentro de sus especificaciones..
describe Agent do before (: each) do @mission = Mission.create (nombre: 'Moonraker') @bond = Agent.create (nombre: 'James Bond', número: '007') fin de describir '#enemy' do antes (: cada uno) do @main_villain = Villain.create (nombre: 'Ernst Stavro Blofeld') @ mission.villains << @main_villain end describe 'Double 0 Agent with associated mission' do it 'returns the main enemy the agent has to face in his mission' do @mission.agents << @bond expect(@bond.enemy).to eq 'Ernst Stavro Blofeld' end end describe 'Low-level agent with associated mission' do it 'returns no info about the main villain involved' do some_schmuck = Agent.create(name: 'Some schmuck', number: '1024') @mission.agents << some_schmuck expect(some_schmuck.enemy).to eq 'That's above your paygrade!' end end… end end
Como puede observar, puede colocar bloques de devolución de llamada en cualquier ámbito a su gusto e ir tan profundo como lo necesite. El código en la devolución de llamada se ejecutará dentro del alcance de cualquier ámbito de bloque descrito. Pero un poco de consejo: si siente la necesidad de anidar demasiado y las cosas parecen complicarse un poco y volver a pensar, reconsidere su enfoque y considere cómo podría simplificar las pruebas y su configuración. ¡BESO! Mantenlo simple, estúpido. Además, preste atención a lo bien que se lee cuando forzamos a estas pruebas a fallar:
Fallos: 1) Agente # enemigo Doble 0 El agente con una misión asociada devuelve al enemigo principal que el agente tiene que enfrentar en su misión. Fallo / Error: esperar (@ bond.enemy). Hasta eq 'Ernst Stavro Blofeld' esperado: "Ernst Stavro Blofeld "got:" Blofeld "2) Agente # enemigo El agente de bajo nivel con una misión asociada no devuelve ninguna información sobre el villano principal involucrado. Error / Error: esperar (some_schmuck.enemy) .to eq '¡Eso está por encima de tu calificación!' esperado: "¡Eso está por encima de tu calificación!" consiguió: "blofeld"
También echemos un vistazo rápido a los generadores que RSpec proporciona para usted. Ya has visto uno cuando usamos rieles generan rspec: instalar
. Este pequeño amigo hizo que configurar RSpec para nosotros sea rápido y fácil. Qué más tenemos?
rspec: modelo
¿Quieres tener otra especificación de modelo ficticio?
rieles generan rspec: model another_dummy_model
crear especificaciones / modelos / otro_modelo_modelo_spec.rb
Rápido, ¿no es así? O una nueva especificación para una prueba de controlador, por ejemplo:
rspec: controlador
rieles generan rspec: controlador dummy_controller
spec / controllers / dummy_controller_controller_spec.rb
rspec: ver
Lo mismo funciona para las vistas, por supuesto. Sin embargo, no estaremos probando ninguna vista así. Las especificaciones para las vistas le dan la menor inversión, y es totalmente suficiente en casi cualquier escenario para probar indirectamente sus vistas mediante pruebas de características.
Las pruebas de características no son una especialidad de RSpec en sí mismas y son más adecuadas para otro artículo. Dicho esto, si tienes curiosidad, echa un vistazo a Capybara, que es una excelente herramienta para ese tipo de cosas. Le permite probar flujos enteros que ejercitan múltiples partes de su aplicación y probar funciones completas mientras simula la experiencia del navegador. Por ejemplo, un usuario que paga por varios artículos en un carrito de compras.
rspec: ayudante
La misma estrategia de generador nos permite también colocar un ayudante sin mucho alboroto..
rieles generan rspec: ayudante dummy_helper
crear especificaciones / ayudantes / dummy_helper_helper_spec.rb
El doble helper_helper
La parte no fue un accidente. Cuando le demos un nombre más "significativo", verá que RSpec simplemente adjunta _ayudante
por sí mismo.
rieles generan rspec: helper important_stuff
crear especificaciones / helpers / important_stuff_helper_spec.rb
No, este directorio no es un lugar para acumular sus valiosos métodos de ayuda que surgen al refactorizar sus pruebas. Estos irian debajo especificaciones / soporte
, actualmente. especificaciones / ayudantes
es para las pruebas que debe escribir para sus ayudantes: un ayudante como Establece la fecha
Sería un ejemplo común. Sí, la cobertura completa de prueba de su código también debe incluir estos métodos de ayuda. El hecho de que a menudo parezcan pequeños y triviales no significa que debamos pasarlos por alto o ignorar su potencial para los errores que queremos detectar. Cuanto más complejo resulte realmente el ayudante, mayor será la razón por la que deberías escribir un helper_spec
para ello!
En caso de que empiece a jugar con él de inmediato, tenga en cuenta que necesita ejecutar sus métodos de ayuda en una ayudante
Objeto cuando escribes tus pruebas de ayuda para poder trabajar. Así que solo pueden ser expuestos usando este objeto. Algo como esto:
describe '#set_date' do ... helper.set_date ... end ...
Puede usar el mismo tipo de generadores para especificaciones de funciones, especificaciones de integración y especificaciones de correo. Estos están fuera de nuestro alcance para hoy, pero puede guardarlos en la memoria para su uso futuro:
Las especificaciones que creamos a través del generador anterior están listas para usar, y puede agregar las pruebas allí de inmediato. Sin embargo, echemos un vistazo a la diferencia entre las especificaciones:
requiere 'rails_helper' RSpec.describe DummyModel, escribe:: model do pendiente "agrega algunos ejemplos para (o elimina) # __ FILE__" fin
requiere 'rails_helper' RSpec.describe DummyControllerController, escribe:: el controlador termina
requiere 'rails_helper' RSpec.describe DummyHelperHelper, escribe:: helper do "agrega algunos ejemplos para (o elimina) # __ FILE__" fin
No se necesita un niño prodigio para darse cuenta de que todos tienen diferentes tipos. Esta :tipo
Los metadatos de RSpec le dan la oportunidad de dividir y dividir sus pruebas en estructuras de archivos. Puedes apuntar estas pruebas un poco mejor de esa manera. Digamos que quieres tener algún tipo de ayudantes solo cargados para las especificaciones del controlador, por ejemplo. Otro ejemplo sería que desea usar otra estructura de directorios para especificaciones que RSpec no espera. Tener estos metadatos en sus pruebas hace posible continuar usando las funciones de soporte de RSpec y no hacer que el conjunto de pruebas se dispare. Por lo tanto, es libre de usar cualquier estructura de directorios que le sirva si agrega esto :tipo
metadatos.
Sus pruebas estándar de RSpec no dependen de esos metadatos, por otro lado. Cuando use estos generadores, se agregarán de forma gratuita, pero también puede evitarlos totalmente si no los necesita..
También puede utilizar estos metadatos para filtrar en sus especificaciones. Digamos que tienes un bloque anterior que debería ejecutarse solo en las especificaciones del modelo, por ejemplo. ¡Ordenado! Para suites de prueba más grandes, esto podría ser muy útil algún día. Puede filtrar qué grupo enfocado de pruebas desea ejecutar, en lugar de ejecutar toda la suite, lo que puede llevar un tiempo.
Sus opciones se extienden más allá de las tres opciones de etiquetado anteriores, por supuesto. Aprendamos más acerca de cortar y rebanar tus pruebas en la siguiente sección.
Cuando acumula un conjunto de pruebas más grande con el tiempo, no solo será suficiente para ejecutar pruebas en ciertas carpetas para ejecutar las pruebas RSpec de manera rápida y eficiente. Lo que desea poder hacer es ejecutar pruebas que pertenezcan juntas pero que puedan estar distribuidas en varios directorios. ¡Etiquetando al rescate! No me malinterpretes, también es clave organizar tus pruebas de forma inteligente en tus carpetas, pero el etiquetado lleva esto un poco más lejos..
Le está dando a sus pruebas algunos metadatos como símbolos como ": wip", ": checkout", o lo que sea que se ajuste a sus necesidades. Cuando ejecuta estos grupos enfocados de pruebas, simplemente especifique que RSpec debe ignorar la ejecución de otras pruebas esta vez al proporcionar una marca con el nombre de las etiquetas.
describa Agente,: wip do it 'es un desastre en este momento' do expect (agent.favorite_gadgets). to eq 'Unknown' end end
rspec --tag wip
Fallas: 1) El agente es un desastre en este momento. Falla / Error: esperar (agent.favorite_gadgets) .to eq 'Unknown' ...
También puede ejecutar todo tipo de pruebas e ignorar un grupo de grupos que están etiquetados de cierta manera. Simplemente proporciona una tilde (~) delante del nombre de la etiqueta, y RSpec está feliz de ignorar estas pruebas.
rspec --tag ~ wip
Ejecutar múltiples etiquetas de forma sincrónica tampoco es un problema:
rspec --tag wip --tag checkout rspec --tag ~ wip --tag checkout
Como puede ver arriba, puede mezclarlos y combinarlos a voluntad. La sintaxis no se repite a la perfección. --etiqueta
Tal vez no sea lo ideal, pero bueno, ¡tampoco es algo importante! Sí, todo esto es un poco más de trabajo y sobrecarga mental cuando compones las especificaciones, pero por otro lado, realmente te brinda una poderosa capacidad para dividir tu suite de pruebas a pedido. En proyectos más grandes, puede ahorrarle un montón de tiempo de esa manera.
Lo que has aprendido hasta ahora debe equiparte con los fundamentos absolutos para jugar con las pruebas por tu cuenta: un kit de supervivencia para principiantes. Y realmente jugar y cometer errores tanto como puedas. Pruebe a RSpec y toda la prueba guiada por una prueba, y no espere escribir pruebas de calidad de inmediato. Todavía faltan un par de piezas antes de que se sienta cómodo y antes de que sea efectivo con él.
Para mí, esto fue un poco frustrante al principio porque era difícil ver cómo probar algo cuando todavía no lo había implementado y no entendía completamente cómo se comportaría..
La prueba realmente prueba si entiendes un marco como Rails y sabes cómo encajan las piezas. Cuando escriba pruebas, deberá poder escribir las expectativas sobre cómo debe comportarse un marco..
Eso no es fácil si estás empezando con todo esto. Tratar con múltiples lenguajes específicos de dominio, aquí RSpec y Rails, por ejemplo, más aprender el API Ruby puede ser muy confuso. No te sientas mal si la curva de aprendizaje parece desalentadora; será más fácil si te quedas con él. Hacer que esta bombilla se apague no ocurrirá durante la noche, pero para mí, valió la pena el esfuerzo.