Prueba de su código de Ruby con Guardia, RSpec & Pry Parte 2

¡Dar una buena acogida! Si se perdió la primera parte de nuestro viaje hasta el momento, es posible que desee regresar y ponerse al día primero..

Hasta ahora, hemos aplicado un proceso de desarrollo impulsado por pruebas para construir nuestra aplicación, junto con la utilización del popular marco de pruebas RSpec. A partir de aquí, vamos a investigar algunas otras características de RSpec, así como a utilizar la gema Pry para ayudarte a depurar y escribir tu código..

Otras características de RSpec

Tomemos un momento para revisar otras características de RSpec que no hemos necesitado en esta sencilla aplicación de ejemplo, pero puede que encuentre útil trabajar en su propio proyecto..

Declaración pendiente

Imagina que escribes una prueba pero te interrumpen, o necesitas irte a una reunión y aún no has completado el código requerido para que la prueba sea aprobada.

Puede eliminar la prueba y volver a escribirla más tarde cuando pueda volver a su trabajo. O alternativamente, puedes comentar el código, pero eso es bastante feo y definitivamente no es bueno cuando usas un sistema de control de versiones..

Lo mejor que se puede hacer en esta situación es definir nuestra prueba como 'pendiente', de modo que cada vez que se ejecuten las pruebas, el marco de prueba ignorará la prueba. Para hacer esto necesitas usar el pendiente palabra clave:

describa "algún método" hágalo "debería hacer algo" pendiente del final 

Configuración y desmontaje

Todos los buenos marcos de prueba le permiten ejecutar código antes y después de ejecutar cada prueba. RSpec no es diferente.

Nos proporciona antes de y después métodos que nos permiten configurar un estado específico para que se ejecute nuestra prueba, y luego limpiar ese estado después de que se haya ejecutado la prueba (esto es para que el estado no se filtre y afecte el resultado de pruebas posteriores).

describe "algún método" hacer antes (: cada uno) hacer # un código de configuración finalizar después de (: cada uno) hacer # un código desmontable terminar "debería hacer algo" pendiente de un extremo final 

Bloques de contexto

Ya hemos visto el describir bloquear; Pero hay otro bloque que es funcionalmente equivalente llamado contexto. Puedes usarlo en cualquier lugar que uses describir.

La diferencia entre ellos es sutil pero importante: contexto Nos permite definir un estado para nuestra prueba. Sin embargo, no explícitamente (en realidad no establecemos el estado definiendo un contexto bloque: en cambio, es para facilitar la lectura, por lo que la intención del siguiente código es más clara).

Aquí hay un ejemplo:

describe "Algún método" hacer contexto "bloque proporcionado" hacerlo "cede al bloque" hacer pendiente extremo final contexto "no bloque proporcionado" hacerlo "llama a un método de reserva" hacer pendiente final extremo final 

Talones

Podemos usar el talón Método para crear una versión falsa de un objeto existente y hacer que devuelva un valor predeterminado.

Esto es útil para evitar que nuestras pruebas toquen las API de servicio en vivo y para guiar nuestras pruebas dando resultados predecibles de ciertas llamadas.

Imagina que tenemos una clase llamada Persona y que esta clase tiene una hablar método. Queremos probar que el método funciona como esperamos que lo haga. Para ello vamos a tentar el hablar Método utilizando el siguiente código:

Describe a la persona hazlo "hablar ()" do bob = stub () bob.stub (: speak) .and_return ('hello') Person.any_instance.stub (: initialize) .and_return (bob) instance = Person.new expect ( instance.speak) .to eq ('hello') end end 

En este ejemplo, decimos que 'cualquier instancia' de la Persona la clase debe tener su inicializar método aplastado por lo que devuelve el objeto mover.

Te darás cuenta que mover es en sí mismo un código auxiliar que se configura de modo que cada vez que el código intente ejecutar el código hablar Método devolverá "hola".

Luego procedemos a crear un nuevo Persona instancia y pasar la llamada de instancia.speak en RSpec's esperar sintaxis.

Le decimos a RSpec que estamos esperando que esa llamada dé como resultado la cadena "hola".

Valores de retorno consecutivos

En los ejemplos anteriores hemos usado la característica RSpec y volver para indicar lo que nuestro talón debe devolver cuando se llama.

Podemos indicar un valor de retorno diferente cada vez que se llama al apéndice especificando múltiples argumentos a la y volver método:

obj = stub () obj.stub (: foo) .and_return (1, 2, 3) expect (obj.foo ()). hasta eq (1) expect (obj.foo ()). hasta eq (2) expect (obj.foo ()). a eq (3) 

Burlas

Los simulacros son similares a los stubs ya que estamos creando versiones falsas de nuestros objetos, pero en lugar de devolver un valor predefinido, orientamos más específicamente las rutas de nuestros objetos. debe tomar para que la prueba sea válida.

Para ello utilizamos el burlarse de método:

describa Obj. "testing ()" do bob = mock () bob.should_receive (: testing) .with ('content') Obj.any_instance.stub (: initialize) .and_return (bob) instance = Obj.new instance. final de prueba ('algo de valor') 

En el ejemplo anterior creamos una nueva Objeto instancia y luego llamar a su pruebas método.

Detrás de las escenas de ese código esperamos que el pruebas Método a llamar con el valor 'contenido'. Si no se llama con ese valor (que en el ejemplo anterior no lo es), entonces sabemos que parte de nuestro código no ha funcionado correctamente..

Bloque de materias

los tema palabra clave se puede utilizar en un par de maneras diferentes. Todos los cuales están diseñados para reducir la duplicación de código.

Puedes usarlo implícitamente (nota nuestro eso bloque no hace referencia tema en absoluto):

describe Array describe "con 3 elementos" do asunto [1,2,3] it should_not be_empty end end end 

Puedes usarlo explícitamente (nota nuestra eso bloque se refiere a tema directamente):

describe MyClass describe "inicialización" do asunto MyClass "crea una nueva instancia" do instance = subject.new expect (instance) .to be_a (MyClass) end end end end end 

En lugar de hacer referencia constantemente a un tema dentro de su código y pasar valores diferentes para la creación de instancias, por ejemplo:

describe "Foo" do contexto "A" hazlo "Bar" do baz = Baz.new ('a') espera (baz.type) .to eq ('a') final contexto "B" hazlo "Bar" hacer baz = Baz.new ('b') esperar (baz.type). hasta eq ('b') terminar el contexto final "C" hacerlo "Bar" hacer baz = Baz.new ('c') esperar (baz .type) .to eq ('c') end end end end 

En su lugar puedes usar tema junto con dejar Para reducir la duplicación:

describa "Person" do subject Person.new (name) # Person tiene un contexto del método get_name "Bob" do let (: name) 'Bob' su (: get_name) should == 'Bob' end context "Joe" no deje que (: nombre) 'Joe' es su (: obtener nombre) debería == 'Joe' contexto final "Smith" deja que (: nombre) 'Smith' es (: obtener nombre) debería == 'Smith' end end end 

Hay muchas más funciones disponibles para RSpec, pero hemos analizado las más importantes que encontrará usando mucho al escribir pruebas utilizando RSpec..

Pruebas aleatorias

Puede configurar RSpec para ejecutar sus pruebas en un orden aleatorio. Esto le permite asegurarse de que ninguna de sus pruebas dependa o dependa de las otras pruebas a su alrededor..

RSpec.configure do | config | config.order = final 'aleatorio' 

Puede configurar esto a través del comando usando el --orden bandera / opción Por ejemplo: rspec - orden aleatorio.

Cuando usas el --orden aleatorio La opción RSpec mostrará el número aleatorio que utilizó para inicializar el algoritmo. Puede usar este valor 'inicial' nuevamente cuando crea que ha descubierto un problema de dependencia dentro de sus pruebas. Una vez que haya solucionado lo que cree que es el problema, puede pasar el valor semilla a RSpec (por ejemplo, si la semilla fue 1234 luego ejecuta --orden aleatorio: 1234) y usará esa misma semilla aleatoria para ver si puede replicar el error de dependencia original.

configuración global

Usted ha visto que hemos agregado un conjunto específico de objetos de configuración dentro de nuestro Rakefile. Pero puede configurar las opciones de configuración globalmente agregándolas a un .rspec archivo dentro de su directorio de inicio.

Por ejemplo, dentro .rspec:

--color - formato anidado 

Depuración con palanca

Ahora estamos listos para comenzar a investigar cómo podemos depurar nuestra aplicación y nuestro código de prueba usando la gema Pry.

Es importante entender que aunque Pry es realmente bueno para depurar su código, en realidad está pensado como una herramienta REPL de Ruby mejorada (para reemplazar irb) y no estrictamente propósitos de depuración; así, por ejemplo, no hay funciones integradas como: paso a paso, paso o paso, etc. que normalmente encontrará en una herramienta diseñada para la depuración.

Pero como una herramienta de depuración, Pry es muy concentrado y delgado.

Volveremos a la depuración en un momento, pero primero revisemos cómo usaremos Pry inicialmente.

Ejemplo de código actualizado

Con el fin de demostrar Pry, voy a agregar más código a mi aplicación de ejemplo (este código adicional no afecta nuestra prueba de ninguna manera)

class RSpecGreeter attr_accessor: test @@ class_property = "Soy una propiedad de clase" def greet binding.pry @instance_property = "Soy una propiedad de instancia" pubs privs "Hello RSpec!" end def pubs test_var = "Soy una variable de prueba" test_var end private def privs pone "I'm private" end end 

Notará que hemos agregado algunos métodos adicionales, propiedades de instancia y clase. También hacemos llamadas a dos de los nuevos métodos que hemos agregado desde nuestro saludar método.

Por último, notarás el uso de vinculante.pry.

Configuración de puntos de ruptura con vinculante.pry

Un punto de ruptura es un lugar dentro de su código donde la ejecución se detendrá.

Puede tener múltiples puntos de interrupción establecidos dentro de su código y puede crearlos usando vinculante.pry.

Cuando ejecute su código, notará que el terminal se detendrá y lo ubicará dentro del código de su aplicación en el lugar exacto donde se colocó su enlace..

A continuación se muestra un ejemplo de cómo podría verse ...

 8: def greet => 9: binding.pry 10: pubs 11: privs 12: "Hello RSpec!" 13: fin 

Desde este punto, Pry tiene acceso al ámbito local, por lo que puede usar Pry como lo haría en irb y comience a escribir, por ejemplo, variables para ver qué valores tienen.

Puede ejecutar el salida comando para salir de Pry y para que su código continúe ejecutándose.

Encontrar dónde estás: Dónde estoy

Cuando se usa mucho vinculante.pry puntos de ruptura puede ser difícil de entender en qué parte de la aplicación se encuentra.

Para obtener un mejor contexto de dónde se encuentra en cualquier momento, puede utilizar el Dónde estoy mando.

Cuando se ejecuta solo verá algo similar a cuando usó vinculante.pry (Verá la línea en la que se estableció el punto de quiebre y un par de líneas arriba y abajo). La diferencia es que si pasas un argumento numérico extra como dónde son 5 verá cinco líneas adicionales encima de donde vinculante.pry fue puesto. Podría solicitar ver 100 líneas alrededor del punto de quiebre actual, por ejemplo..

Este comando puede ayudarte a orientarte dentro del archivo actual.

Traza de la pila: wtf

los wtf comando significa "what the f ***" y proporciona un seguimiento completo de la pila para la excepción más reciente que se ha lanzado. Puede ayudarlo a comprender los pasos que conducen al error que ocurrió.

Inspeccionando ls

los ls comando muestra qué métodos y propiedades están disponibles para Pry.

Cuando lo ejecutes te mostrará algo como ...

RSpecGreeter # métodos: greet pubs test test = variables de clase: @@ class_property locals: _ __ _dir_ _ex_ _file_ _in_ _out_ _pry_ 

En el ejemplo anterior, podemos ver que tenemos cuatro métodos públicos (recuerde que actualizamos nuestro código para incluir algunos métodos adicionales y luego prueba y prueba = fueron creados cuando se usa Ruby attr_accessor mano corta).

También muestra otras variables de clase y locales a las que Pry puede acceder..

Otra cosa útil que puedes hacer es grep (buscar) los resultados solo para lo que te interesa. Necesitarás tener un entendimiento de las expresiones regulares, pero puede ser una técnica útil. Aquí hay un ejemplo…

ls -p -G ^ p => RSpecGreeter # métodos: privs 

En el ejemplo anterior estamos usando el -pag y -sol opciones / banderas que indican a Pry que solo queremos ver métodos públicos y privados y usamos la expresión regular ^ p (lo que significa emparejar cualquier cosa que comience con pag) Como nuestro patrón de búsqueda para filtrar los resultados..

Corriendo ls - ayuda También te mostrará todas las opciones disponibles..

Cambiando el alcance: discos compactos

Puede cambiar el alcance actual usando el discos compactos mando.

En nuestro ejemplo si corremos cd… / pubs Nos llevará al resultado de esa llamada al método..

Si ahora corremos Dónde estoy verá que se mostrará Dentro de "Soy una variable de prueba".

Si corremos yo entonces verás que obtenemos "Soy una variable de prueba" devuelto.

Si corremos clase propia ya veremos Cuerda devuelto.

Puedes subir la cadena de alcance usando discos compactos… o puede volver al nivel superior del alcance usando discos compactos /.

Nota: podríamos añadir otra. vinculante.pry dentro de pubs Método y luego nuestro alcance estaría dentro de ese método en lugar del resultado del método.

Viendo lo profundo que eres: anidando

Considere el ejemplo anterior de correr pubs cd. Si corremos el anidando comando obtendremos una vista de nivel superior en la cantidad de contextos / niveles que Pry tiene actualmente:

Estado de anidación: - 0. # (nivel superior de palanca) 1. "Soy una variable de prueba" 

Desde alli podemos correr salida para volver al contexto anterior (por ejemplo, dentro de saludar método)

Corriendo salida nuevamente significará que estamos cerrando el último contexto que Pry tiene y así Pry termina y nuestro código continúa ejecutándose.

Localice cualquier método: método de búsqueda

Si no está seguro de dónde encontrar un método en particular, puede utilizar el método de búsqueda comando para mostrarle todos los archivos dentro de su base de código que tienen un método que coincide con lo que está buscando:

find-method priv => Kernel Kernel # private_methods Module Module # private_instance_methods Module # private_constant Module # private_method_defined? Módulo # private_class_method Módulo # private RSpecGreeter RSpecGreeter # privs 

También puedes usar el -do Opción / bandera para buscar el contenido de los archivos en su lugar:

find-method -c greet => RSpecGreeter RSpecGreeter: def greet RSpecGreeter # privs: greet 

Depuración clásica: siguiente, paso, continuar

Aunque las técnicas anteriores son útiles, no es realmente 'depuración' en el mismo sentido que lo que probablemente estás acostumbrado.

Para la mayoría de los desarrolladores, su editor o navegador les proporcionará una herramienta de depuración incorporada que les permite recorrer su código línea por línea y seguir la ruta que el código toma hasta su finalización..

Como Pry está desarrollado para ser usado como REPL, no quiere decir que no sea útil para la depuración..

Una solución ingenua sería establecer múltiples vinculante.pry declaraciones a través de un método y uso ctrl-d para moverse a través de cada conjunto de puntos de ruptura. Pero eso todavía no es lo suficientemente bueno..

Para la depuración paso a paso puede cargar la gema pry-nav ...

fuente "https://rubygems.org" gema 'rspec' grupo: desarrollo hacer gema 'guardia' gema 'guardia-rspec' gema '#' Agrega pasos de depuración a Pry # continuar, paso, siguiente gema 'pry-remoto' gema 'pry-nav' final 

Esta gema extiende Pry para que entienda los siguientes comandos:

  • Siguiente (pasar a la siguiente línea)
  • Paso (Mueve a la siguiente línea y si es un método, muévete a ese método)
  • Continuar (Ignora cualquier otro punto de ruptura en este archivo)

Integración continua con Travis-CI

Como beneficio adicional, integremos nuestras pruebas con el servicio en línea de CI (integración continua) Travis-CI.

El principio de CI es cometer / presionar temprano y con frecuencia para evitar conflictos entre su código y la rama maestra. Cuando lo haga (en este caso nos comprometemos con GitHub), entonces eso debería iniciar una 'compilación' en su servidor de CI que ejecuta las pruebas relevantes para garantizar que todo funcione como debería..

Ahora, con TDD como su principal metodología de desarrollo, es menos probable que incurra en errores cada vez que empuja debido a que sus pruebas son una parte integral de su flujo de trabajo de desarrollo y, por lo tanto, antes de que empiece, ya estará al tanto de los errores o las regresiones. Pero esto no lo protege necesariamente de los errores que se producen en las pruebas de integración (donde todos los códigos de varios sistemas se ejecutan juntos para garantizar que el sistema "en su conjunto" funcione correctamente).

En cualquier caso, el código nunca debe enviarse directamente a su servidor de producción en vivo; siempre debe enviarse primero a un servidor de CI para ayudar a detectar cualquier error potencial que surja de las diferencias entre su entorno de desarrollo y el entorno de producción.

Muchas empresas tienen más entornos para que pase su código antes de que llegue al servidor de producción en vivo.

Por ejemplo, en BBC News tenemos:

  • CI
  • Prueba
  • Escenario
  • Vivir

Aunque cada entorno debe ser idéntico en su configuración, el propósito es implementar diferentes tipos de pruebas para garantizar que se detecten y solucionen tantos errores antes de que el código llegue a "vivo"..

Travis-CI

Travis CI es un servicio de integración continua alojado para la comunidad de código abierto. Está integrado con GitHub y ofrece soporte de primera clase para múltiples idiomas.

Lo que esto significa es que Travis-CI ofrece servicios gratuitos de IC para proyectos de código abierto y también tiene un modelo pagado para las empresas y organizaciones que desean mantener en privado su integración de CI..

Usaremos el modelo gratuito de código abierto en nuestro repositorio GitHub de ejemplo.

El proceso es este:

  • Registrar una cuenta en GitHub
  • Inicie sesión en Travis-CI con su cuenta de GitHub
  • Vaya a su página de "Cuentas"
  • Active "en" cualquier repositorio en el que quiera ejecutar CI
  • Crear un .travis.yml Archivo dentro del directorio raíz de su proyecto y confírmelo en su repositorio GitHub

El paso final es el más importante (creando un .travis.yml archivo) ya que esto determina los ajustes de configuración de Travis-CI para que sepa cómo manejar la ejecución de las pruebas para su proyecto.

Echemos un vistazo a la .travis.yml Archivo que estamos usando para nuestro repositorio GitHub de ejemplo:

idioma: ruby ​​caché: bundler rvm: - 2.0.0 - 1.9.3 script: 'bundle exec rake spec' bundler_args: --sin las ramas de desarrollo: solo: - notificaciones maestras: correo electrónico: - [email protected] 

Vamos a desglosar pieza por pieza ...

Primero especificamos qué idioma estamos usando en nuestro proyecto. En este caso estamos usando Ruby: idioma: rubí.

Debido a que ejecutar Bundler puede ser un poco lento y sabemos que nuestras dependencias no van a cambiar, que a menudo podemos optar por almacenar las dependencias en caché, así que configuramos caché: bundler.

Travis-CI usa RVM (Ruby Version Manager) para instalar Rubies en sus servidores. Así que necesitamos especificar contra qué versiones de Ruby queremos ejecutar nuestras pruebas. En este caso hemos elegido 2.0 y 1.9.3 que son dos versiones populares de Ruby (técnicamente nuestra aplicación usa Ruby 2, pero también es bueno saber que nuestro código pasa en otras versiones de Ruby):

rvm: - 2.0.0 - 1.9.3 

Para ejecutar nuestras pruebas sabemos que podemos usar el comando rastrillo o rastrillo espec. Travis-CI por defecto ejecuta el comando rastrillo pero debido a cómo se instalan las gemas en Travis-CI usando Bundler, necesitamos cambiar el comando predeterminado: script: 'bundle exec rake spec'. Si no hiciéramos esto, entonces Travis-CI tendría un problema para localizar el rspec / core / rake_task archivo que se especifica dentro de nuestra Rakefile.

Nota: si tiene algún problema con Travis-CI, puede unirse al canal #travis en IRC freenode para obtener ayuda para responder cualquier pregunta que pueda tener. Ahí es donde descubrí la solución a mi problema con Travis-CI al no poder ejecutar mis pruebas usando su valor predeterminado rastrillo comando y la sugerencia de sobrescribir el valor predeterminado con paquete de rastrillo ejecutivo resuelto ese problema.

Luego, debido a que solo estamos interesados ​​en ejecutar nuestras pruebas, podemos pasar argumentos adicionales a Travis-CI para filtrar gemas que no queremos molestar en la instalación. Así que para nosotros queremos excluir la instalación de gemas agrupadas como desarrollo: bundler_args: --sin desarrollo (Esto significa que estamos excluyendo gemas que solo se usan realmente para el desarrollo y la depuración como Pry y Guard).

Es importante tener en cuenta que originalmente estaba cargando Pry dentro de nuestro spec_helper.rb expediente. Esto causó un problema al ejecutar el código en Travis-CI, ahora que estaba excluyendo las gemas de "desarrollo". Así que tuve que modificar el código así:

requiere 'palanca' si ENV ['APP_ENV'] == 'depurar' 

Puedes ver que ahora la gema de Pry es solo exigir'ed si una variable de entorno de APP_ENV está configurado para depurar. De esta manera podemos evitar que Travis-CI arroje cualquier error. Esto significa que cuando ejecute su código localmente tendría que establecer la variable de entorno si desea depurar su código usando Pry. A continuación se muestra cómo se podría hacer esto en una línea:

APP_ENV = debug && ruby ​​lib / example.rb

Hubo otros dos cambios que hice y eso fue para nuestra Gemfile. Uno fue para aclarar qué gemas eran necesarias para las pruebas y cuáles para el desarrollo, y el otro fue explícitamente requerido por Travis-CI:

fuente grupo "https://rubygems.org": prueba hacer gema 'rake' gema 'rspec' grupo final: desarrollo hacer gema 'guardia' gema 'guard-rspec' gema 'pry' # Agrega pasos de depuración a Pry # continuar, paso, siguiente gema 'pry-remote' gema 'pry-nav' final 

Viendo lo anterior actualizado Gemfile Podemos ver que hemos movido la gema RSpec a una nueva prueba grupo, por lo que ahora debería ser más claro qué propósito tiene cada gema. También hemos añadido un nuevo gema 'rastrillo'. La documentación de Travis-CI indica que esto debía especificarse explícitamente.

La siguiente sección es opcional y le permite a la lista blanca (o lista negra) ciertas ramas en su repositorio. Por lo tanto, de manera predeterminada, Travis-CI ejecutará pruebas en todas sus sucursales, a menos que le indique lo contrario. En este ejemplo, le decimos que solo queremos que se ejecute contra nuestra dominar rama:

ramas: solo: - maestro 

Podríamos decirle que ejecute cada rama 'excepto' una rama en particular, así:

ramas: excepto: - some_branch_I_dont_want_run 

La sección final le dice a Travis-CI a dónde enviar notificaciones cuando una compilación falla o tiene éxito:

notificaciones: email: - [email protected] 

Puede especificar múltiples direcciones de correo electrónico si lo desea:

notificaciones: correo electrónico: - [email protected] - [email protected] - [email protected] 

Puede ser más específico y especificar qué quiere que suceda en caso de fracaso o éxito (por ejemplo, más personas solo estarán interesadas si las pruebas fallan en lugar de recibir un correo electrónico cada vez que pasan):

notificaciones: correo electrónico: destinatarios: - [email protected] on_failure: change on_success: never 

El ejemplo anterior muestra que el destinatario nunca recibirá un correo electrónico si las pruebas pasan, pero se le notificará si el estado de falla cambia (el valor predeterminado para ambos es siempre lo que significa que siempre se le notificará independientemente del resultado del estado).

Nota: cuando especifique explícitamente un en el fracaso o on_success necesita mover las direcciones de correo electrónico dentro de recipientes llave.

Conclusión

Este es el final de nuestra mirada de dos partes en RSpec, TDD y Pry.

En la primera parte, logramos escribir nuestra aplicación utilizando el proceso TDD y el marco de prueba RSpec. En esta segunda mitad también hemos utilizado Pry para mostrar cómo podemos depurar más fácilmente una aplicación Ruby en ejecución. Finalmente, pudimos configurar nuestras pruebas para que funcionen como parte de un servidor de integración continua utilizando el popular servicio Travis-CI..

Esperemos que esto le haya dado suficiente sabor, por lo que le interesa investigar cada una de estas técnicas.