Pruebas RSpec para principiantes, Parte 3

En este artículo final sobre los conceptos básicos de RSpec, cubrimos algunas partes dudosas que puede y debe evitar, cómo debe componer sus pruebas, por qué debe evitar la base de datos lo más posible y cómo acelerar su conjunto de pruebas..  

Los temas

  • Prueba de velocidad
  • Cuellos de botella en la base de datos
  • Precargador de primavera
  • Iffy RSpec Conveniences
  • Huéspedes misteriosos
  • Código en línea
  • Métodos de extracción

Ahora que tiene lo básico en su haber, deberíamos tomarnos el tiempo para discutir algunas partes dudosas de RSpec y TDD, algunos problemas que pueden ser fácilmente utilizados y algunos inconvenientes de usar partes del DSL de RSpec sin reflejar. Quiero evitar meter una gran cantidad de conceptos avanzados en sus cerebros TDD recién nacidos, pero siento que es necesario hacer algunos puntos antes de comenzar su primera ola de pruebas. Además, crear un conjunto de pruebas lentas debido a los malos hábitos que se pueden evitar fácilmente es algo que puede mejorar como principiante de inmediato..

Claro, hay muchas cosas con las que necesita adquirir más experiencia antes de sentirse cómodo y eficaz con las pruebas, pero apuesto a que también se sentirá mejor desde el principio si elimina algunas de las mejores prácticas que mejorarán su rendimiento. Especificaciones múltiples sin estirar demasiado tus habilidades en este momento. También es una pequeña ventana a conceptos más avanzados que deberá recoger con el tiempo para "dominar" las pruebas. Siento que no debería molestarte demasiado al principio con esto, ya que puede que te sientas complicado y confuso antes de que hayas desarrollado la imagen más grande que une todo perfectamente..

Prueba de velocidad

Vamos a empezar con la velocidad. Una suite rápida no es nada que pase por accidente; Es una cuestión de mantenimiento "constante". Escuchar sus exámenes con mucha frecuencia es muy importante, al menos si está de acuerdo con TDD y ha estado tomando Kool-Aid por un tiempo, y las suites de pruebas rápidas hacen que sea mucho más razonable prestar atención al lugar que guían las pruebas. tú.

La velocidad de la prueba es algo que debe cuidar bien. Es esencial hacer de las pruebas un hábito regular y mantenerlo divertido. Desea poder ejecutar rápidamente sus pruebas para obtener una retroalimentación rápida durante el desarrollo. Cuanto más tiempo lleve ejercitar el conjunto de pruebas, más probabilidades habrá de que omita cada vez más las pruebas hasta que solo lo haga al final antes de enviar una nueva función.. 

Puede que eso no suene tan mal al principio, pero esto no es un problema trivial. Uno de los principales beneficios de un conjunto de pruebas es que guía el diseño de su aplicación; para mí, esta es probablemente la mayor victoria de TDD. Las pruebas más largas hacen que esta parte sea prácticamente imposible porque es muy probable que no las ejecute para no interrumpir el flujo. Las pruebas rápidas le garantizan que no tiene ninguna razón para no realizar las pruebas.

Puede ver este proceso como un diálogo entre usted y la suite de pruebas. Si esta conversación se vuelve demasiado lenta, es realmente doloroso continuar. Cuando su editor de código ofrece la posibilidad de ejecutar también sus pruebas, definitivamente debe hacer uso de esta función. Esto aumentará dramáticamente la velocidad y mejorará su flujo de trabajo. Cambiar cada vez entre su editor y un shell para ejecutar sus pruebas envejece muy rápidamente. Pero como estos artículos están dirigidos a programadores novatos, no espero que configure sus herramientas de esta manera de inmediato. Hay otras formas en que puede mejorar este proceso sin necesidad de jugar con su editor de inmediato. Sin embargo, es bueno saberlo, y recomiendo que esas herramientas formen parte de su flujo de trabajo..

Además, tenga en cuenta que ya aprendió a dividir sus pruebas y que no necesita ejecutar todo el conjunto de pruebas todo el tiempo. Fácilmente puede ejecutar archivos individuales o incluso solo eso Bloques: todo dentro de un editor de código capaz sin dejarlo nunca para el terminal. Puede enfocar la prueba en la línea bajo prueba, por ejemplo. Eso se siente como magia, para ser franco, nunca se vuelve aburrido.

Cuellos de botella en la base de datos

Escribir demasiado en la base de datos, a menudo de manera muy innecesaria, es una forma segura de ralentizar rápidamente el conjunto de pruebas. En muchos escenarios de prueba, puede falsificar los datos que necesita para configurar una prueba y simplemente concentrarse en los datos que están directamente bajo prueba. La mayoría de las veces, no es necesario que acceda a la base de datos, especialmente no para las partes que no se están probando directamente, y solo respalda la prueba de alguna manera: un usuario registrado mientras prueba la cantidad a pagar a Checkout, por ejemplo. El usuario es como un extra que se puede falsificar.. 

Debes intentar alejarte de no golpear la base de datos tanto como sea posible, ya que esto elimina un gran trozo de un conjunto de pruebas lentas. Además, trate de no configurar demasiados datos si no los necesita en absoluto. Eso puede ser muy fácil de olvidar, especialmente con las pruebas de integración. Las pruebas unitarias a menudo están mucho más enfocadas por definición. Esta estrategia será muy eficaz para evitar la ralentización de las suites de pruebas con el tiempo. Elija sus dependencias con mucho cuidado y vea cuál es la cantidad más pequeña de datos que hace que pasen sus pruebas.

No quiero entrar en más detalles por ahora, probablemente sea un poco demasiado temprano en tu trayectoria para hablar sobre talones, espías, falsificaciones y esas cosas. Confundirlo aquí con conceptos tan avanzados parece contraproducente, y pronto se encontrará con estos. Existen muchas estrategias para las pruebas rápidas que también involucran otras herramientas además de RSpec. Por ahora, trate de envolver su cabeza alrededor de la imagen más grande con RSpec y las pruebas en general.

También quieres apuntar a probar todo solo una vez, si es posible. No vuelva a realizar la misma prueba una y otra vez, es un desperdicio. Esto sucede principalmente por accidente y / o malas decisiones de diseño. Si empezaste a tener pruebas que son lentas, este es un lugar fácil de refactorizar para obtener un aumento de velocidad.

La mayoría de sus pruebas también deben estar en el nivel de Unidad, probando sus modelos. Esto no solo mantendrá las cosas rápidas, sino que también le proporcionará la mejor inversión. Las pruebas de integración que prueban flujos de trabajo completos, imitando el comportamiento del usuario hasta cierto punto al reunir un grupo de componentes y probarlos de forma síncrona, deberían ser la parte más pequeña de su pirámide de pruebas. Estos son bastante lentos y "caros". Tal vez el 10% de tus pruebas generales no sea irreal, pero esto depende, supongo.

Hacer que la base de datos sea lo menos posible puede ser difícil porque necesita aprender muchas más herramientas y técnicas para lograrlo de manera efectiva, pero es esencial hacer crecer las suites de pruebas que sean lo suficientemente rápidas y lo suficientemente rápidas como para ejecutar las pruebas con frecuencia..

Precargador de primavera

El servidor Spring es una característica de Rails y precarga su aplicación. Debo agregar que esta es otra estrategia sencilla para aumentar la velocidad de la prueba de manera significativa, desde el primer momento. Lo que hace es simplemente mantener su aplicación ejecutándose en segundo plano sin necesidad de iniciarla con cada ejecución de prueba. Lo mismo se aplica a las tareas de Rake y migraciones; estos correrán más rápido también.

Desde Rails 4.1, Spring se ha incluido en Rails, que se agrega automáticamente al Gemfile, y no necesita hacer mucho para iniciar o detener este preloader. En el pasado, tuvimos que conectar nuestras propias herramientas de elección para esto, lo cual aún puede hacer si tiene otras preferencias. Lo que es realmente bueno y reflexivo es que se reiniciará automáticamente si cambias algunas gemas, inicializadores o archivos de configuración, un toque agradable y práctico porque es fácil olvidarte de cuidarlo tú mismo..

Por defecto está configurado para ejecutarse. rieles y rastrillo solo comandos Así que tenemos que configurarlo para que también se ejecute con el rspec Comando para ejecutar nuestras pruebas. Puedes preguntar por el estado de la primavera así:

Terminal

estado de primavera

Salida

La primavera no esta corriendo.

Como la salida nos dijo que Spring no se está ejecutando, simplemente inicie con servidor de primavera. Cuando corres ahora estado de primavera, Deberías ver algo similar a esto:

Salida

La primavera se está ejecutando: 3738 servidor de la primavera | rspec-dummy | comenzó hace 21 segundos

Ahora deberíamos comprobar qué primavera está configurada para precargar.

Terminal

binstub de primavera --todos

Salida

* bandeja / rastrillo: resorte ya presente * bandeja / rieles: resorte ya presente

Esto nos dice que Spring está cargando Rails para rastrillo y rieles Comandos, y nada más hasta ahora. Que tenemos que cuidar. Necesitamos añadir la gema. primavera-comandos-rspec, y nuestras pruebas están listas para ser cargadas previamente..

Gemfile

gema 'spring-commands-rspec', grupo:: desarrollo

Terminal

paquete de instalación paquete exec resorte binstub rspec

Te ahorro la salida de instalación de paquete; Estoy seguro de que ya has visto más de lo que te corresponde. Corriendo paquete exec primavera binstub rspec, por otro lado, genera un bin / rspec Archivo que básicamente lo agrega para ser cargado por Spring. A ver si esto funcionó:

Terminal

binstub de primavera --todos

Esto creó algo llamado binstub-un contenedor para ejecutables como rieles, rastrillo, haz, rspec y tal, para que cuando se utiliza el rspec comando que utilizará primavera. Además, tales binstubs aseguran que está ejecutando estos ejecutables en el entorno correcto. También le permiten ejecutar estos comandos desde todos los directorios de su aplicación, no solo desde la raíz. La otra ventaja de binstubs es que no tienes que prepend paquete exec con todo.

Salida

* bin / rastrillo: resorte ya presente * bin / rspec: resorte ya presente * bin / rieles: resorte ya presente

Parece A-OK! Detengamos y reinicie el servidor Spring antes de continuar:

Terminal

parada de primavera servidor de primavera

Así que ahora ejecuta el servidor Spring en una ventana de terminal dedicada, y ejecuta sus pruebas con una sintaxis ligeramente diferente en otra. Simplemente necesitamos prefijar cada ejecución de prueba con la primavera mando:

Terminal

especificación de primavera rspec

Esto ejecuta todos sus archivos de especificaciones, por supuesto. Pero no hay necesidad de parar allí. También puede ejecutar archivos individuales o pruebas etiquetadas a través de Spring, ¡no hay problema! Y todos serán rápidos como un rayo; En las suites de prueba más pequeñas realmente parecen casi instantáneas. Además de eso, puedes usar la misma sintaxis para tu rieles y rastrillo comandos Bien eh?

Terminal

Rastrillos de resorte Rieles de resorte Modelo G Nombre de la aerolínea: Cadena Rastrillo de primavera Db: Migrar 

Entonces, sacamos a Spring fuera de la caja para acelerar las cosas en Rails, pero no debemos olvidar agregar esta pequeña gema para que Spring sepa cómo jugar a la pelota con RSpec..

Iffy RSpec Conveniences

Las cosas que se mencionan en esta sección son probablemente buenas para evitar siempre que pueda encontrar otra solución para ellas. El uso excesivo de algunas de las conveniencias de RSpec puede llevar a desarrollar malos hábitos de prueba, por lo menos dudosos. Lo que discutiremos aquí es conveniente en la superficie, pero podríamos morderte un poco más adelante en el camino.

No deben ser considerados como antipatrices, cosas que deben evitarse de inmediato, sino más bien como "olores", cosas que debe tener cuidado y que pueden generar un costo significativo que a menudo no desea pagar. El razonamiento para esto involucra algunas ideas y conceptos más con los que usted, como principiante, probablemente no esté familiarizado todavía y, francamente, podría estar un poco por encima de su cabeza en este momento, pero al menos debería enviarlo a casa con algunos Banderas rojas para pensar y comprometerse con la memoria por ahora..

  • dejar

Tener un montón de dejar Las referencias pueden parecer muy convenientes al principio, especialmente porque se SECAN las cosas bastante. Parece una extracción razonablemente buena al principio para tenerlos en la parte superior de sus archivos, por ejemplo. Por otro lado, pueden fácilmente dificultarle la comprensión de su propio código si visita pruebas específicas una cantidad significativa de tiempo más tarde. No tener los datos configurados dentro de su dejar Los bloques no ayudan demasiado a la comprensión de tus pruebas. Eso no es tan trivial como puede parecer al principio, especialmente si hay otros desarrolladores involucrados que también necesitan leer su trabajo..

Este tipo de confusión se vuelve mucho más costosa cuanto más involucrados están los desarrolladores. No solo requiere mucho tiempo si tienes que cazar dejar referencias una y otra vez, también es estúpido porque se podría haber evitado con muy poco esfuerzo. La claridad es rey, no hay duda al respecto. Otro argumento para mantener estos datos en línea es que su conjunto de pruebas será menos frágil. No quieres construir una casa de naipes que se vuelve más inestable con cada dejar que es ocultar los detalles de cada prueba. Probablemente aprendió que usar variables globales no es una buena idea. En ese sentido, dejar es semi-global dentro de sus archivos de especificaciones.

Otro problema es que tendrá que probar muchas variaciones diferentes, diferentes estados para escenarios similares. Pronto te quedarás sin un nombre razonable dejar declaraciones para cubrir todas las diferentes versiones que pueda necesitar, o terminar con un montón de toneladas de variaciones de estado con nombres similares. Cuando configura los datos en cada prueba directamente, no tiene ese problema. Las variables locales son baratas, muy legibles y no se meten con otros ámbitos. De hecho, pueden ser aún más expresivos porque no necesita considerar toneladas de otras pruebas que podrían tener un problema con un nombre en particular. Desea evitar crear otro DSL en la parte superior del marco que todos necesitan descifrar para cada prueba que se esté utilizando dejar. Espero que se sienta como una pérdida de tiempo para todos..

  • antes de Y después

Guardar cosas como antes de, después y sus variaciones para ocasiones especiales y no lo use todo el tiempo, por todas partes. Míralo como una de las grandes armas que sacas para las cosas meta. La limpieza de sus datos es un buen ejemplo que es demasiado meta para cada prueba individual para tratar. Usted quiere extraer eso, por supuesto..

Huéspedes misteriosos

A menudo pones el dejar las cosas en la parte superior de un archivo y oculte estos detalles de otras pruebas que los utilizan para bajar el archivo. Desea tener la información y los datos relevantes lo más cerca posible de la parte en la que realmente realiza la prueba, no a kilómetros de distancia, lo que hace que las pruebas individuales sean más oscuras. 

Al final, se siente como demasiada cuerda para ahorcarte, porque dejar introduce accesorios ampliamente compartidos. Eso básicamente se descompone en datos de prueba ficticios cuyo alcance no es lo suficientemente ajustado. 

Esto conduce fácilmente a un olor principal llamado "invitado misterioso". Eso significa que tiene datos de prueba que aparecen de la nada o simplemente se están asumiendo. A menudo, primero tendrá que cazarlos para comprender una prueba, especialmente si ha pasado algún tiempo desde que escribió el código o si es nuevo en una base de código. Es mucho más efectivo definir los datos de prueba en línea exactamente donde los necesita: en la configuración de una prueba en particular y no en un ámbito mucho más amplio.

Especificación del agente

… Describa el Agente, '#print_favorite_gadget' do it 'imprime el nombre, rango y dispositivo favorito de los agentes' do expect (agent.print_favorite_gadget) .to eq ('Commander Bond tiene una cosa para Aston Martins') end end end end end

Cuando miras esto, se lee bastante bien, ¿verdad? Es sucinto, de una sola línea, bastante limpio, ¿no? No nos engañemos a nosotros mismos. Esta prueba no nos dice mucho sobre el agente En cuestión, y no nos cuenta toda la historia. Los detalles de la implementación son importantes, pero no estamos viendo nada de eso. El agente parece haber sido creado en otro lugar, y tendríamos que buscarlo primero para comprender completamente lo que está sucediendo aquí. Tal vez se vea elegante en la superficie, pero tiene un precio alto.

Sí, es posible que sus exámenes no terminen siendo súper SECOS todo el tiempo, pero creo que este es un pequeño precio por ser más expresivo y más fácil de entender. Claro que hay excepciones, pero en realidad deberían aplicarse meramente a circunstancias excepcionales después de haber agotado las opciones que ofrece Ruby puro de inmediato.. 

Con un invitado misterioso, tienes que descubrir de dónde provienen los datos, por qué son importantes y cuáles son sus características específicas. No ver los detalles de la implementación en una prueba en particular hace que su vida sea más difícil de lo que debe ser. Quiero decir, haz lo que sientes si trabajas en tus propios proyectos, pero cuando otros desarrolladores estén involucrados, sería mejor pensar en hacer que su experiencia con tu código sea lo más fluida posible.. 

Como con muchas otras cosas, por supuesto, lo esencial está en los detalles, y no querrás mantenerte a ti mismo y a los demás en la oscuridad acerca de ellos. La legibilidad, la brevedad y la conveniencia de dejar no debe tener el costo de perder claridad sobre los detalles de la implementación y la mala dirección. Desea que cada prueba individual cuente la historia completa y proporcione todo el contexto para entenderla de inmediato..

Código en línea

En pocas palabras, usted quiere tener pruebas que sean fáciles de leer y más fáciles de razonar sobre una base de prueba por prueba. Intente especificar todo lo que necesita en la prueba real, y no más que eso. Este tipo de desperdicio comienza a "oler" como cualquier otro tipo de basura. Eso también implica que debe agregar los detalles que necesita para pruebas específicas lo más tarde posible, cuando cree datos de prueba en general, dentro del escenario real y no en un lugar remoto. El uso sugerido de dejar Ofrece una comodidad diferente que parece oponerse a esta idea..

Tengamos otro ejemplo con el ejemplo anterior e implementarlo sin el problema del invitado misterioso. En la solución a continuación, encontraremos toda la información relevante para la prueba en línea. Podemos mantenernos en esta especificación si falla y no es necesario buscar información adicional en otro lugar.

Especificación del agente

… Describa Agente, '#print_favorite_gadget' do it 'imprime el nombre, rango y dispositivo favorito de los agentes' do agent = Agent.new (nombre: 'James Bond', rango: 'Comandante', favorite_gadget: 'Aston Martin') espera (agent.print_favorite_gadget) .to eq ('El comandante Bond tiene algo para el final de Aston Martins')

Sería bueno si dejar le permite configurar datos de pruebas barebones que podría mejorar según las necesidades de cada prueba específica, pero no es así dejar está rodando Así es como usamos las fábricas a través de Factory Girl en estos días.. 

Te ahorraré los detalles, sobre todo porque ya he escrito algunos artículos al respecto. Aquí están mis artículos 101 y 201 diseñados para novatos sobre lo que Factory Girl tiene para ofrecer, si ya tiene curiosidad sobre eso. Está escrito para desarrolladores sin mucha experiencia..

Veamos otro ejemplo simple que hace un buen uso de los datos de prueba compatibles que se configuran en línea:

Especificación del agente

describir Agente, '#current_mission' do it 'imprime el estado actual de la misión del agente y su objetivo' do mission_octopussy = Mission.new (nombre: 'Octopussy', objetivo: 'Stop bad white guy') bond = Agent.new (nombre : 'James Bond', estado: 'Operación encubierta', sección: '00', licence_to_kill: true) bond.missions << mission_octopussy expect(bond.current_mission).to eq ('Agent Bond is currently engaged in an undercover operation for mission Octopussy which aims to stop bad white dude') end end

Como puede ver, tenemos toda la información que esta prueba necesita en un solo lugar y no necesitamos buscar información específica en otro lugar. Cuenta una historia y no es oscura. Como se mencionó, esta no es la mejor estrategia para el código DRY. Sin embargo, la recompensa es buena. La claridad y la legibilidad superan este pequeño código repetitivo por un tiro largo, especialmente en grandes bases de código.

Por ejemplo, digamos que escribes una característica nueva, aparentemente no relacionada, y de repente esta prueba comienza a fallar como daño colateral y no has tocado este archivo de especificaciones en mucho tiempo. 

¿Crees que estarás contento si primero necesitas descifrar los componentes de la configuración para comprender y corregir esta prueba fallida antes de poder continuar con una función completamente diferente en la que estás trabajando? ¡Yo creo que no! Desea salir de esta especificación "no relacionada" lo antes posible y volver a terminar la otra característica. 

Cuando encuentra todos los datos de las pruebas allí donde las pruebas le indican dónde falla, aumenta las posibilidades de solucionar esto rápidamente sin “descargar” una parte completamente diferente de la aplicación en su cerebro.

Métodos de extracción

Puede limpiar y SECAR su código significativamente escribiendo sus propios métodos de ayuda. No hay necesidad de usar RSpec DSL para algo tan barato como un método Ruby. 

Digamos que encontraste un par de accesorios repetitivos que están empezando a sentirse un poco sucios. En lugar de ir con un dejar o un tema, define un método en la parte inferior de un bloque de descripción, una convención, y extrae los elementos comunes en él. Si se usa un poco más ampliamente dentro de un archivo, también puede colocarlo en la parte inferior del archivo.. 

Un efecto secundario agradable es que no está tratando con ninguna variable semi-global de esa manera. También se salvará de hacer un montón de cambios en todo el lugar si necesita modificar los datos un poco. Ahora puede ir a un lugar central donde se define el método y afectar a todos los lugares donde se usa a la vez. No está mal!

Especificación del agente

describir Agente, '#current_status' do it 'especula sobre la elección del destino del agente si el estado es vacation' do bond = Agent.new (nombre: 'James Bond', estado: 'On vacation', sección: '00', licence_to_kill : verdadero) expect (bond.current_status) .to eq ('Commander Bond está de vacaciones, probablemente en las Bahamas') finaliza 'especula sobre la elección del destino del intendente si el estado es de vacaciones' do q = Agent.new (nombre: 'Q', estado: 'En vacaciones', sección: '00', licence_to_kill: true) expect (q.current_status) .to eq ('El intendente está de vacaciones, probablemente en DEF CON') fin

Como puede ver, hay un poco de código de configuración repetitivo, y queremos evitar escribir esto una y otra vez. En su lugar, solo queremos ver lo esencial para esta prueba y hacer que un método construya el resto del objeto para nosotros.

Especificación del agente

describir Agente, '#current_status' do it 'especula sobre la elección del destino del agente si el estado es vacation' do bond = build_agent_on_vacation ('James Bond', 'On vacation') expect (bond.current_status) .to eq ('Commander Bond está de vacaciones, probablemente en las Bahamas 'fin' especula sobre la elección del destino del intendente si el estado es vacaciones 'do q = build_agent_on_vacation (' Q ',' On Vacation ') expect (q.current_status) .to eq (' El intendente está de vacaciones, probablemente en DEF CON ') end def build_agent_on_vacation (nombre, estado) Agent.new (name: name, status: status, sección:' 00 ', licence_to_kill: true) end end

Ahora nuestro método extraído se encarga de la sección y licencia cosas y por lo tanto no nos distrae de lo esencial de la prueba. Por supuesto, este es un ejemplo ficticio, pero puede escalar su complejidad tanto como lo necesite. La estrategia no cambia. Es una técnica de refactorización muy simple, por eso la introduzco tan pronto, pero una de las más efectivas. Además, es casi imposible evitar las herramientas de extracción que ofrece RSpecs..

A lo que también debe prestar atención es a qué tan expresivos pueden ser estos métodos de ayuda sin tener que pagar un precio adicional.

Pensamientos finales

Evitar algunas partes de la DSL de RSpec y hacer un buen uso de los principios de programación orientados a objetos de Ruby y Good es una buena manera de abordar sus pruebas. Puedes usar libremente lo esencial., describir, contexto y eso, por supuesto. 

Encuentre una buena razón para usar otras partes de RSpec y evítelas todo el tiempo que pueda. Solo porque las cosas puedan parecer convenientes y la fantasía no es una razón suficiente para usarlas, es mejor mantener las cosas más simples. 

Lo simple es bueno; Mantiene tus pruebas saludables y rápidas.