1 ¿Por qué escribir pruebas para tus aplicaciones Rails?
Rails facilita enormemente la escritura de tus pruebas. Comienza produciendo código de prueba esqueleto mientras creas tus modelos y controladores.
Al ejecutar tus pruebas de Rails puedes asegurarte de que tu código se adhiera a la funcionalidad deseada incluso después de una importante refactorización del código.
Las pruebas de Rails también pueden simular solicitudes de navegador y así puedes probar la respuesta de tu aplicación sin tener que probarla a través de tu navegador.
2 Introducción a las Pruebas
El soporte para pruebas fue integrado en el tejido de Rails desde el principio. No fue una epifanía de "oh! agreguemos soporte para ejecutar pruebas porque son nuevas y geniales".
2.1 Rails se prepara para las pruebas desde el principio
Rails crea un directorio test
para ti tan pronto como creas un proyecto Rails usando rails new
nombre_de_aplicación. Si listas los contenidos de este directorio, verás:
$ ls -F test
application_system_test_case.rb controllers/ helpers/ mailers/ system/
channels/ fixtures/ integration/ models/ test_helper.rb
Los directorios helpers
, mailers
y models
están destinados a contener pruebas para ayudantes de vista, mailers y modelos, respectivamente. El directorio channels
está destinado a contener pruebas para la conexión y canales de Action Cable. El directorio controllers
está destinado a contener pruebas para controladores, rutas y vistas. El directorio integration
está destinado a contener pruebas para interacciones entre controladores.
El directorio de pruebas del sistema contiene pruebas del sistema, que se utilizan para pruebas completas del navegador de tu aplicación. Las pruebas del sistema te permiten probar tu aplicación de la manera en que tus usuarios la experimentan y también te ayudan a probar tu JavaScript. Las pruebas del sistema heredan de Capybara y realizan pruebas en el navegador para tu aplicación.
Los fixtures son una forma de organizar los datos de prueba; residen en el directorio fixtures
.
También se creará un directorio jobs
cuando se genere por primera vez una prueba asociada.
El archivo test_helper.rb
contiene la configuración predeterminada para tus pruebas.
El archivo application_system_test_case.rb
contiene la configuración predeterminada para tus pruebas del sistema.
2.2 El Entorno de Pruebas
Por defecto, cada aplicación Rails tiene tres entornos: desarrollo, prueba y producción.
La configuración de cada entorno se puede modificar de manera similar. En este caso, podemos modificar nuestro entorno de prueba cambiando las opciones que se encuentran en config/environments/test.rb
.
NOTA: Tus pruebas se ejecutan bajo RAILS_ENV=test
.
2.3 Rails se encuentra con Minitest
Si recuerdas, usamos el comando bin/rails generate model
en la guía
Comenzando con Rails. Creamos nuestro primer modelo, y entre otras cosas, creó plantillas de prueba en el directorio test
:
$ 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
...
La plantilla de prueba predeterminada en test/models/article_test.rb
se ve así:
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 te ayudará a orientarte en el código y la terminología de pruebas de Rails.
require "test_helper"
Al requerir este archivo, test_helper.rb
, se carga la configuración predeterminada para ejecutar nuestras pruebas. Incluiremos esto con todas las pruebas que escribamos, por lo que cualquier método agregado a este archivo estará disponible para todas nuestras pruebas.
class ArticleTest < ActiveSupport::TestCase
# ...
end
La clase ArticleTest
define un caso de prueba porque hereda de ActiveSupport::TestCase
. ArticleTest
tiene así todos los métodos disponibles de ActiveSupport::TestCase
. Más adelante en esta guía, veremos algunos de los métodos que nos proporciona.
Cualquier método definido dentro de una clase heredada de Minitest::Test
(que es la superclase de ActiveSupport::TestCase
) que comience con test_
se llama simplemente una prueba. 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 test
que toma un nombre de prueba y un bloque. Genera una prueba normal de Minitest::Unit
con nombres de método 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
Lo cual es aproximadamente lo mismo que escribir esto:
def test_the_truth
assert true
end
Aunque aún puedes usar definiciones de métodos regulares, usar la macro test
permite un nombre de prueba más legible.
NOTA: El nombre del método se genera reemplazando espacios con guiones bajos. Sin embargo, el resultado no necesita ser un identificador Ruby válido: el nombre puede contener caracteres de puntuación, etc. Eso se debe a que, técnicamente, en Ruby cualquier cadena puede ser un nombre de método. Esto puede requerir el uso de llamadas a define_method
y send
para funcionar correctamente, pero formalmente hay pocas restricciones sobre el nombre.
A continuación, veamos nuestra primera afirmación:
assert true
Una afirmación es una línea de código que evalúa un objeto (o expresión) para obtener resultados esperados. Por ejemplo, una afirmación puede verificar:
- ¿este valor = ese valor?
- ¿es este objeto nulo?
- ¿esta línea de código lanza una excepción?
- ¿es la contraseña del usuario mayor de 5 caracteres?
Cada prueba puede contener una o más afirmaciones, sin restricción sobre cuántas afirmaciones se permiten. Solo cuando todas las afirmaciones son exitosas, la prueba pasará.
2.3.1 Tu Primera Prueba Fallida
Para ver cómo se informa un fallo en la prueba, puedes agregar una prueba fallida al caso de prueba article_test.rb
.
test "should not save article without title" do
article = Article.new
assert_not article.save
end
Ejecutemos 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. Puedes ver el rastro correspondiente que se muestra bajo Failure
junto con el nombre de la prueba fallida. Las siguientes líneas contienen el rastro de la pila seguido de un mensaje que menciona el valor real y el valor esperado por la afirmación. Los mensajes de afirmación predeterminados proporcionan suficiente información para ayudar a identificar el error. Para hacer que el mensaje de fallo de la afirmación sea más legible, cada afirmació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
Ejecutar esta prueba muestra el mensaje de afirmació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 que esta prueba pase, podemos agregar una validación a nivel de modelo para el campo title.
class Article < ApplicationRecord
validates :title, presence: true
end
Ahora la prueba debería pasar. Verifiquemos ejecutando la prueba nuevamente:
$ 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 diste cuenta, primero escribimos una prueba que falla para una funcionalidad deseada, luego escribimos un poco de 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 Desarrollo Basado en Pruebas (TDD).
2.3.2 Cómo se ve un Error
Para ver cómo se informa un error, aquí hay una prueba que contiene un error:
test "should report error" do
# some_undefined_variable no está definida en otra parte del caso de prueba
some_undefined_variable
assert true
end
Ahora puedes ver aún más salida en la consola al ejecutar 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
Observa la 'E' en la salida. Denota una prueba con error.
NOTA: La ejecución de cada método de prueba se detiene tan pronto como se encuentra un error o un fallo en la afirmació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 usar para configurar el orden de las pruebas.
Cuando una prueba falla, se te presenta el rastro correspondiente. Por defecto, Rails filtra ese rastro y solo imprimirá líneas relevantes para tu aplicación. Esto elimina el ruido del marco y ayuda a centrarse en tu código. Sin embargo, hay situaciones en las que deseas ver el rastro completo. Establece el argumento -b
(o --backtrace
) para habilitar este comportamiento:
$ bin/rails test -b test/models/article_test.rb
Si queremos que esta prueba pase, podemos modificarla para usar assert_raises
así:
test "should report error" do
# some_undefined_variable no está definida en otra parte del caso de prueba
assert_raises(NameError) do
some_undefined_variable
end
end
Esta prueba debería pasar ahora.
2.4 Afirmaciones Disponibles
Hasta ahora has visto un vistazo de algunas de las afirmaciones que están disponibles. Las afirmaciones son las abejas obreras de las pruebas. Son las que realizan realmente las comprobaciones para asegurarse de que las cosas van según lo planeado.
Aquí hay un extracto de las afirmaciones que puedes usar con Minitest
, la biblioteca de pruebas predeterminada utilizada por Rails. El parámetro [msg]
es un mensaje de cadena opcional que puedes especificar para hacer más claros los mensajes de fallo de tus pruebas.
Afirmación | Propósito |
---|---|
assert( test, [msg] ) |
Asegura que test es verdadero. |
assert_not( test, [msg] ) |
Asegura que test es falso. |
assert_equal( expected, actual, [msg] ) |
Asegura que expected == actual es verdadero. |
assert_not_equal( expected, actual, [msg] ) |
Asegura que expected != actual es verdadero. |
assert_same( expected, actual, [msg] ) |
Asegura que expected.equal?(actual) es verdadero. |
assert_not_same( expected, actual, [msg] ) |
Asegura que expected.equal?(actual) es falso. |
assert_nil( obj, [msg] ) |
Asegura que obj.nil? es verdadero. |
assert_not_nil( obj, [msg] ) |
Asegura que obj.nil? es falso. |
assert_empty( obj, [msg] ) |
Asegura que obj está empty? . |
assert_not_empty( obj, [msg] ) |
Asegura que obj no está empty? . |
assert_match( regexp, string, [msg] ) |
Asegura que una cadena coincide con la expresión regular. |
assert_no_match( regexp, string, [msg] ) |
Asegura que una cadena no coincide con la expresión regular. |
assert_includes( collection, obj, [msg] ) |
Asegura que obj está en collection . |
assert_not_includes( collection, obj, [msg] ) |
Asegura que obj no está en collection . |
assert_in_delta( expected, actual, [delta], [msg] ) |
Asegura que los números expected y actual están dentro de delta entre sí. |
assert_not_in_delta( expected, actual, [delta], [msg] ) |
Asegura que los números expected y actual no están dentro de delta entre sí. |
assert_in_epsilon ( expected, actual, [epsilon], [msg] ) |
Asegura que los números expected y actual tienen un error relativo menor que epsilon . |
assert_not_in_epsilon ( expected, actual, [epsilon], [msg] ) |
Asegura que los números expected y actual no tienen un error relativo menor que epsilon . |
assert_throws( symbol, [msg] ) { block } |
Asegura que el bloque dado lanza el símbolo. |
assert_raises( exception1, exception2, ... ) { block } |
Asegura que el bloque dado lanza una de las excepciones dadas. |
assert_instance_of( class, obj, [msg] ) |
Asegura que obj es una instancia de class . |
assert_not_instance_of( class, obj, [msg] ) |
Asegura que obj no es una instancia de class . |
assert_kind_of( class, obj, [msg] ) |
Asegura que obj es una instancia de class o desciende de ella. |
assert_not_kind_of( class, obj, [msg] ) |
Asegura que obj no es una instancia de class y no desciende de ella. |
assert_respond_to( obj, symbol, [msg] ) |
Asegura que obj responde a symbol . |
assert_not_respond_to( obj, symbol, [msg] ) |
Asegura que obj no responde a symbol . |
assert_operator( obj1, operator, [obj2], [msg] ) |
Asegura que obj1.operator(obj2) es verdadero. |
assert_not_operator( obj1, operator, [obj2], [msg] ) |
Asegura que obj1.operator(obj2) es falso. |
assert_predicate ( obj, predicate, [msg] ) |
Asegura que obj.predicate es verdadero, por ejemplo, assert_predicate str, :empty? |
assert_not_predicate ( obj, predicate, [msg] ) |
Asegura que obj.predicate es falso, por ejemplo, assert_not_predicate str, :empty? |
assert_error_reported(class) { block } |
Asegura que se ha informado de la clase de error, por ejemplo, assert_error_reported IOError { Rails.error.report(IOError.new("Oops")) } |
assert_no_error_reported { block } |
Asegura que no se han informado errores, por ejemplo, assert_no_error_reported { perform_service } |
flunk( [msg] ) |
Asegura el fallo. Esto es útil para marcar explícitamente una prueba que aún no está terminada. |
Las anteriores son un subconjunto de las afirmaciones que admite minitest. Para una lista exhaustiva y más actualizada, consulta la documentación de la API de Minitest, específicamente Minitest::Assertions
.
Debido a la naturaleza modular del marco de pruebas, es posible crear tus propias afirmaciones. De hecho, eso es exactamente lo que hace Rails. Incluye algunas afirmaciones especializadas para facilitarte la vida.
NOTA: Crear tus propias afirmaciones es un tema avanzado que no cubriremos en este tutorial.
2.5 Afirmaciones Específicas de Rails
Rails agrega algunas afirmaciones personalizadas propias al marco minitest
:
Afirmación | Propósito |
---|---|
assert_difference(expressions, difference = 1, message = nil) {...} |
Prueba la diferencia numérica entre el valor de retorno de una expresión como resultado de lo que se evalúa en el bloque generado. |
assert_no_difference(expressions, message = nil, &block) |
Asegura que el resultado numérico de evaluar una expresión no cambie antes y después de invocar el bloque pasado. |
assert_changes(expressions, message = nil, from:, to:, &block) |
Prueba que el resultado de evaluar una expresión cambie después de invocar el bloque pasado. |
assert_no_changes(expressions, message = nil, &block) |
Prueba que el resultado de evaluar una expresión no cambie después de invocar el bloque pasado. |
assert_nothing_raised { block } |
Asegura que el bloque dado no arroja ninguna excepción. |
assert_recognizes(expected_options, path, extras={}, message=nil) |
Asegura que el enrutamiento de la ruta dada se manejó correctamente y que las opciones analizadas (dadas en el hash expected_options) coinciden con la ruta. Básicamente, asegura que Rails reconoce la ruta dada por expected_options. |
assert_generates(expected_path, options, defaults={}, extras = {}, message=nil) |
Asegura que las opciones proporcionadas se puedan usar para generar la ruta proporcionada. Este es el inverso de assert_recognizes. El parámetro extras se usa para indicar los nombres y valores de parámetros de solicitud adicionales que estarían en una cadena de consulta. El parámetro message te permite especificar un mensaje de error personalizado para fallos de afirmación. |
assert_response(type, message = nil) |
Asegura que la respuesta venga con un código de estado específico. Puedes especificar :success para indicar 200-299, :redirect para indicar 300-399, :missing para indicar 404, o :error para coincidir con el rango 500-599. También puedes pasar un número de estado explícito o su equivalente simbólico. Para más información, consulta lista completa de códigos de estado y cómo funciona su mapeo. |
assert_redirected_to(options = {}, message=nil) |
Asegura que la respuesta es una redirección a una URL que coincide con las opciones dadas. También puedes pasar rutas nombradas como assert_redirected_to root_path y objetos Active Record como assert_redirected_to @article . |
assert_queries_count(count = nil, include_schema: false, &block) |
Asegura que &block genera un número int de consultas SQL. |
assert_no_queries(include_schema: false, &block) |
Asegura que &block no genera consultas SQL. |
assert_queries_match(pattern, count: nil, include_schema: false, &block) |
Asegura que &block genera consultas SQL que coinciden con el patrón. |
assert_no_queries_match(pattern, &block) |
Asegura que &block no genera consultas SQL que coinciden con el patrón. |
Verás el uso de algunas de estas afirmaciones en el próximo capítulo.
2.6 Una Breve Nota sobre los Casos de Prueba
Todas las afirmaciones 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 heredes de ellas:
ActiveSupport::TestCase
ActionMailer::TestCase
ActionView::TestCase
ActiveJob::TestCase
ActionDispatch::IntegrationTest
ActionDispatch::SystemTestCase
Rails::Generators::TestCase
Cada una de estas clases incluye Minitest::Assertions
, lo que nos permite usar todas las afirmaciones básicas en nuestras pruebas.
NOTA: Para más información sobre Minitest
, consulta su documentación.
2.7 Transacciones
Por defecto, Rails envuelve automáticamente las pruebas en una transacción de base de datos que se revierte después de que terminan. Esto hace que las pruebas sean independientes entre sí y los cambios en la base de datos solo son visibles dentro de una sola prueba.
class MyTest < ActiveSupport::TestCase
test "newly created users are active by default" do
# Dado que la prueba está implícitamente envuelta en una transacción de base de datos, el usuario
# creado aquí no será visto por otras pruebas.
assert User.create.active?
end
end
El método ActiveRecord::Base.current_transaction
aún funciona como se espera, sin embargo:
class MyTest < ActiveSupport::TestCase
test "current_transaction" do
# La transacción implícita alrededor de las pruebas no interfiere con la
# semántica de nivel de aplicación de current_transaction.
assert User.current_transaction.blank?
end
end
Si hay múltiples bases de datos de escritura en su lugar, las pruebas se envuelven en tantas transacciones respectivas, y todas ellas se revierten.
2.7.1 Optar por No Usar Transacciones de Prueba
Los casos de prueba individuales pueden optar por no usarlas:
class MyTest < ActiveSupport::TestCase
# No hay transacción de base de datos implícita que envuelva las pruebas en este caso de prueba.
self.use_transactional_tests = false
end
2.8 El Ejecutor de Pruebas de Rails
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
el nombre del 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 puedes ejecutar un método de prueba particular del caso de prueba proporcionando la bandera -n
o --name
y el nombre del método de 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 puedes 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 # ejecutar prueba específica y línea
También puedes ejecutar un rango de pruebas proporcionando el rango de líneas.
$ bin/rails test test/models/article_test.rb:6-20 # ejecuta pruebas desde la línea 6 hasta la 20
También puedes ejecutar un directorio completo de pruebas proporcionando la ruta al directorio.
$ bin/rails test test/controllers # ejecutar todas las pruebas de un directorio específico
El ejecutor de pruebas también proporciona muchas otras características como fallar rápido, diferir la salida de las pruebas al final de la ejecución de la prueba, etc. Consulta la documentación del ejecutor de pruebas de la siguiente manera:
$ bin/rails test -h
Usage:
bin/rails test [PATHS...]
Run tests except system tests
Examples:
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 tests with in a line range by appending the line range to a filename:
bin/rails test test/models/user_test.rb:10-20
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.
minitest options:
-h, --help Display this help.
--no-plugins Bypass minitest plugin auto-loading (or set $MT_NO_PLUGINS).
-s, --seed SEED Sets random seed. Also via env. Eg: SEED=n rake
-v, --verbose Verbose. Show progress processing files.
-q, --quiet Quiet. Show no progress processing files.
--show-skips Show skipped at the end of run.
-n, --name PATTERN Filter run on /regexp/ or string.
--exclude PATTERN Exclude /regexp/ or string from run.
-S, --skip CODES Skip reporting of certain types of results (eg E).
Known extensions: rails, pride
-w, --warnings Run with Ruby warnings enabled
-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
--profile [COUNT] Enable profiling of tests and list the slowest test cases (default: 10)
-p, --pride Pride. Show your testing pride!
2.9 Ejecutando pruebas en Integración Continua (CI)
Para ejecutar todas las pruebas en un entorno CI, solo hay un comando que necesitas:
$ bin/rails test
Si estás usando Pruebas de Sistema, bin/rails test
no las ejecutará, ya que pueden ser lentas. Para ejecutarlas también, agrega otro paso de CI que ejecute bin/rails test:system
, o cambia tu primer paso a bin/rails test:all
, que ejecuta todas las pruebas incluidas las pruebas del sistema.
3 Pruebas Paralelas
Las pruebas paralelas te permiten paralelizar tu conjunto de pruebas. Si bien el método predeterminado es bifurcar procesos, también se admite el uso de hilos. Ejecutar pruebas en paralelo reduce el tiempo que tarda en ejecutarse todo tu conjunto de pruebas.
3.1 Pruebas Paralelas con Procesos
El método de paralelización predeterminado es bifurcar procesos usando el sistema DRb de Ruby. Los procesos se bifurcan según el número de trabajadores proporcionados. El número predeterminado es el recuento real de núcleos en la máquina en la que te encuentras, pero se puede cambiar por el número pasado al método parallelize.
Para habilitar la paralelización, agrega lo siguiente a tu test_helper.rb
:
class ActiveSupport::TestCase
parallelize(workers: 2)
end
El número de trabajadores pasado es el número de veces que se bifurcará el proceso. Es posible que desees paralelizar tu conjunto de pruebas local de manera diferente a tu CI, por lo que se proporciona una variable de entorno para poder cambiar fácilmente el número de trabajadores que debe usar una ejecución de prueba:
$ PARALLEL_WORKERS=15 bin/rails test
Al paralelizar pruebas, Active Record maneja automáticamente la creación de una base de datos y la carga del esquema en la base de datos para cada proceso. Las bases de datos se sufijarán con el número correspondiente al trabajador. Por ejemplo, si tienes 2 trabajadores, las pruebas crearán test-database-0
y test-database-1
respectivamente.
Si el número de trabajadores pasado es 1 o menos, los procesos no se bifurcarán y las pruebas no se paralelizarán y usarán la base de datos test-database
original.
Se proporcionan dos ganchos, uno se ejecuta cuando se bifurca el proceso y otro se ejecuta antes de que se cierre el proceso bifurcado. Estos pueden ser útiles si tu aplicación usa múltiples bases de datos o realiza otras tareas que dependen del número de trabajadores.
El método parallelize_setup
se llama justo después de que se bifurcan los procesos. El método parallelize_teardown
se llama justo antes de que se cierren los procesos.
class ActiveSupport::TestCase
parallelize_setup do |worker|
# configurar bases de datos
end
parallelize_teardown do |worker|
# limpiar bases de datos
end
parallelize(workers: :number_of_processors)
end
Estos métodos no son necesarios ni están disponibles cuando se utilizan pruebas paralelas con hilos.
3.2 Pruebas Paralelas con Hilos
Si prefieres usar hilos o estás usando JRuby, se proporciona una opción de paralelización con hilos. El paralelizador con hilos está respaldado por el Parallel::Executor
de Minitest.
Para cambiar el método de paralelización para usar hilos en lugar de bifurcaciones, coloca lo siguiente en tu test_helper.rb
:
class ActiveSupport::TestCase
parallelize(workers: :number_of_processors, with: :threads)
end
Las aplicaciones Rails generadas desde JRuby o TruffleRuby incluirán automáticamente la opción with: :threads
.
El número de trabajadores pasado a parallelize
determina el número de hilos que las pruebas usarán. Es posible que desees paralelizar tu conjunto de pruebas local de manera diferente a tu CI, por lo que se proporciona una variable de entorno para poder cambiar fácilmente el número de trabajadores que debe usar una ejecución de prueba:
$ PARALLEL_WORKERS=15 bin/rails test
3.3 Pruebas de Transacciones Paralelas
Cuando deseas probar el código que ejecuta transacciones de base de datos paralelas en hilos, estas pueden bloquearse entre sí porque ya están anidadas bajo la transacción implícita de prueba.
Para solucionar esto, puedes deshabilitar las transacciones en una clase de caso de prueba configurando self.use_transactional_tests = false
:
class WorkerTest < ActiveSupport::TestCase
self.use_transactional_tests = false
test "parallel transactions" do
# iniciar algunos hilos que crean transacciones
end
end
NOTA: Con pruebas transaccionales deshabilitadas, debes limpiar cualquier dato que las pruebas creen ya que los cambios no se revierten automáticamente después de que la prueba se completa.
3.4 Umbral para paralelizar pruebas
Ejecutar pruebas en paralelo agrega una sobrecarga en términos de configuración de la base de datos y carga de fixtures. Debido a esto, Rails no paralelizará ejecuciones que involucren menos de 50 pruebas.
Puedes configurar este umbral en tu test.rb
:
config.active_support.test_parallelization_threshold = 100
Y también al configurar la paralelización a nivel del caso de prueba:
class ActiveSupport::TestCase
parallelize threshold: 100
end
4 La Base de Datos de Prueba
Casi todas las aplicaciones Rails interactúan mucho con una base de datos y, como resultado, tus pruebas necesitarán una base de datos para interactuar también. Para escribir pruebas eficientes, necesitarás entender cómo configurar esta base de datos y poblarla con datos de ejemplo.
Por defecto, cada aplicación Rails tiene tres entornos: desarrollo, prueba y producción. La base de datos para cada uno de ellos se configura en config/database.yml
.
Una base de datos de prueba dedicada te permite configurar e interactuar con datos de prueba de forma aislada. De esta manera, tus pruebas pueden manipular los datos de prueba con confianza, sin preocuparse por los datos en las bases de datos de desarrollo o producción.
4.1 Manteniendo el Esquema de la Base de Datos de Prueba
Para ejecutar tus pruebas, tu base de datos de prueba necesitará tener la estructura actual. El ayudante de prueba verifica si tu base de datos de prueba tiene alguna migración pendiente. Intentará cargar tu db/schema.rb
o db/structure.sql
en la base de datos de prueba. Si las migraciones aún están pendientes, se generará un error. Por lo general, esto indica que tu esquema no está completamente migrado. Ejecutar las migraciones contra la base de datos de desarrollo (bin/rails db:migrate
) actualizará el esquema.
NOTA: Si hubo modificaciones en las migraciones existentes, la base de datos de prueba necesita ser reconstruida. Esto se puede hacer ejecutando bin/rails db:test:prepare
.
4.2 Lo Básico sobre los Fixtures
Para buenas pruebas, necesitarás pensar en cómo configurar los datos de prueba. En Rails, puedes manejar esto definiendo y personalizando fixtures. Puedes encontrar documentación completa en la documentación de la API de Fixtures.
4.2.1 ¿Qué son los Fixtures?
Fixtures es una palabra elegante para datos de ejemplo. Los fixtures te permiten poblar tu base de datos de prueba con datos predefinidos antes de que se ejecuten tus pruebas. Los fixtures son independientes de la base de datos y están escritos en YAML. Hay un archivo por modelo.
NOTA: Los fixtures no están diseñados para crear cada objeto que tus pruebas necesiten, y se manejan mejor cuando solo se usan para datos predeterminados que se pueden aplicar al caso común.
Encontrarás fixtures bajo tu directorio test/fixtures
. Cuando ejecutas bin/rails generate model
para crear un nuevo modelo, Rails crea automáticamente plantillas de fixtures en este directorio.
4.2.2 YAML
Los fixtures formateados en YAML son una forma amigable para describir tus datos de ejemplo. Estos tipos de fixtures tienen la extensión de archivo .yml (como en users.yml
).
Aquí hay un archivo de fixture YAML de ejemplo:
# ¡mira y asómbrate! ¡Soy un comentario YAML!
david:
name: David Heinemeier Hansson
birthday: 1979-10-15
profession: Systems development
steve:
name: Steve Ross Kellock
birthday: 1974-09-27
profession: guy with keyboard
Cada fixture se le da un nombre seguido de una lista indentada de pares clave/valor separados por dos puntos. Los registros se separan típicamente por una línea en blanco. Puedes colocar comentarios en un archivo de fixture usando el carácter # en la primera columna.
Si estás trabajando con asociaciones, puedes definir un nodo de referencia entre dos fixtures diferentes. Aquí tienes un ejemplo con una asociación belongs_to
/has_many
:
# test/fixtures/categories.yml
about:
name: About
# test/fixtures/articles.yml
first:
title: Welcome to Rails!
category: about
# test/fixtures/action_text/rich_texts.yml
first_content:
record: first (Article)
name: content
body: <div>Hello, from <strong>a fixture</strong></div>
Observa que la clave category
del primer Artículo encontrado en fixtures/articles.yml
tiene un valor de about
, y que la clave record
de la entrada first_content
encontrada en fixtures/action_text/rich_texts.yml
tiene un valor de first (Article)
. Esto sugiere a Active Record cargar la Categoría about
encontrada en fixtures/categories.yml
para el primero, y a Action Text cargar el Artículo first
encontrado en fixtures/articles.yml
para el último.
NOTA: Para que las asociaciones se refieran entre sí por nombre, puedes usar el nombre del fixture en lugar de especificar el atributo id:
en los fixtures asociados. Rails asignará automáticamente una clave primaria para ser consistente entre ejecuciones. Para obtener más información sobre este comportamiento de asociación, lee la documentación de la API de Fixtures.
4.2.3 Fixtures de Archivos Adjuntos
Al igual que otros modelos respaldados por Active Record, los registros de archivos adjuntos de Active Storage heredan de instancias de ActiveRecord::Base y, por lo tanto, pueden ser poblados por fixtures.
Considera un modelo Article
que tiene una imagen asociada como un archivo adjunto thumbnail
, junto con datos de fixtures en YAML:
class Article < ApplicationRecord
has_one_attached :thumbnail
end
# test/fixtures/articles.yml
first:
title: An Article
Suponiendo que hay un archivo codificado como image/png en test/fixtures/files/first.png
, las siguientes entradas de fixtures en YAML generarán los registros relacionados ActiveStorage::Blob
y ActiveStorage::Attachment
:
# test/fixtures/active_storage/blobs.yml
first_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob filename: "first.png" %>
# test/fixtures/active_storage/attachments.yml
first_thumbnail_attachment:
name: thumbnail
record: first (Article)
blob: first_thumbnail_blob
4.2.4 Usando ERB
ERB te permite incrustar código Ruby dentro de plantillas. El formato de fixture YAML se preprocesa con ERB cuando Rails carga los fixtures. Esto te permite usar Ruby para ayudarte a generar algunos datos de ejemplo. Por ejemplo, el siguiente código genera mil usuarios:
<% 1000.times do |n| %>
user_<%= n %>:
username: <%= "user#{n}" %>
email: <%= "user#{n}@example.com" %>
<% end %>
4.2.5 Fixtures en Acción
Rails carga automáticamente todos los fixtures del directorio test/fixtures
por defecto. La carga implica tres pasos:
- Eliminar cualquier dato existente en la tabla correspondiente al fixture
- Cargar los datos del fixture en la tabla
- Volcar los datos del fixture en un método en caso de que desees acceder a ellos directamente
CONSEJO: Para eliminar datos existentes de la base de datos, Rails intenta deshabilitar los triggers de integridad referencial (como claves foráneas y restricciones de verificación). Si estás recibiendo errores de permisos molestos al ejecutar pruebas, asegúrate de que el usuario de la base de datos tenga privilegio para deshabilitar estos triggers en el entorno de prueba. (En PostgreSQL, solo los superusuarios pueden deshabilitar todos los triggers. Lee más sobre los permisos de PostgreSQL aquí).
4.2.6 Los Fixtures son Objetos Active Record
Los fixtures son instancias de Active Record. Como se mencionó en el punto #3 arriba, puedes acceder al objeto directamente porque está automáticamente disponible como un método cuyo alcance es local del caso de prueba. Por ejemplo:
# esto devolverá el objeto User para el fixture llamado david
users(:david)
# esto devolverá la propiedad para david llamada id
users(:david).id
# también se pueden acceder a métodos disponibles en la clase User
david = users(:david)
david.call(david.partner)
Para obtener múltiples fixtures a la vez, puedes pasar una lista de nombres de fixtures. Por ejemplo:
# esto devolverá un array que contiene los fixtures david y steve
users(:david, :steve)
5 Pruebas de Modelo
Las pruebas de modelo se utilizan para probar los diversos modelos de tu aplicación.
Las pruebas de modelo de Rails se almacenan en el directorio test/models
. Rails proporciona un generador para crear un esqueleto de prueba de modelo para ti.
$ bin/rails generate test_unit:model article title:string body:text
create test/models/article_test.rb
create test/fixtures/articles.yml
Las pruebas de modelo no tienen su propia superclase como ActionMailer::TestCase
. En su lugar, heredan de ActiveSupport::TestCase
.
6 Pruebas de Sistema
Las pruebas de sistema te permiten probar las interacciones del usuario con tu aplicación, ejecutando pruebas en un navegador real o sin cabeza. Las pruebas de sistema utilizan Capybara bajo el capó.
Para crear pruebas de sistema en Rails, usas el directorio test/system
en tu aplicación. Rails proporciona un generador para crear un esqueleto de prueba de sistema para ti.
$ bin/rails generate system_test users
invoke test_unit
create test/system/users_test.rb
Aquí tienes cómo se ve una prueba de sistema recién generada:
require "application_system_test_case"
class UsersTest < ApplicationSystemTestCase
# test "visiting the index" do
# visit users_url
#
# assert_selector "h1", text: "Users"
# end
end
Por defecto, las pruebas de sistema se ejecutan con el controlador Selenium, usando el navegador Chrome, y un tamaño de pantalla de 1400x1400. La siguiente sección explica cómo cambiar la configuración predeterminada.
Por defecto, Rails intentará rescatar de las excepciones planteadas durante las pruebas y responder con páginas de error HTML. Este comportamiento se puede controlar mediante la configuración config.action_dispatch.show_exceptions
.
6.1 Cambiando la Configuración Predeterminada
Rails hace que cambiar la configuración predeterminada para las pruebas de sistema sea muy simple. Toda la configuración está abstraída para que puedas centrarte en escribir tus pruebas.
Cuando generas una nueva aplicación o scaffold, se crea un archivo application_system_test_case.rb
en el directorio de pruebas. Aquí es donde debe vivir toda la configuración para tus pruebas de sistema.
Si deseas cambiar la configuración predeterminada, puedes cambiar lo que las pruebas de sistema están "impulsadas por". Digamos que deseas cambiar el controlador de Selenium a Cuprite. Primero agrega la gema cuprite
a tu Gemfile
. Luego, en tu archivo application_system_test_case.rb
, haz lo siguiente:
require "test_helper"
require "capybara/cuprite"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :cuprite
end
El nombre del controlador es un argumento requerido para driven_by
. Los argumentos opcionales que se pueden pasar a driven_by
son :using
para el navegador (esto solo será utilizado por Selenium), :screen_size
para cambiar el tamaño de la pantalla para capturas de pantalla, y :options
que se pueden usar para establecer opciones compatibles con el controlador.
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :firefox
end
Si deseas usar un navegador sin cabeza, podrías usar Chrome sin cabeza o Firefox sin cabeza agregando headless_chrome
o headless_firefox
en el argumento :using
.
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :headless_chrome
end
Si deseas usar un navegador remoto, por ejemplo, Chrome sin cabeza en Docker, debes agregar la url
remota y establecer browser
como remoto a través de options
.
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
url = ENV.fetch("SELENIUM_REMOTE_URL", nil)
options = if url
{ browser: :remote, url: url }
else
{ browser: :chrome }
end
driven_by :selenium, using: :headless_chrome, options: options
end
Ahora deberías obtener una conexión al navegador remoto.
$ SELENIUM_REMOTE_URL=http://localhost:4444/wd/hub bin/rails test:system
Si tu aplicación en prueba también se está ejecutando de forma remota, por ejemplo, un contenedor Docker, Capybara necesita más información sobre cómo llamar a servidores remotos.
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
def setup
Capybara.server_host = "0.0.0.0" # enlazar a todas las interfaces
Capybara.app_host = "http://#{IPSocket.getaddress(Socket.gethostname)}" if ENV["SELENIUM_REMOTE_URL"].present?
super
end
# ...
end
Ahora deberías obtener una conexión al navegador y servidor remotos, independientemente de si está ejecutándose en un contenedor Docker o CI.
Si tu configuración de Capybara requiere más configuración de la que proporciona Rails, esta configuración adicional podría agregarse en el archivo application_system_test_case.rb
.
Consulta la documentación de Capybara para configuraciones adicionales.
6.2 Ayudante de Captura de Pantalla
El ScreenshotHelper
es un ayudante diseñado para capturar capturas de pantalla de tus pruebas. Esto puede ser útil para ver el navegador en el punto en que una prueba falló, o para ver capturas de pantalla más tarde para depuración.
Se proporcionan dos métodos: take_screenshot
y take_failed_screenshot
. take_failed_screenshot
se incluye automáticamente en before_teardown
dentro de Rails.
El método de ayuda take_screenshot
se puede incluir en cualquier lugar de tus pruebas para tomar una captura de pantalla del navegador.
6.3 Implementando una Prueba de Sistema
Ahora vamos a agregar una prueba de sistema a nuestra aplicación de blog. Demostraremos escribiendo una prueba de sistema visitando la página de índice y creando un nuevo artículo de blog.
Si usaste el generador de scaffold, se creó automáticamente un esqueleto de prueba de sistema para ti. Si no usaste el generador de scaffold, comienza creando un esqueleto de prueba de sistema.
$ bin/rails generate system_test articles
Debería haber creado un archivo de prueba de marcador de posición para nosotros. Con la salida del comando anterior, deberías ver:
invoke test_unit
create test/system/articles_test.rb
Ahora abramos ese archivo y escribamos nuestra primera afirmación:
require "application_system_test_case"
class ArticlesTest < ApplicationSystemTestCase
test "viewing the index" do
visit articles_path
assert_selector "h1", text: "Articles"
end
end
La prueba debería ver que hay un h1
en la página de índice de artículos y pasar.
Ejecuta las pruebas del sistema.
$ bin/rails test:system
NOTA: Por defecto, ejecutar bin/rails test
no ejecutará tus pruebas de sistema. Asegúrate de ejecutar bin/rails test:system
para ejecutarlas realmente. También puedes ejecutar bin/rails test:all
para ejecutar todas las pruebas, incluidas las pruebas de sistema.
6.3.1 Creando una Prueba de Sistema de Artículos
Ahora probemos el flujo para crear un nuevo artículo en nuestro blog.
test "should create Article" do
visit articles_path
click_on "New Article"
fill_in "Title", with: "Creating an Article"
fill_in "Body", with: "Created this article successfully!"
click_on "Create Article"
assert_text "Creating an Article"
end
El primer paso es llamar a visit articles_path
. Esto llevará la prueba a la página de índice de artículos.
Luego, el click_on "New Article"
encontrará el botón "New Article" en la página de índice. Esto redirigirá el navegador a /articles/new
.
Luego, la prueba llenará el título y el cuerpo del artículo con el texto especificado. Una vez que se llenen los campos, se hace clic en "Create Article", lo que enviará una solicitud POST para crear el nuevo artículo en la base de datos.
Seremos redirigidos de vuelta a la página de índice de artículos y allí afirmamos que el texto del título del nuevo artículo está en la página de índice de artículos.
6.3.2 Probando para Múltiples Tamaños de Pantalla
Si deseas probar para tamaños móviles además de probar para escritorio, puedes crear otra clase que herede de ActionDispatch::SystemTestCase
y usarla en tu suite de pruebas. En este ejemplo, se crea un archivo llamado mobile_system_test_case.rb
en el directorio /test
con la siguiente configuración.
require "test_helper"
class MobileSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :chrome, screen_size: [375, 667]
end
Para usar esta configuración, crea una prueba dentro de test/system
que herede de MobileSystemTestCase
. Ahora puedes probar tu aplicación usando múltiples configuraciones diferentes.
require "mobile_system_test_case"
class PostsTest < MobileSystemTestCase
test "visiting the index" do
visit posts_url
assert_selector "h1", text: "Posts"
end
end
6.3.3 Llevándolo Más Allá
La belleza de las pruebas de sistema es que son similares a las pruebas de integración en el sentido de que prueban la interacción del usuario con tu controlador, modelo y vista, pero las pruebas de sistema son mucho más robustas y realmente prueban tu aplicación como si un usuario real la estuviera usando. En el futuro, puedes probar cualquier cosa que el usuario mismo haría en tu aplicación, como comentar, eliminar artículos, publicar artículos de borrador, etc.
7 Pruebas de Integración
Las pruebas de integración se utilizan para probar cómo interactúan varias partes de nuestra aplicación. Generalmente se utilizan para probar flujos de trabajo importantes dentro de nuestra aplicación.
Para crear pruebas de integración en Rails, usamos el directorio test/integration
para nuestra aplicación. Rails proporciona un generador para crear un esqueleto de prueba de integración para nosotros.
$ bin/rails generate integration_test user_flows
exists test/integration/
create test/integration/user_flows_test.rb
Aquí tienes cómo se ve una prueba de integración recién generada:
require "test_helper"
class UserFlowsTest < ActionDispatch::IntegrationTest
# test "the truth" do
# assert true
# end
end
Aquí la prueba hereda de ActionDispatch::IntegrationTest
. Esto hace que algunos ayudantes adicionales estén disponibles para que los usemos en nuestras pruebas de integración.
Por defecto, Rails intentará rescatar de las excepciones planteadas durante las pruebas y responder con páginas de error HTML. Este comportamiento se puede controlar mediante la configuración config.action_dispatch.show_exceptions
.
7.1 Ayudantes Disponibles para Pruebas de Integración
Además de los ayudantes de prueba estándar, heredar de ActionDispatch::IntegrationTest
viene con algunos ayudantes adicionales disponibles al escribir pruebas de integración. Vamos a presentarnos brevemente a las tres categorías de ayudantes que tenemos para elegir.
Para tratar con el ejecutor de pruebas de integración, consulta ActionDispatch::Integration::Runner
.
Al realizar solicitudes, tendremos ActionDispatch::Integration::RequestHelpers
disponibles para nuestro uso.
Si necesitamos cargar archivos, echa un vistazo a ActionDispatch::TestProcess::FixtureFile
para ayudar.
Si necesitamos modificar la sesión o el estado de nuestra prueba de integración, echa un vistazo a ActionDispatch::Integration::Session
para ayudar.
7.2 Implementando una Prueba de Integración
Agreguemos una prueba de integración a nuestra aplicación de blog. Comenzaremos con un flujo de trabajo básico de crear un nuevo artículo de blog, para verificar que todo esté funcionando correctamente.
Comenzaremos generando nuestro esqueleto de prueba de integración:
$ bin/rails generate integration_test blog_flow
Debería haber creado un archivo de prueba de marcador de posición para nosotros. Con la salida del comando anterior, deberíamos ver:
invoke test_unit
create test/integration/blog_flow_test.rb
Ahora abramos ese archivo y escribamos nuestra primera afirmación:
require "test_helper"
class BlogFlowTest < ActionDispatch::IntegrationTest
test "can see the welcome page" do
get "/"
assert_select "h1", "Welcome#index"
end
end
Veremos assert_select
para consultar el HTML resultante de una solicitud en la sección Pruebas de Vistas a continuación. Se utiliza para probar la respuesta de nuestra solicitud afirmando la presencia de elementos HTML clave y su contenido.
Cuando visitamos nuestra ruta raíz, deberíamos ver welcome/index.html.erb
renderizado para la vista. Así que esta afirmación debería pasar.
7.2.1 Creación de Integración de Artículos
¿Qué tal probar nuestra capacidad para crear un nuevo artículo en nuestro blog y ver el artículo resultante?
test "can create an article" do
get "/articles/new"
assert_response :success
post "/articles",
params: { article: { title: "can create", body: "article successfully." } }
assert_response :redirect
follow_redirect!
assert_response :success
assert_select "p", "Title:\n can create"
end
Desglosemos esta prueba para que podamos entenderla.
Comenzamos llamando a la acción :new
en nuestro controlador de Artículos. Esta respuesta debería ser exitosa.
Después de esto, hacemos una solicitud post a la acción :create
de nuestro controlador de Artículos:
post "/articles",
params: { article: { title: "can create", body: "article successfully." } }
assert_response :redirect
follow_redirect!
Las dos líneas que siguen a la solicitud son para manejar la redirección que configuramos al crear un nuevo artículo.
NOTA: No olvides llamar a follow_redirect!
si planeas hacer solicitudes subsecuentes después de que se realice una redirección.
Finalmente, podemos afirmar que nuestra respuesta fue exitosa y que nuestro nuevo artículo es legible en la página.
7.2.2 Llevándolo Más Allá
Pudimos probar con éxito un flujo de trabajo muy pequeño para visitar nuestro blog y crear un nuevo artículo. Si quisiéramos llevar esto más allá, podríamos agregar pruebas para comentar, eliminar artículos o editar comentarios. Las pruebas de integración son un excelente lugar para experimentar con todo tipo de casos de uso para nuestras aplicaciones.
8 Pruebas Funcionales para tus Controladores
En Rails, probar las diversas acciones de un controlador es una forma de escribir pruebas funcionales. Recuerda que tus controladores manejan las solicitudes web entrantes a tu aplicación y finalmente responden con una vista renderizada. Al escribir pruebas funcionales, estás probando cómo tus acciones manejan las solicitudes y el resultado o respuesta esperada, en algunos casos una vista HTML.
8.1 Qué Incluir en tus Pruebas Funcionales
Deberías probar cosas como:
- ¿la solicitud web fue exitosa?
- ¿el usuario fue redirigido a la página correcta?
- ¿el usuario fue autenticado correctamente?
- ¿se mostró el mensaje apropiado al usuario en la vista?
- ¿se mostró la información correcta en la respuesta?
La forma más fácil de ver pruebas funcionales en acción es generar un controlador usando el generador de scaffold:
$ bin/rails generate scaffold_controller article title:string body:text
...
create app/controllers/articles_controller.rb
...
invoke test_unit
create test/controllers/articles_controller_test.rb
...
Esto generará el código del controlador y pruebas para un recurso Article
. Puedes echar un vistazo al archivo articles_controller_test.rb
en el directorio test/controllers
.
Si ya tienes un controlador y solo deseas generar el código de prueba de scaffold para cada una de las siete acciones predeterminadas, puedes usar el siguiente comando:
$ bin/rails generate test_unit:scaffold article
...
invoke test_unit
create test/controllers/articles_controller_test.rb
...
Echemos un vistazo a una de esas pruebas, test_should_get_index
del archivo articles_controller_test.rb
.
# articles_controller_test.rb
class ArticlesControllerTest < ActionDispatch::IntegrationTest
test "should get index" do
get articles_url
assert_response :success
end
end
En la prueba test_should_get_index
, Rails simula una solicitud en la acción llamada index
, asegurándose de que la solicitud fue exitosa y también asegurando que se generó el cuerpo de respuesta correcto.
El método get
inicia la solicitud web y llena los resultados en @response
. Puede aceptar hasta 6 argumentos:
- La URI de la acción del controlador que estás solicitando. Esto puede ser en forma de una cadena o un ayudante de ruta (por ejemplo,
articles_url
). params
: opción con un hash de parámetros de solicitud para pasar a la acción (por ejemplo, parámetros de cadena de consulta o variables de artículo).headers
: para establecer los encabezados que se pasarán con la solicitud.env
: para personalizar el entorno de la solicitud según sea necesario.xhr
: si la solicitud es una solicitud Ajax o no. Se puede establecer en true para marcar la solicitud como Ajax.as
: para codificar la solicitud con un tipo de contenido diferente.
Todos estos argumentos de palabra clave son opcionales.
Ejemplo: Llamando a la acción :show
para el primer Article
, pasando un encabezado HTTP_REFERER
:
get article_url(Article.first), headers: { "HTTP_REFERER" => "http://example.com/home" }
Otro ejemplo: Llamando a la acción :update
para el último Article
, pasando texto nuevo para el title
en params
, como una solicitud Ajax:
patch article_url(Article.last), params: { article: { title: "updated" } }, xhr: true
Un ejemplo más: Llamando a la acción :create
para crear un nuevo artículo, pasando texto para el title
en params
, como solicitud JSON:
post articles_path, params: { article: { title: "Ahoy!" } }, as: :json
NOTA: Si intentas ejecutar la prueba test_should_create_article
de articles_controller_test.rb
, fallará debido a la validación a nivel de modelo recién agregada y con razón.
Modifiquemos la prueba test_should_create_article
en articles_controller_test.rb
para que todas nuestras pruebas pasen:
test "should create article" do
assert_difference("Article.count") do
post articles_url, params: { article: { body: "Rails is awesome!", title: "Hello Rails" } }
end
assert_redirected_to article_path(Article.last)
end
Ahora puedes intentar ejecutar todas las pruebas y deberían pasar.
NOTA: Si seguiste los pasos en la sección Autenticación Básica, necesitarás agregar autorización a cada encabezado de solicitud para que todas las pruebas pasen:
post articles_url, params: { article: { body: "Rails is awesome!", title: "Hello Rails" } }, headers: { Authorization: ActionController::HttpAuthentication::Basic.encode_credentials("dhh", "secret") }
Por defecto, Rails intentará rescatar de las excepciones planteadas durante las pruebas y responder con páginas de error HTML. Este comportamiento se puede controlar mediante la configuración config.action_dispatch.show_exceptions
.
8.2 Tipos de Solicitud Disponibles para Pruebas Funcionales
Si estás familiarizado con el protocolo HTTP, sabrás que get
es un tipo de solicitud. Hay 6 tipos de solicitud admitidos en pruebas funcionales de Rails:
get
post
patch
put
head
delete
Todos los tipos de solicitud tienen métodos equivalentes que puedes usar. En una aplicación típica de C.R.U.D. usarás get
, post
, put
y delete
con más frecuencia.
NOTA: Las pruebas funcionales no verifican si el tipo de solicitud especificado es aceptado por la acción, estamos más preocupados por el resultado. Las pruebas de solicitud existen para este caso de uso para hacer que tus pruebas sean más significativas.
8.3 Probando Solicitudes XHR (Ajax)
Para probar solicitudes Ajax, puedes especificar la opción xhr: true
para los métodos get
, post
, patch
, put
y delete
. Por ejemplo:
test "ajax request" do
article = articles(:one)
get article_url(article), xhr: true
assert_equal "hello world", @response.body
assert_equal "text/javascript", @response.media_type
end
8.4 Los Tres Hashes del Apocalipsis
Después de que se ha realizado y procesado una solicitud, tendrás 3 objetos Hash listos para usar:
cookies
- Cualquier cookie que esté configuradaflash
- Cualquier objeto que viva en el flashsession
- Cualquier objeto que viva en las variables de sesión
Como es el caso con los objetos Hash normales, puedes acceder a los valores haciendo referencia a las claves por cadena. También puedes referenciarlos por nombre de símbolo. Por ejemplo:
flash["gordon"] # o flash[:gordon]
session["shmession"] # o session[:shmession]
cookies["are_good_for_u"] # o cookies[:are_good_for_u]
8.5 Variables de Instancia Disponibles
Después de que se realiza una solicitud, también tienes acceso a tres variables de instancia en tus pruebas funcionales:
@controller
- El controlador que procesa la solicitud@request
- El objeto de solicitud@response
- El objeto de respuesta
class ArticlesControllerTest < ActionDispatch::IntegrationTest
test "should get index" do
get articles_url
assert_equal "index", @controller.action_name
assert_equal "application/x-www-form-urlencoded", @request.media_type
assert_match "Articles", @response.body
end
end
8.6 Estableciendo Encabezados y Variables CGI
Encabezados HTTP y variables CGI se pueden pasar como encabezados:
# establecer un Encabezado HTTP
get articles_url, headers: { "Content-Type": "text/plain" } # simular la solicitud con encabezado personalizado
# establecer una variable CGI
get articles_url, headers: { "HTTP_REFERER": "http://example.com/home" } # simular la solicitud con variable de entorno personalizada
8.7 Probando Avisos de flash
Si recuerdas de antes, uno de los Tres Hashes del Apocalipsis era flash
.
Queremos agregar un mensaje de flash
a nuestra aplicación de blog cada vez que alguien cree con éxito un nuevo Artículo.
Comencemos agregando esta afirmación a nuestra prueba test_should_create_article
:
test "should create article" do
assert_difference("Article.count") do
post articles_url, params: { article: { title: "Some title" } }
end
assert_redirected_to article_path(Article.last)
assert_equal "Article was successfully created.", flash[:notice]
end
Si ejecutamos nuestra prueba ahora, deberíamos ver un fallo:
$ bin/rails test test/controllers/articles_controller_test.rb -n test_should_create_article
Run options: -n test_should_create_article --seed 32266
# Running:
F
Finished in 0.114870s, 8.7055 runs/s, 34.8220 assertions/s.
1) Failure:
ArticlesControllerTest#test_should_create_article [/test/controllers/articles_controller_test.rb:16]:
--- expected
+++ actual
@@ -1 +1 @@
-"Article was successfully created."
+nil
1 runs, 4 assertions, 1 failures, 0 errors, 0 skips
Implementemos ahora el mensaje flash en nuestro controlador. Nuestra acción :create
debería verse así:
def create
@article = Article.new(article_params)
if @article.save
flash[:notice] = "Article was successfully created."
redirect_to @article
else
render "new"
end
end
Ahora, si ejecutamos nuestras pruebas, deberíamos ver que pasa:
$ bin/rails test test/controllers/articles_controller_test.rb -n test_should_create_article
Run options: -n test_should_create_article --seed 18981
# Running:
.
Finished in 0.081972s, 12.1993 runs/s, 48.7972 assertions/s.
1 runs, 4 assertions, 0 failures, 0 errors, 0 skips
8.8 Juntándolo Todo
En este punto, nuestras pruebas del controlador de Artículos prueban la acción :index
así como las acciones :new
y :create
. ¿Qué hay de tratar con datos existentes?
Escribamos una prueba para la acción :show
:
test "should show article" do
article = articles(:one)
get article_url(article)
assert_response :success
end
Recuerda de nuestra discusión anterior sobre los fixtures, el método articles()
nos dará acceso a nuestros datos de fixtures de Artículos.
¿Qué tal eliminar un Artículo existente?
test "should destroy article" do
article = articles(:one)
assert_difference("Article.count", -1) do
delete article_url(article)
end
assert_redirected_to articles_path
end
También podemos agregar una prueba para actualizar un Artículo existente.
test "should update article" do
article = articles(:one)
patch article_url(article), params: { article: { title: "updated" } }
assert_redirected_to article_path(article)
# Recargar la asociación para obtener los datos actualizados y afirmar que el título se actualizó.
article.reload
assert_equal "updated", article.title
end
Observa que estamos empezando a ver cierta duplicación en estas tres pruebas, ambas acceden a los mismos datos de fixtures de Artículo. Podemos D.R.Y. esto usando los métodos setup
y teardown
proporcionados por ActiveSupport::Callbacks
.
Nuestra prueba ahora debería verse algo como lo que sigue. Desprecia las otras pruebas por ahora, las estamos dejando fuera por brevedad.
require "test_helper"
class ArticlesControllerTest < ActionDispatch::IntegrationTest
# llamado antes de cada prueba individual
setup do
@article = articles(:one)
end
# llamado después de cada prueba individual
teardown do
# cuando el controlador está usando caché, puede ser una buena idea restablecerlo después
Rails.cache.clear
end
test "should show article" do
# Reutilizar la variable de instancia @article de setup
get article_url(@article)
assert_response :success
end
test "should destroy article" do
assert_difference("Article.count", -1) do
delete article_url(@article)
end
assert_redirected_to articles_path
end
test "should update article" do
patch article_url(@article), params: { article: { title: "updated" } }
assert_redirected_to article_path(@article)
# Recargar la asociación para obtener los datos actualizados y afirmar que el título se actualizó.
@article.reload
assert_equal "updated", @article.title
end
end
Al igual que con otros callbacks en Rails, los métodos setup
y teardown
también se pueden usar pasando un bloque, lambda o nombre de método como símbolo para llamar.
8.9 Ayudantes de Prueba
Para evitar la duplicación de código, puedes agregar tus propios ayudantes de prueba. El ayudante de inicio de sesión puede ser un buen ejemplo:
# test/test_helper.rb
module SignInHelper
def sign_in_as(user)
post sign_in_url(email: user.email, password: user.password)
end
end
class ActionDispatch::IntegrationTest
include SignInHelper
end
require "test_helper"
class ProfileControllerTest < ActionDispatch::IntegrationTest
test "should show profile" do
# el ayudante ahora es reutilizable desde cualquier caso de prueba de controlador
sign_in_as users(:david)
get profile_url
assert_response :success
end
end
8.9.1 Usando Archivos Separados
Si encuentras que tus ayudantes están desordenando test_helper.rb
, puedes extraerlos en archivos separados. Un buen lugar para almacenarlos es test/lib
o test/test_helpers
.
# test/test_helpers/multiple_assertions.rb
module MultipleAssertions
def assert_multiple_of_forty_two(number)
assert (number % 42 == 0), "expected #{number
Comentarios
Se te anima a ayudar a mejorar la calidad de esta guía.
Por favor contribuye si ves algún error tipográfico o errores fácticos. Para comenzar, puedes leer nuestra sección de contribuciones a la documentación.
También puedes encontrar contenido incompleto o cosas que no están actualizadas. Por favor agrega cualquier documentación faltante para main. Asegúrate de revisar Guías Edge primero para verificar si los problemas ya están resueltos o no en la rama principal. Revisa las Guías de Ruby on Rails para estilo y convenciones.
Si por alguna razón detectas algo que corregir pero no puedes hacerlo tú mismo, por favor abre un issue.
Y por último, pero no menos importante, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.