El soporte para hacer testing en Rails se realizo desde el principio. No fue como una epifania tipo "oh, vamos a hacer el soporte para las pruebas de ejecución porque son nuevos y cool"
Rails crea un directorio de testing para usted tan pronto como crea un proyecto de Rails utilizando los rails new application_name
. Si enlista el contenido de este directorio, verá:
$ ls -F test
controllers/ helpers/
mailers/ system/ test_helper.rb
fixtures/ integration/
models/ application_system_test_case.rb
Los helpers, los mailers y los directorios de los modelos están destinados a realizar pruebas para los ayudantes de la vista, los mailers y los modelos, respectivamente. El directorio de controladores está destinado a realizar pruebas para controladores, rutas y vistas. El directorio de integración está destinado a realizar pruebas de interacciones entre controladores.
El directorio de prueba del sistema contiene pruebas del sistema, que se utilizan para la prueba completa del navegador de su aplicación. Las pruebas del sistema le permiten probar su aplicación de la misma manera que sus usuarios la experimentan y también ayudarle a probar su JavaScript. Las pruebas del sistema heredan de Capybara y realizan pruebas de navegador para su aplicación.
Los fixtures son una manera de organizar los datos de la prueba; Residen en el directorio fixtures
.
También se creará un directorio de jobs
cuando se genere una prueba asociada.
El archivo test_helper.rb
contiene la configuración predeterminada para las pruebas.
El archivo application_system_test_case.rb
contiene la configuración predeterminada para las pruebas del sistema.
Por defecto, cada aplicación de Rails tiene tres entornos: desarrollo, test y producción.
La configuración de cada entorno se puede modificar de forma similar. En este caso, podemos modificar nuestro entorno de prueba cambiando las opciones encontradas en config/environments/test.rb
.
Sus pruebas se ejecutan bajo
RAILS_ENV=test
.
Si te acuerdas, usamos el comando rails generate model
en la guía Getting Started with Rails. Hemos creado nuestro primer modelo, y entre otras cosas creó stubs de prueba en el directorio de prueba:
$ bin/rails generate model article title:string body:text
...
create app/models/article.rb
create test/models/article_test.rb
create test/fixtures/articles.yml
...
El stub de test predeterminado se encuentra en test/models/article_test.rb
tiene este aspecto:
require 'test_helper'
class ArticleTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end
Un examen línea por línea de este archivo le ayudará a orientarlo a las pruebas código con Rails y la terminología.
require 'test_helper'
Al solicitar este archivo, test_helper.rb
se carga en la configuración predeterminada para ejecutar nuestras pruebas. Vamos a incluir esto con todas las pruebas que escribimos, por lo que cualquier método añadido a este archivo están disponibles para todas nuestras pruebas.
class ArticleTest < ActiveSupport::TestCase
La clase ArticleTest
define un caso de prueba porque hereda de ActiveSupport::TestCase
. Por lo tanto, ArticleTest
tiene todos los métodos disponibles de ActiveSupport::TestCase
. Más adelante en esta guía, veremos algunos de los métodos que nos brinda.
Cualquier método definido en una clase heredada de Minitest::Test
(que es la superclase de ActiveSupport::TestCase
) que comienza con test_
(sensible a mayúsculas y minúsculas) se llama simplemente test. Por lo tanto, los métodos definidos como test_password
y test_valid_password
son nombres de prueba legales y se ejecutan automáticamente cuando se ejecuta el caso de prueba.
Rails también agrega un método de prueba que toma un nombre de test y un bloque. Genera una prueba normal de Minitest::Unit
con nombres de métodos prefijados con test_
. Así que no tienes que preocuparte por nombrar los métodos, y puedes escribir algo como:
test "the truth" do
assert true
end
Que es mas o menos como escribir esto:
def test_the_truth
assert true
end
Aunque todavía puede utilizar definiciones de métodos habituales, el uso de la macro de prueba permite un nombre de prueba más legible.
El nombre del método se genera reemplazando espacios con subrayados. El resultado no necesita ser un identificador de Ruby válido, aunque el nombre puede contener caracteres de puntuación, etc. Eso es porque en Ruby técnicamente cualquier cadena puede ser un nombre de método. Esto puede requerir el uso de
define_method
y enviar llamadas para funcionar correctamente, pero formalmente hay poca restricción en el nombre.
A continuación, echemos un vistazo a nuestra primera assertion:
assert true
Una aserción es una línea de código que evalúa un objeto (o expresión) para los resultados esperados. Por ejemplo, una aserción puede comprobar:
- ¿Es este valor = a este valor?
- ¿Este objeto es nil?
- ¿Esta línea de código arroja una excepción?
- ¿Es la contraseña del usuario mayor de 5 caracteres?
Cada prueba puede contener una o más aserciones, sin restricción en cuanto a cuántas aserciones se permiten. Sólo cuando todas las aserciones sean exitosas, la prueba pasará.
Para ver cómo se informa un error de testing, puede agregar una prueba de error al caso de prueba article_test.rb
.
test "should not save article without title" do
article = Article.new
assert_not article.save
end
Vamos a ejecutar esta prueba recién agregada (donde 6 es el número de línea donde se define la prueba).
$ bin/rails test test/models/article_test.rb:6
Run options: --seed 44656
# Running:
F
Failure:
ArticleTest#test_should_not_save_article_without_title [/path/to/blog/test/models/article_test.rb:6]:
Expected true to be nil or false
bin/rails test test/models/article_test.rb:6
Finished in 0.023918s, 41.8090 runs/s, 41.8090 assertions/s.
1 runs, 1 assertions, 1 failures, 0 errors, 0 skips
En la salida, F denota un fallo. Puede ver la traza correspondiente que se muestra en Failure
junto con el nombre de la prueba que falla. Las siguientes líneas contienen el seguimiento de la pila seguido de un mensaje que menciona el valor real y el valor esperado por la aserción. Los mensajes de aserción predeterminados proporcionan suficiente información para ayudar a identificar el error. Para que el mensaje de fallo de aserción sea más legible, cada aserción proporciona un parámetro de mensaje opcional, como se muestra aquí:
test "should not save article without title" do
article = Article.new
assert_not article.save, "Saved the article without a title"
end
Al ejecutar esta prueba se muestra el mensaje de aserción más amigable:
Failure:
ArticleTest#test_should_not_save_article_without_title [/path/to/blog/test/models/article_test.rb:6]:
Saved the article without a title
Ahora para pasar esta prueba podemos agregar una validación de nivel de modelo para el campo de título.
class Article < ApplicationRecord
validates :title, presence: true
end
Ahora la prueba debe pasar. Vamos a verificar la ejecución de la prueba de nuevo:
$ bin/rails test test/models/article_test.rb:6
Run options: --seed 31252
# Running:
.
Finished in 0.027476s, 36.3952 runs/s, 36.3952 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
Ahora, si te fijaste, primero escribimos una prueba que falla para una funcionalidad deseada, luego escribimos algún código que agrega la funcionalidad y finalmente nos aseguramos de que nuestra prueba pase. Este enfoque para el desarrollo de software se conoce como Test-Driven Development (TDD).
Para ver cómo se informa de un error, aquí hay una prueba que contiene un error:
test "should report error" do
# some_undefined_variable is not defined elsewhere in the test case
some_undefined_variable
assert true
end
Ahora puedes ver más información en la salida de la consola ejecutando las pruebas:
$ bin/rails test test/models/article_test.rb
Run options: --seed 1808
# Running:
.E
Error:
ArticleTest#test_should_report_error:
NameError: undefined local variable or method 'some_undefined_variable' for #<ArticleTest:0x007fee3aa71798>
test/models/article_test.rb:11:in 'block in <class:ArticleTest>'
bin/rails test test/models/article_test.rb:9
Finished in 0.040609s, 49.2500 runs/s, 24.6250 assertions/s.
2 runs, 1 assertions, 0 failures, 1 errors, 0 skips
Observe el 'E' en la salida. Denota una prueba con error.
La ejecución de cada método de prueba se detiene en cuanto se produce un error o un fallo de aserción y el conjunto de pruebas continúa con el siguiente método. Todos los métodos de prueba se ejecutan en orden aleatorio. La opción
config.active_support.test_order
se puede utilizar para configurar el orden de las pruebas.
Cuando una prueba falla se le presenta el backtrace correspondiente. De forma predeterminada, Rails filtra el backtrace y sólo imprime líneas relevantes para su aplicación. Esto elimina el ruido del framework y ayuda a centrarse en su código. Sin embargo, hay situaciones en las que desea ver el backtrace completo. Simplemente establezca el argumento -b
(o --backtrace
) para habilitar este comportamiento:
$ bin/rails test -b test/models/article_test.rb
Si queremos que pase esta prueba, podemos modificarla para usar assert_raises
así:
test "should report error" do
# some_undefined_variable is not defined elsewhere in the test case
assert_raises(NameError) do
some_undefined_variable
end
end
Esta prueba debería pasar.
A estas alturas ya has visto algunas de las aserciones que están disponibles. Las aserciones son las abejas obreras de las pruebas. Son los que realmente realizan los chequeos para asegurarse de que las cosas van como están planificadas.
He aquí un extracto de las aserciones que puede utilizar con Minitest, la biblioteca de pruebas predeterminada utilizada por Rails. El parámetro [msg]
es un mensaje de cadena opcional que puede especificar para que los mensajes de error de prueba sean más claros.
Assertion | Purpose |
---|---|
assert( test, [msg] ) |
Se asegura quetest es true. |
assert_not( test, [msg] ) |
Ensures thattest es false. |
assert_equal( expected, actual, [msg] ) |
Se asegura queexpected == actual es true. |
assert_not_equal( expected, actual, [msg] ) |
Se asegura queexpected != actual es true. |
assert_same( expected, actual, [msg] ) |
Se asegura queexpected.equal?(actual) es true. |
assert_not_same( expected, actual, [msg] ) |
Se asegura queexpected.equal?(actual) es false. |
assert_nil( obj, [msg] ) |
Se asegura queobj.nil? es true. |
assert_not_nil( obj, [msg] ) |
Se asegura que obj.nil? es false. |
assert_empty( obj, [msg] ) |
Se asegura que obj isempty? . |
assert_not_empty( obj, [msg] ) |
Se asegura queobj no es empty? . |
assert_match( regexp, string, [msg] ) |
Se asegura que el match entre un string y una expresion regular es true. |
assert_no_match( regexp, string, [msg] ) |
Se asegura que un string no hace match con una expresion regular. |
assert_includes( collection, obj, [msg] ) |
Se asegura queobj esta en collection . |
assert_not_includes( collection, obj, [msg] ) |
Se asegura queobj no esta encollection . |
assert_in_delta( expected, actual, [delta], [msg] ) |
Se asegura que los numeros expected andactual are withindelta of each other. |
assert_not_in_delta( expected, actual, [delta], [msg] ) |
Se asegura que the numbersexpected andactual are not withindelta of each other. |
assert_throws( symbol, [msg] ) { block } |
Se asegura que el bloque dado lanza un simbolo |
assert_raises( exception1, exception2, ... ) { block } |
Se asegura que el bloque dado plantea una de las excepciones dadas. |
assert_instance_of( class, obj, [msg] ) |
Se asegura queobj es una instancia de la class . |
assert_not_instance_of( class, obj, [msg] ) |
Se asegura queobj no es una instancia de laclass . |
assert_kind_of( class, obj, [msg] ) |
Se asegura queobj es una instancia de la class o es decendente de la misma. |
assert_not_kind_of( class, obj, [msg] ) |
Se asegura queobj no es una instancia de laclass y no es decendente de la misma |
assert_respond_to( obj, symbol, [msg] ) |
Se asegura queobj responde a un symbol . |
assert_not_respond_to( obj, symbol, [msg] ) |
Se asegura queobj no responde a un symbol . |
assert_operator( obj1, operator, [obj2], [msg] ) |
Se asegura queobj1.operator(obj2) es true. |
assert_not_operator( obj1, operator, [obj2], [msg] ) |
Se asegura queobj1.operator(obj2) es false. |
assert_predicate ( obj, predicate, [msg] ) |
Se asegura queobj.predicate es true, por ejemplo.assert_predicate str, :empty? |
assert_not_predicate ( obj, predicate, [msg] ) |
Se asegura queobj.predicate es false, por ejemplo.assert_not_predicate str, :empty? |
flunk( [msg] ) |
Se asegura que sea failure. Esto es útil para marcar explícitamente una prueba que aún no ha terminado. |
Lo anterior es un subconjunto de afirmaciones que admite minitest. Para obtener una lista exhaustiva y actualizada, consulte la documentación de la API de Minitest, específicamente Minitest::Assertions.
Debido a la naturaleza modular del framework de testing, es posible crear sus propias aserciones. De hecho, eso es exactamente lo que hace Rails. Incluye algunas aserciones especializadas para hacer su vida más fácil.
Crear sus propias aserciones es un tema avanzado que no cubriremos en este tutorial.
Rails añade algunas aserciones personalizadas propias al framework de minitest:
Assertion | Purpose |
---|---|
assert_difference(expressions, difference = 1, message = nil) {...} |
Probar la diferencia numérica entre el valor devuelto de una expresión como resultado de lo que se evalúa en el bloque cedido. |
assert_no_difference(expressions, message = nil, &block) |
Afirma que el resultado numérico de evaluar una expresión no se cambia antes y después de invocar el pasado en un bloque. |
assert_nothing_raised { block } |
Asegura que el bloque dado no plantea ninguna excepción. |
assert_recognizes(expected_options, path, extras={}, message=nil) |
Afirma que el enrutamiento de la ruta de acceso dada se ha manejado correctamente y que las opciones analizadas (dadas en el hash expected_options) coinciden con la ruta. Básicamente, afirma que Rails reconoce la ruta dada por expected_options. |
assert_generates(expected_path, options, defaults={}, extras = {}, message=nil) |
Afirma que las opciones proporcionadas pueden usarse para generar la ruta de acceso proporcionada. Este es el inverso de assert_recognizes. El parámetro extras se utiliza para indicar a la solicitud los nombres y valores de los parámetros de petición adicionales que estarían en una cadena de consulta. El parámetro de mensaje le permite especificar un mensaje de error personalizado para los fallos de aserción. |
assert_response(type, message = nil) |
Afirma que la respuesta viene con un código de estado específico. Puede especificar :success para indicar 200-299, :redirect para indicar 300-399, :missing para indicar 404, o :errorpara coincidir con el rango 500-599. También puede pasar un número de estado explícito o su equivalente simbólico. Para obtener más información, consulte la lista completa de los códigos de estado y cómo. |
assert_redirected_to(options = {}, message=nil) |
Afirma que las opciones de redirección pasadas coinciden con las de la redirección que se llama en la acción más reciente. Este partido puede ser parcial, por ejemplo, que asert_redirected_to (controller: "weblog") también coincide con la redirección de redirector_to (controlador: "weblog", acción: "show") y así sucesivamente. También puede pasar rutas nombradas como asassert_redirected_to root_pathand Active Record objects such asassert_redirected_to @article. |
Veremos el uso de algunas de estas aserciones en el próximo capítulo.
Todas las aserciones básicas como assert_equal
definidas en Minitest::Assertions
también están disponibles en las clases que usamos en nuestros propios casos de prueba. De hecho, Rails proporciona las siguientes clases para que usted herede de:
ActiveSupport::TestCase
ActionMailer::TestCase
ActionView::TestCase
ActionDispatch::IntegrationTest
ActiveJob::TestCase
ActionDispatch::SystemTestCase
Cada una de estas clases incluye Minitest::Assertions
, lo que nos permite usar todas las aserciones básicas en nuestras pruebas.
Para obtener más información sobre Minitest, consulte su documentación.
Podemos ejecutar todas nuestras pruebas a la vez usando el comando bin / rails test
.
O podemos ejecutar un solo archivo de prueba pasando el comando bin / rails test
al nombre de archivo que contiene los casos de prueba.
$ bin/rails test test/models/article_test.rb
Run options: --seed 1559
# Running:
..
Finished in 0.027034s, 73.9810 runs/s, 110.9715 assertions/s.
2 runs, 3 assertions, 0 failures, 0 errors, 0 skips
Esto ejecutará todos los métodos de prueba del caso de prueba.
También puede ejecutar un método de prueba particular desde el caso de prueba proporcionando el indicador -n
o --name
y el nombre del método de la prueba.
$ bin/rails test test/models/article_test.rb -n test_the_truth
Run options: -n test_the_truth --seed 43583
# Running:
.
Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
También puede ejecutar una prueba en una línea específica proporcionando el número de línea.
$ bin/rails test test/models/article_test.rb:6 # run specific test and line
También puede ejecutar un directorio completo de pruebas proporcionando la ruta al directorio.
$ bin/rails test test/controllers # run all tests from specific directory
El corredor de prueba también proporciona muchas otras características como failing fast, diferir la salida de la prueba al final de la prueba, y así sucesivamente. Compruebe la documentación del test runner de la siguiente manera:
$ bin/rails test -h
minitest options:
-h, --help Display this help.
-s, --seed SEED Sets random seed. Also via env. Eg: SEED=n rake
-v, --verbose Verbose. Show progress processing files.
-n, --name PATTERN Filter run on /regexp/ or string.
--exclude PATTERN Exclude /regexp/ or string from run.
Known extensions: rails, pride
Usage: bin/rails test [options] [files or directories]
You can run a single test by appending a line number to a filename:
bin/rails test test/models/user_test.rb:27
You can run multiple files and directories at the same time:
bin/rails test test/controllers test/integration/login_test.rb
By default test failures and errors are reported inline during a run.
Rails options:
-e, --environment ENV Run tests in the ENV environment
-b, --backtrace Show the complete backtrace
-d, --defer-output Output test failures and errors after the test run
-f, --fail-fast Abort test run on first failure or error
-c, --[no-]color Enable color in the output