Spanish (Español) translation by Valeria Angulo (you can also view the original English article)
Ruby es uno de los lenguajes más populares usados en la web. Estamos llevando a cabo una sesión aquí en Nettuts + que te presentará a Ruby, así como también los excelentes frameworks y herramientas que acompañan al desarrollo de Ruby. En este episodio, aprenderás a probar tu código Ruby con Rspec, una de las mejores bibliotecas de prueba del mercado.
Prefieres un Screencast?
Te parece Familiar?
Si has leído mi reciente tutorial sobre JasmineJS, probablemente notarás varias similitudes en Rspec. En realidad, las similitudes están en Jasmine: Jasmine se creó con Rspec en mente. Vamos a ver cómo se puede usar Rspec para hacer TDD en Ruby. En este tutorial, crearemos algunas clases artificiales de Ruby para familiarizarnos con la sintaxis de Rspec. Sin embargo, el próximo episodio de "Ruby for Newbies" incluirá el uso de Rspec junto con algunas otras bibliotecas para probar aplicaciones web ... ¡así que mantente atento!
Configurando
Es bastante fácil de instalar Rspec. Pop abre esa línea de comando y ejecuta esto:
gem install rspec
Así de fácil.
Ahora, configuremos un pequeño proyecto. Vamos a crear dos clases: Book
y library
. Nuestros objetos de Book
solo almacenarán un título, autor y categoría. Nuestro objeto Library
almacenará una lista de libros, los guardará en un archivo y nos permitirá buscarlos por categoría.
Este es el aspecto que debería tener el directorio de su proyecto:



Ponemos las especificaciones (o specs) en una carpeta spec
; tenemos un archivo de especificaciones para cada clase. Observe el archivo spec_helper.rb
. Para que se ejecuten nuestras especificaciones, debemos solicitar con require
las clases de Ruby que estamos probando. Eso es lo que estamos haciendo dentro del archivo spec_helper
:
require_relative '../library' require_relative '../book' require 'yaml'
(¿Ya conociste require_relative
? ¿No? Bueno, require_relative
es como require
, excepto que en lugar de buscar tu ruta de Ruby, busca en relación con el directorio actual).
Es posible que no estés familiarizado con el módulo YAML; YAML es una base de datos de texto simple que usaremos para almacenar datos. Verás cómo funciona, y hablaremos sobre eso más adelante.
Entonces, ahora que estamos listos, ¡analicemos algunas especificaciones!
La Clase Book
Comencemos con las pruebas para la clase Book
.
require 'spec_helper' describe Book do end
Así es como comenzamos: con un bloque describe
. Nuestro parámetro para describe
explica lo que estamos probando: esto podría ser una cadena, pero en nuestro caso estamos usando el nombre de la clase.
Entonces, ¿qué vamos a poner dentro de este bloque describe
?
before :each do @book = Book.new "Title", "Author", :category end
Comenzaremos haciendo una llamada a before
; pasamos el símbolo :each
para especificar que queremos que este código se ejecute antes de cada prueba (también podríamos hacer :all
para ejecutarlo una vez antes de todas las pruebas). ¿Qué estamos haciendo exactamente antes de cada prueba? Estamos creando una instancia de Book
. Observe cómo lo estamos convirtiendo en una variable de instancia, anteponiendo el nombre de la variable con @
. Necesitamos hacer esto para que nuestra variable sea accesible desde dentro de nuestras pruebas. De lo contrario, obtendremos una variable local que solo es válida dentro del bloque before
... lo cual no es bueno en absoluto.
Continuando,
describe "#new" do it "takes three parameters and returns a Book object" do @book.should be_an_instance_of Book end end
Aquí está nuestra primera prueba. Estamos usando un bloque describe
anidado aquí para decir que estamos describiendo las acciones de un método específico. Notarás que he usado la cadena "#new"; es una convención en Ruby para hablar referente a métodos de instancia como este:ClassName#methodName
Dado que tenemos el nombre de la clase en nuestra descripción
de nivel superior, aquí solo estamos poniendo el nombre del método.
Nuestra prueba simplemente confirma que, de hecho, estamos haciendo un objeto Book.
Observe la gramática que usamos aquí: object.should do_something
. El noventa y nueve por ciento de sus pruebas tomarán esta forma: usted tiene un objeto y comienza llamando al should
o should_not
sobre el objeto. Luego, pasa a ese objeto la llamada a otra función. En este caso, eso es be_an_instance_of
(que toma Book
como su único parámetro). En conjunto, esto hace una prueba perfectamente legible. Está muy claro que @book
debería ser una instancia de la clase Book
. Entonces, ejecútelo.
Abra su terminal, escriba cd
en el directorio del proyecto y ejecute la especificación rspec spec
. La spec
es la carpeta en la que rspec
encontrará las pruebas. Deberías ver el resultado que dice algo sobre "Objeto constante sin iniciar Object::Book"; esto solo significa que no está la clase Book
. Arreglemos eso.
De acuerdo con TDD, solo queremos escribir suficiente código para solucionar este problema. En el archivo book.rb
, sería esto:
class Book end
Vuelve a ejecutar la prueba (rspec spec
), y verá que está pasando bien. No tenemos un método initialize
, así que llamar a Ruby#new
no tiene ningún efecto en este momento. Pero, podemos crear objetos Book
(aunque sean huecos). Normalmente, seguiríamos este proceso durante el resto de nuestro desarrollo: escribir una prueba (o algunas pruebas relacionadas), verla fallar, hacerla pasar, refactorizar, repetir. Sin embargo, para este tutorial, solo te mostraré las pruebas y el código, y los discutiremos.
Así, más pruebas para Book
:
describe "#title" do it "returns the correct title" do @book.title.should eql "Title" end end describe "#author" do it "returns the correct author" do @book.author.should eql "Author" end end describe "#category" do it "returns the correct category" do @book.category.should eql :category end end
Todo debería ser muy directo para ti. Pero observa cómo estamos comparando en la prueba: con eql
. Hay tres maneras de probar la igualdad con Rspec: utilizando el operador ==
o el método eql
ambos devuelven true
si los dos objetos tienen el mismo contenido. Por ejemplo, ambos son cadenas o símbolos que dicen lo mismo. Luego está equal
, que solo devuelve "true" en los dos objetos que son realmente y verdaderamente iguales, lo que significa que son el mismo objeto en la memoria. En nuestro caso, eql
(o ==
) es lo que queremos.
Estos fallarán, así que aquí está el código paraBook
para hacerlos pasar:
class Book attr_accessor :title, :author, :category def initialize title, author, category @title = title @author = author @category = category end end
Pasemos a Library
!
Especificando la clase Library
Esto será un poco más complicado. Así que comencemos con esto:
require 'spec_helper' describe "Library object" do before :all do lib_obj = [ Book.new("JavaScript: The Good Parts", "Douglas Crockford", :development), Book.new("Designing with Web Standards", "Jeffrey Zeldman", :design), Book.new("Don't Make me Think", "Steve Krug", :usability), Book.new("JavaScript Patterns", "Stoyan Stefanov", :development), Book.new("Responsive Web Design", "Ethan Marcotte", :design) ] File.open "books.yml", "w" do |f| f.write YAML::dump lib_obj end end before :each do @lib = Library.new "books.yml" end end
Todo está configurado: estamos usando dos bloques before
: uno para :each
y uno para :all
En el bloque before: all
, creamos una matriz de libros. Luego abrimos el archivo "books.yml" (en el modo "w"rite) y usamos YAML
para volcar la matriz en el archivo.
Usamos "Short rabbit-trail" para explicar YAML un poco mejor: YAML es, de acuerdo con el sitio "un estándar de serialización de datos amigable para todos los lenguajes de programación." Es como una base de datos basada en texto, algo así como JSON. Estamos importando YAML en nuestro spec_helper.rb
. El módulo YAML
tiene dos métodos principales que utilizará: dump
, que genera los datos serializados como una cadena. Luego, load
toma la cadena de datos y la vuelve a convertir en objetos Ruby.
Por lo tanto, hemos creado este archivo con algunos datos. Antes de cada :each
prueba, vamos a crear un objeto Library
, pasándole el nombre del archivo YAML. Ahora veamos las pruebas:
describe "#new" do context "with no parameters" do it "has no books" do lib = Library.new lib.should have(0).books end end context "with a yaml file parameter" do it "has five books" do @lib.should have(5).books end end end it "returns all the books in a given category" do @lib.get_books_in_category(:development).length.should == 2 end it "accepts new books" do @lib.add_book( Book.new("Designing for the Web", "Mark Boulton", :design) ) @lib.get_book("Designing for the Web").should be_an_instance_of Book end it "saves the library" do books = @lib.books.map { |book| book.title } @lib.save lib2 = Library.new "books.yml" books2 = lib2.books.map { |book| book.title } books.should eql books2 end
Comenzamos con un bloque interno describe
especialmente para el método Library#new
. Estamos presentando otro bloque aquí: context
Esto nos permite especificar un contexto para las pruebas dentro de él, o especificar diferentes resultados para diferentes situaciones. En nuestro ejemplo, tenemos dos contextos diferentes: "sin parámetros" y "con un parámetro de archivo yaml"; estos muestran los dos comportamientos para usar Library#new
.
Además, observe los evaluadores de prueba que estamos usando en estas dos pruebas: lib.should have(0).books
y @ lib.should have(5).books
. La otra forma de escribir esto sería lib.books.length.should == 5
, pero esto no es tan legible. Sin embargo, muestra que necesitamos tener una propiedad books
que sea una matriz de los libros que tenemos.
Luego, tenemos otras tres pruebas para probar la funcionalidad de obtener libros por categoría, agregar un libro a la biblioteca y guardar la biblioteca. Todos están fallando, así que vamos a escribir la clase ahora.
class Library attr_accessor :books def initialize lib_file = false @lib_file = lib_file @books = @lib_file ? YAML::load(File.read(@lib_file)) : [] end def get_books_in_category category @books.select do |book| book.category == category end end def add_book book @books.push book end def get_book title @books.select do |book| book.title == title end.first end def save lib_file = false @lib_file = lib_file || @lib_file || "library.yml" File.open @lib_file, "w" do |f| f.write YAML::dump @books end end end
Podríamos escribir más pruebas y agregar muchas otras funcionalidades a esta clase Library
, pero nos detendremos allí. Ahora ejecutando rspec spec
, verás que todas las pruebas pasan.

Sin embargo, esto no nos da mucha información sobre las pruebas. Si desea ver más, use el parámetro de formato anidado: rspec spec --format nested
. Verás esto:



Unos pocos últimos Igualadores
Antes de concluir, déjame mostrarte un par de otros igualadores
-
obj.should be_true
,obj.should be_false
,obj.should be_nil
,obj.should be_empty
- los primeros tres de estos podrían hacerse por== true
, etc.be_empty
podría ser verdadero siobj.empty?
es verdadero. -
obj.should exist
- ¿este objeto existe aún? -
obj.should have_at_most(n).items
,object.should have_at_least(n).items
- comohave
, pero pasará si hay más o menos den
artículos, respectivamente. -
obj.should include (a [, b, ...])
- ¿hay uno o más elementos en una matriz? -
obj.should match (string_or_regex)
- ¿el objeto coincide con la cadena o con la expresión regular? -
obj.should raise_exception (error)
- ¿Este método genera un error cuando se llama? -
obj.should respon_to (method_name)
- ¿este objeto tiene este método? Puedes tomar más de un nombre de método, ya sea en cadenas o símbolos.
¿Quieres Aprender Más?
Rspec es uno de los mejores frameworks para probar en Ruby, y hay mucho que puedes hacer con él. Para obtener más información, consulte el sitio web de Rspec. También está el libro The Rspec, que enseña más que solo Rspec: se trata de TDD y BDD en Ruby. Lo estoy leyendo ahora, y es extremadamente minucioso y profundo.
Bueno, ¡eso es todo por esta lección! La próxima vez, veremos cómo podemos usar Rspec para probar las interfaces en una aplicación web.