Probando tu código Ruby con Guardia, RSpec & Pry

Mi trabajo reciente ha sido en un proyecto Ruby basado en la nube para la BBC News, las próximas elecciones de 2014. Requiere I / O rápido, escalabilidad y necesita ser bien probado. El requisito de "estar bien probado" es lo que quiero enfocar en este tutorial.

Introducción

Este proyecto utiliza algunos servicios diferentes de Amazon como:

  • SQS (Servicio de Cola Simple)
  • DynamoDB (almacén de clave / valor)
  • S3 (Servicio de almacenamiento simple)

Necesitamos poder escribir pruebas que sean rápidas y darnos retroalimentación instantánea sobre problemas con nuestro código.

Aunque no usaremos los servicios de Amazon en este tutorial, los menciono porque para que tengamos pruebas rápidas, es necesario que falsifiquemos estos objetos externos (por ejemplo, no deberíamos necesitar una conexión de red para ejecutar nuestra pruebas, porque esa dependencia puede resultar en pruebas de ejecución lenta).

Junto con el líder en tecnología Robert Kenny (que es muy versado en la escritura de aplicaciones Ruby basadas en TDD (desarrollo impulsado por pruebas)), hemos estado utilizando diferentes herramientas que han hecho este proceso y nuestra programación mucho más fácil..

Quiero compartir contigo información sobre estas herramientas..

Las herramientas que estaré cubriendo son:

  • RSpec (marco de prueba)
  • Guardia (corredor de tareas)
  • Pry (REPL y depuración)

¿Qué necesito saber por adelantado??

Supondré que está familiarizado con el código de Ruby y el ecosistema de Ruby. Por ejemplo, no debería tener que explicarte qué son las 'gemas' o cómo funcionan ciertos conceptos / sintaxis de Ruby.

Si no estás seguro, antes de continuar, recomendaría leer uno de mis otros mensajes en Ruby para estar al día..

Guardia

Puede que no esté familiarizado con Guard, pero en esencia es una herramienta de línea de comandos que utiliza Ruby para manejar diferentes eventos.

Por ejemplo, Guard puede notificarle cada vez que se han editado archivos específicos y puede realizar alguna acción en función del tipo de archivo o evento que se disparó.

Esto se conoce como un 'corredor de tareas', es posible que haya escuchado la frase antes, ya que en este momento se están usando mucho en el mundo de cliente / cliente (Grunt y Gulp son dos ejemplos populares).

La razón por la que usaremos Guard es porque ayuda a que el circuito de retroalimentación (cuando se hace TDD) sea mucho más estricto. Nos permite editar nuestros archivos de prueba, ver una prueba fallida, actualizar y guardar nuestro código y ver de inmediato si pasa o falla (según lo que escribimos).

Podría usar algo como Grunt o Gulp en su lugar, pero preferimos usar esos tipos de corredores de tareas para manejar las cosas de front-end / cliente. Para el código de back-end / servidor, usamos Rake and Guard.

RSpec

RSpec, si aún no lo sabía, es una herramienta de prueba para el lenguaje de programación Ruby.

Ejecuta sus pruebas (utilizando RSpec) a través de la línea de comandos y demostraré cómo puede facilitar este proceso mediante el uso del programa de construcción de Ruby, Rake.

Palanca

Por último, usaremos otra gema Ruby llamada Pry, que es una herramienta de depuración de Ruby extremadamente poderosa que se inyecta en su aplicación, mientras se ejecuta, para permitirle inspeccionar su código y descubrir por qué algo no funciona..

TDD (Desarrollo Dirigido por Pruebas)

Aunque no es necesario para demostrar el uso de RSpec y Guard, vale la pena señalar que respaldo completamente el uso de TDD como un medio para garantizar que cada línea de código que escriba tenga un propósito y que haya sido diseñada de manera comprobable y confiable.

Detallaré cómo haríamos TDD con una aplicación simple, por lo que al menos obtendrá una idea de cómo funciona el proceso..

Creando un proyecto de ejemplo

He creado un ejemplo básico en GitHub para evitar que tengas que escribir todo tú mismo. Siéntete libre de descargar el código.. 

Sigamos adelante y revisemos este proyecto, paso a paso..

Archivos primarios

Hay tres archivos principales requeridos para que funcione nuestra aplicación de ejemplo, estos son:

  1. Gemfile
  2. Guardfile
  3. Rakefile

Revisaremos el contenido de cada archivo en breve, pero lo primero que debemos hacer es instalar nuestra estructura de directorios..

Estructura de directorios

Para nuestro proyecto de ejemplo, necesitaremos dos carpetas creadas:

  • lib (esto mantendrá nuestro código de aplicación)
  • especulación (esto mantendrá nuestro código de prueba)

Este no es un requisito para su aplicación, puede modificar fácilmente el código de nuestros otros archivos para que funcione con la estructura que más le convenga..

Instalación

Abre tu terminal y ejecuta el siguiente comando:

paquete de instalación de gemas

Bundler es una herramienta que facilita la instalación de otras gemas..

Una vez que haya ejecutado ese comando, cree los tres archivos anteriores (Gemfile, Guardfile y Rakefile).

Gemfile

los Gemfile Es responsable de definir una lista de dependencias para nuestra aplicación..

Esto es lo que parece:

fuente "https://rubygems.org" gem 'rspec' group: desarrollo do gema 'guard' gem 'guard-rspec' gem 'pry' end 

Una vez guardado este archivo, ejecute el comando instalación de paquete.

Esto instalará todas nuestras gemas para nosotros (incluidas las gemas especificadas en el desarrollo grupo).

El propósito de desarrollo El grupo (que es una característica específica del agrupador) es así cuando implementa su aplicación, puede decirle a su entorno de producción que instale solo las gemas que se requieren para que su aplicación funcione correctamente..

Así, por ejemplo, todas las gemas dentro de la desarrollo grupo, no son necesarios para que la aplicación funcione correctamente. Se utilizan únicamente para ayudarnos mientras estamos desarrollando y probando nuestro código..

Para instalar las gemas adecuadas en su servidor de producción, necesitaría ejecutar algo como:

instalación de paquetes - sin desarrollo

Rakefile

los Rakefile nos permitirá ejecutar nuestras pruebas RSpec desde la línea de comandos.

Esto es lo que parece:

requiere 'rspec / core / rake_task' RSpec :: Core :: RakeTask.new do | task | task.rspec_opts = ['--color', '--format', 'doc'] end 

Nota: no necesita Guard para poder ejecutar sus pruebas RSpec. Usamos Guard para que sea más fácil hacer TDD..

Cuando instala RSpec, le da acceso a una tarea integrada de Rake y eso es lo que estamos usando aquí.

Creamos una nueva instancia de RakeTask que por defecto crea una tarea llamada especulación que buscará una carpeta llamada especulación y ejecutará todos los archivos de prueba dentro de esa carpeta, usando las opciones de configuración que hemos definido.

En este caso, queremos que nuestra salida de shell tenga color y queremos formatear la salida a la Doc estilo (puede cambiar el formato para que sea anidado como ejemplo).

Puede configurar la tarea Rake para que funcione de la manera que desee y para buscar en diferentes directorios, si eso es lo que tiene. Pero la configuración predeterminada funciona muy bien para nuestra aplicación y eso es lo que usaremos.

Ahora, si quiero ejecutar las pruebas en mi repositorio de GitHub de ejemplo, entonces necesito abrir mi terminal y ejecutar el comando:

rastrillo espec

Esto nos da la siguiente salida:

rake spec / bin / ruby ​​-S rspec ./spec/example_spec.rb --color --format doc RSpecGreeter RSpecGreeter # greet () Terminado en 0.0006 segundos 1 ejemplo, 0 fallos 

Como puedes ver hay cero fallos. Esto se debe a que, aunque no tenemos un código de aplicación escrito, tampoco tenemos un código de prueba escrito..

Guardfile

El contenido de este archivo le dice a Guard qué hacer cuando ejecutamos el Guardia mando:

guard 'rspec' do # watch / lib / files watch (% r ^ lib /(.+). rb $) do | m | "spec / # m [1] _ spec.rb" end # watch / spec / files watch (% r ^ spec /(.+). rb $) do | m | "spec / # m [1]. rb" end end 

Te habrás dado cuenta dentro de nuestra Gemfile especificamos la gema: guardia-rspec. Necesitamos esa gema para que Guard comprenda cómo manejar los cambios en los archivos relacionados con RSpec.

Si miramos nuevamente el contenido, podemos ver que si corremos guardia rspec luego, Guard vería los archivos especificados y ejecutaría los comandos especificados una vez que se hubieran producido cambios en esos archivos.

Nota: porque solo tenemos una tarea de guardia, rspec, entonces eso se ejecuta por defecto si ejecutamos el comando Guardia.

Puedes ver que Guard nos proporciona una reloj función por la que pasamos una Expresión regular que nos permite definir qué archivos estamos interesados ​​en la vigilancia de Guard.

En este caso, le estamos diciendo a Guard que mire todos los archivos dentro de nuestro lib y especulación carpetas y si se produce algún cambio en cualquiera de esos archivos, ejecute los archivos de prueba en nuestro especulación carpeta para asegurarnos de que ningún cambio que hicimos rompió nuestras pruebas (y posteriormente no rompió nuestro código).

Si tiene todos los archivos descargados del repositorio de GitHub, puede probar el comando por sí mismo..

correr Guardia y luego guarda uno de los archivos para verlo ejecutar las pruebas.

Código de prueba

Antes de comenzar a ver algunos códigos de prueba y aplicación, permítame explicarle lo que va a hacer nuestra aplicación. Nuestra aplicación es una clase única que devolverá un mensaje de saludo a quien esté ejecutando el código.

Nuestros requisitos se simplifican a propósito, ya que hará que el proceso que estamos por emprender sea más fácil de entender..

Veamos ahora una especificación de ejemplo (por ejemplo, nuestro archivo de prueba) que describirá nuestros requisitos. Después de eso, comenzaremos a recorrer el código definido en la especificación y veremos cómo podemos usar TDD para ayudarnos a escribir nuestra aplicación..

Nuestra primera prueba

Vamos a crear un archivo titulado. example_spec.rb. El propósito de este archivo es convertirse en nuestro archivo de especificación (en otras palabras, este será nuestro código de prueba y representará la funcionalidad esperada).

La razón por la que escribimos nuestro código de prueba antes de escribir nuestro código de aplicación real es porque, en última instancia, significa que cualquier código de aplicación que produzcamos existirá porque realmente se usó.

Ese es un punto importante que estoy haciendo y, por lo tanto, permítame tomarme un momento para aclararlo con más detalle..

Escribir código de prueba antes del código de aplicación

Por lo general, si primero escribe el código de su aplicación (por lo que no está haciendo TDD), entonces se encontrará escribiendo un código que, en algún momento en el futuro, está sobre diseñado y potencialmente obsoleto. A través del proceso de refactorización o cambio de requisitos, es posible que algunas funciones dejen de llamarse.

Esta es la razón por la que se considera que TDD es la mejor práctica y el método de desarrollo preferido que se debe usar, porque cada línea de código que produce se ha producido por una razón: para obtener una especificación fallida (su requisito comercial real). Eso es algo muy poderoso para tener en cuenta.

Aquí está nuestro código de prueba:

requiere 'spec_helper' describe 'RSpecGreeter' hazlo 'RSpecGreeter # greet ()' do greeter = RSpecGreeter.new # Given greeting = greeter.greet # When greeting.should eq ('Hello RSpec!') # Fin final 

Puede observar los comentarios del código al final de cada línea:

  • Dado
  • Cuando
  • Entonces

Esta es una forma de terminología BDD (Behavior-Driven Development). Los incluí para lectores que están más familiarizados con BDD (Behavior-Driven Development) y que estaban interesados ​​en cómo pueden equiparar estas afirmaciones con TDD..

Lo primero que hacemos dentro de este archivo es cargar. spec_helper.rb (que se encuentra en el mismo directorio que nuestro archivo de especificaciones). Regresaremos y veremos el contenido de ese archivo en un momento..

A continuación tenemos dos bloques de código que son específicos de RSpec:

  • describe 'x' hacer
  • es 'y' hacer

El primero describir el bloque debe describir adecuadamente la clase / módulo específico en el que estamos trabajando y para el que proporcionamos pruebas. Podrías tener múltiples describir bloques dentro de un único archivo de especificación.

Hay muchas teorías diferentes sobre cómo usar describir y eso bloques de descripcion Personalmente prefiero la simplicidad y, por lo tanto, usaré los identificadores para la Clase / Módulos / Métodos que probaremos. Pero a menudo hay personas que prefieren usar oraciones completas para sus descripciones. Ni lo correcto ni lo incorrecto, solo la preferencia personal.

los eso El bloque es diferente y siempre se debe colocar dentro de un describir bloquear. Debería explicar cómo Queremos que nuestra aplicación funcione..

Nuevamente, podría usar una oración normal para describir los requisitos, pero he encontrado que a veces hacerlo puede hacer que las descripciones sean demasiado explícitas, cuando realmente deberían ser más implícito. Ser menos explícito reduce las posibilidades de cambios en su funcionalidad, lo que hace que su descripción quede desactualizada (tener que actualizar su descripción cada vez que ocurren cambios menores en la funcionalidad es más una carga que una ayuda). Al usar el identificador del método que estamos probando (por ejemplo, el nombre del método que estamos ejecutando) podemos evitar ese problema.

Los contenidos de la eso Bloquear es el código que vamos a probar..

En el ejemplo anterior, creamos una nueva instancia de la clase RSpecGreeter (que aún no existe). Enviamos el mensaje saludar (que tampoco existe todavía) al objeto creado una instancia (Nota: estas dos líneas son código estándar de Ruby en este punto).

Finalmente, le decimos al marco de pruebas que esperamos el resultado de llamar a la saludar Método para ser el texto "Hola RSpec!", usando la sintaxis RSpec: eq (algo).

Observe cómo la sintaxis permite que sea leída fácilmente (incluso por una persona no técnica). Estos son conocidos como afirmaciones.

Hay muchas aserciones diferentes de RSpec y no entraremos en detalles, pero siéntase libre de revisar la documentación para ver todas las características que proporciona RSpec.

Creando un Ayudante para Nuestra Prueba

Hay una cierta cantidad de repetición requerida para que se ejecuten nuestras pruebas. En este proyecto, solo tenemos un archivo de especificación, pero en un proyecto real es probable que tengas docenas (dependiendo del tamaño de tu aplicación).

Para ayudarnos a reducir el código de repetición, lo colocaremos dentro de un archivo de ayuda especial que cargaremos de nuestros archivos de especificaciones. Este archivo se titulará spec_helper.rb.

Este archivo hará un par de cosas:

  • Dile a Ruby dónde se encuentra nuestro código de aplicación principal
  • cargar nuestro código de aplicación (para que las pruebas se ejecuten)
  • carga el palanca gema (nos ayuda a depurar nuestro código; si es necesario).

Aquí está el código:

PS << File.join(File.dirname(FILE), '… ', 'lib') require 'pry' require 'example' 

Nota: la primera línea puede parecer un poco críptica, así que déjame explicarte cómo funciona. Aquí estamos diciendo que queremos añadir el / lib / carpeta a Ruby $ LOAD_PATH variable del sistema. Cuando Ruby evalúe requiere 'algún_archivo' tiene una lista de directorios que intentará localizar ese archivo. En este caso, nos aseguramos de que si tenemos el código requiere 'ejemplo' que Ruby podrá localizarlo porque comprobará nuestra / lib / directorio y allí, encontrará el archivo especificado. Este es un truco inteligente que verás usar en muchas gemas de Ruby, pero puede ser bastante confuso si nunca lo has visto antes..

Código de solicitud

Nuestro código de aplicación va a estar dentro de un archivo titulado example.rb.

Antes de comenzar a escribir cualquier código de aplicación, recuerde que estamos haciendo este proyecto TDD. Así que vamos a dejar que las pruebas en nuestro archivo de especificaciones nos guíen sobre qué hacer primero.

Vamos a empezar asumiendo que estás usando Guardia para ejecutar sus pruebas (por lo que cada vez que hacemos un cambio a example.rb, Guardia notará el cambio y procederá a correr. example_spec.rb para asegurarnos de que nuestras pruebas pasan.

Para que hagamos TDD correctamente, nuestra example.rb El archivo estará vacío, por lo que si lo abrimos y lo guardamos en su estado actual, Guard ejecutará y descubriremos (como es de esperar) que nuestra prueba fallará:

Fallos: 1) RSpecGreeter RSpecGreeter # greet () Failure / Error: greeter = RSpecGreeter.new # Given NameError: constante no inicializada RSpecGreeter # ./spec/example_spec.rb:5:inbloque (2 niveles) en 'Terminado en 0.00059 segundos 1 ejemplo, 1 error Ejemplos fallidos: rspec ./spec/example_spec.rb:4 # RSpecGreeter RSpecGreeter # greet ()

Ahora, antes de continuar, permítanme aclarar de nuevo que TDD se basa en la premisa de que cada línea de código tiene una razón para existir, por lo que no hacer Comience a correr por delante y escriba más código del que necesita. Solo escriba la cantidad mínima de código requerida para que la prueba pase. Incluso si el código es feo o no cumple la funcionalidad completa.

El punto de TDD es tener un apretado Bucle de retroalimentación, También conocido como 'rojo, verde, refactor'). Lo que esto significa en la práctica es:

  • escribe una prueba que falla
  • Escribe la menor cantidad de código para que pase
  • refactorizar el código

Verá en un momento que debido a que nuestros requisitos son tan simples, no hay necesidad de refactorizarlos. Pero en un proyecto real con requisitos mucho más complejos, es probable que tenga que dar el tercer paso y refactorizar el código que ingresó para que la prueba sea aprobada..


Volviendo a nuestra prueba fallida, como puede ver en el error, no hay RSpecGreeter clase definida Vamos a arreglar eso, agregar el siguiente código y guardar el archivo para que se ejecuten nuestras pruebas:

código de clase RSpecGreeter # eventualmente irá aquí final 

Esto dará lugar al siguiente error:

Fallos: 1) RSpecGreeter RSpecGreeter # greet () Failure / Error: greeter = greeter.greet # When NoMethodError: undefined methodgreet 'para # # ./spec/example_spec.rb:6:in' bloque (2 niveles) en 'Acabado en 0.00036 segundos 1 ejemplo, 1 falla 

Ahora podemos ver que este error nos está diciendo el método. saludar no existe, así que vamos a agregarlo y luego volver a guardar nuestro archivo para ejecutar nuestras pruebas:

La clase RSpecGreeter def greet # code finalmente irá aquí end end 

OK, ya casi estamos allí. El error que tenemos ahora es:

Fallos: 1) RSpecGreeter RSpecGreeter # greet () Failure / Error: greeter = greeting.should eq ('Hello RSpec!') # Entonces se esperaba: "Hello RSpec!" got: nil (comparado con ==) # ./spec/example_spec.rb:7:in 'bloque (2 niveles) en' Terminado en 0.00067 segundos 1 ejemplo, 1 error 

RSpec nos está diciendo que esperaba ver Hola RSpec! pero en vez de eso consiguió nulo (porque definimos la saludar método, pero en realidad no define nada dentro del método y por lo que devuelve nulo).

Agregaremos el resto del código que hará que nuestra prueba pase:

clase RSpecGreeter def greet "Hello RSpec!" final fin 

Ahí lo tenemos, una prueba pasajera:

Terminado en 0.00061 segundos 1 ejemplo, 0 fallas 

Hemos terminado aqui. Nuestra prueba está escrita y el código está pasando..

Conclusión

Hasta el momento, hemos aplicado un proceso de desarrollo basado en pruebas para desarrollar nuestra aplicación, junto con el uso del popular marco de pruebas RSpec..

Vamos a dejarlo aquí por ahora. Regresa y únete a nosotros para la segunda parte, donde veremos más características específicas de RSpec y usaremos la gema Pry para ayudarte a depurar y escribir tu código..