Afirmando que una excepción particular es lanzada en pepino

Guión

Estoy escribiendo una biblioteca (no Ruby on Rails) para la que me gustaría tener características muy detalladas de Cucumber. Esto incluye especialmente la descripción de errores / excepciones que se deben lanzar en varios casos.

Ejemplo

La forma más intuitiva de escribir los pasos de Pepino probablemente sería algo así como

When I do something unwanted Then an "ArgumentError" should be thrown 

Problema

Hay dos cuestiones que debo abordar:

  1. El primer paso no debe fallar cuando se lanza una excepción.
  2. La excepción a la que los lanzamientos del primer paso deben ser accesibles al segundo paso para poder realizar una aseveración mágica.

Solución poco elegante y engorrosa

El mejor enfoque que he podido encontrar es almacenar en caché la excepción en el primer paso y colocarla en una variable de instancia a la que pueda acceder el segundo paso, de esta manera:

 When /^I do something unwanted$/ do begin throw_an_exception! rescue => @error end end Then /^an "(.*)" should be thrown$/ do |error| @error.class.to_s.should == error end 

Sin embargo, esto hace que el primer paso sea más o menos inútil en los casos en que no quiero que falle, y requiere una variable de instancia, lo que nunca es bueno.

Entonces, ¿alguien puede ayudarme con una solución al menos menos engorrosa? ¿O debería escribir mis características de manera diferente de todos modos? Cualquier ayuda sería muy apreciada.

Lo pensé una vez más, y tal vez la respuesta es:

No hay una solución elegante, porque el esquema de Given-When-Then-Entonces se viola en su caso. Usted espera que “Entonces se debe lanzar una excepción” es el resultado de “Cuando hago algo no deseado”.

¡Pero cuando lo piensas, esto no es cierto! La excepción no es el resultado de esta acción, de hecho, la excepción solo muestra que el estado de “Cuándo” falló.

Mi solución a esto sería probar en un nivel superior:

 When I do something unwanted Then an error should be logged 

o

 When I do something unwanted Then the user should get an error message 

o

 When I do something unwanted Then the program should be locked in state "error" 

o una combinación de estos.

Luego “guardaría en caché la excepción” en su progtwig, lo que tiene mucho sentido, ya que es muy probable que tenga que hacerlo de todos modos.

Los dos problemas que has declarado también se resolverían.

En caso de que realmente deba probar las excepciones.

Bueno, supongo que el pepino no es el banco de pruebas correcto, ¿eh? 😉

Como el esquema de Given-When-Then-entonces se viola de todos modos, simplemente escribiría

 When I do something unwanted it should fail with "ArgumentError" 

y en las definiciones de los pasos algo como (sin probar, corríjame si lo intenta)

 When /^I do something unwanted it should fail with "(.*)"$/ do |errorstring| expect { throw_an_exception! }.to raise_error(errorstring) end 

Como se dijo anteriormente, eso es terriblemente incorrecto ya que el esquema se rompe, pero serviría para ese propósito, ¿no es así? 😉

Encontrará documentación adicional en los errores de prueba a las expectativas de rspec .

Una opción es marcar el escenario con @allow-rescue y verificar la salida y el código de estado de la página. Por ejemplo

En my_steps.rb

 Then(/^the page (?:should have|has) content (.+)$/) do |content| expect(page).to have_content(content) end Then(/^the page should have status code (\d+)$/) do |status_code| expect(page.status_code.to_s).to eq(status_code) end Then /^I should see an error$/ do expect(400..599).to include(page.status_code) end 

En my_feature.feature

 @allow-rescue Scenario: Make sure user can't do XYZ Given some prerequisite When I do something unwanted Then the page should have content Routing Error And the page should have status code 404 

o alternativamente:

 @allow-rescue Scenario: Make sure user can't do XYZ Given some prerequisite When I do something unwanted Then I should see an error 

Es posible que esto no sea exactamente lo que estaba esperando, pero podría ser una solución aceptable para algunas personas que se encuentran en esta página. Creo que dependerá del tipo de excepción, ya que si la excepción no se rescata en ningún nivel, el escenario seguirá fallando. He utilizado este enfoque principalmente para errores de enrutamiento hasta ahora, que ha funcionado bien.

Es posible generar una excepción en un bloque When y luego hacer afirmaciones al respecto en los siguientes bloques Then .

Usando tu ejemplo:

 When /^I do something unwanted$/ do @result = -> { throw_an_exception! } end Then /^an "(.*)" should be thrown$/ do |error| expect{ @result.call }.to raise_error(error) end 

Ese ejemplo usa los comparadores de RSpec, pero la parte importante es el -> (Lambda); lo que permite la referencia a la throw_an_exception! Método para ser transmitido alrededor.

¡Espero que eso ayude!

Estoy respondiendo desde la perspectiva de alguien que usa las características de Cucumber en una situación de desarrollo impulsado por el comportamiento, así que tómalo o déjalo …

Los escenarios deben escribirse para probar una ‘característica’ o funcionalidad de la aplicación, en lugar de usarse para probar el código en sí. Un ejemplo es:

 When the service is invoked Then a success code should be returned 

Parece que su caso de prueba (es decir, si hago esto, esta excepción debería ser lanzada) es un candidato para pruebas de unidad o integración. En mi caso, usaríamos un marco de prueba de burla o prueba unitaria.

Mi sugerencia sería reevaluar los escenarios de las características para ver si realmente están probando lo que pretendes probar. Por experiencia personal, he encontrado que si mis clases de prueba se vuelven anormalmente complejas, entonces mis características son “incorrectas”.