bdd - tutorial - specflow documentation
¿Cómo consigo que SpecFlow espere una excepción? (6)
Estoy usando SpecFlow, y me gustaría escribir un escenario como el siguiente:
Scenario: Pressing add with an empty stack throws an exception
Given I have entered nothing into the calculator
When I press add
Then it should throw an exception
Es calculator.Add()
que lanzará una excepción, entonces, ¿cómo manejo esto en el método marcado [Then]
?
BDD se puede practicar en el comportamiento a nivel de característica y / o en el comportamiento a nivel de unidad.
SpecFlow es una herramienta BDD que se enfoca en el comportamiento a nivel de característica. Las excepciones no son algo que deba especificar u observar en el comportamiento de nivel de característica. Las excepciones deben especificarse / observarse en el comportamiento a nivel de unidad.
Piense en los escenarios de SpecFlow como una especificación en vivo para el interesado no técnico. Tampoco escribiría en la especificación que se lanza una excepción, sino cómo se comporta el sistema en tal caso.
¡Si no tiene ningún interesado no técnico, entonces SpecFlow es la herramienta incorrecta para usted! ¡No desperdicie energía creando especificaciones legibles para el negocio si no hay nadie interesado en leerlas!
Hay herramientas BDD que se enfocan en el comportamiento a nivel de unidad. En .NET, el más popular es MSpec ( http://github.com/machine/machine.specifications ). BDD a nivel de unidad también puede ser fácilmente prácticas con marcos de pruebas unitarias estándar.
Dicho esto, aún podrías verificar una excepción en SpecFlow .
Aquí hay más discusión sobre bdd en el nivel de unidad vs. bdd en el nivel de característica: SpecFlow / BDD frente a Unit Testing BDD para pruebas de aceptación frente a BDD para pruebas unitarias (o: ATDD vs. TDD)
También vea esta publicación en el blog: Clasificar las herramientas BDD (Drive-Test-Driven vs. Acceptance Test Driven) y un poco de BDD history
Cambiar el escenario para que no tenga una excepción es una buena manera de que el escenario esté más orientado al usuario. Sin embargo, si aún necesita que funcione, tenga en cuenta lo siguiente:
Capture una excepción (realmente recomiendo capturar excepciones específicas a menos que realmente necesite capturar todas) en el paso que invoca una operación y la pasa al contexto del escenario.
[When("I press add")] public void WhenIPressAdd() { try { _calc.Add(); } catch (Exception err) { ScenarioContext.Current[("Error")] = err; } }
Valide que la excepción se almacena en el contexto del escenario
[Then(@"it should throw an exception")] public void ThenItShouldThrowAnException() { Assert.IsTrue(ScenarioContext.Current.ContainsKey("Error")); }
PD: está muy cerca de una de las respuestas existentes. Sin embargo, si intenta obtener el valor de ScenarioContext usando la sintaxis como a continuación:
var err = ScenarioContext.Current["Error"]
lanzará otra excepción en caso de que la clave "Error" no exista (y que fallará todos los escenarios que realizan cálculos con los parámetros correctos). Así que ScenarioContext.Current.ContainsKey
puede ser más apropiado
Como novato de SpecFlow, no te diré que esta es la manera de hacerlo, pero una forma de hacerlo sería usar ScenarioContext
para almacenar la excepción lanzada en When ;
try
{
calculator.Add(1,1);
}
catch (Exception e)
{
ScenarioContext.Current.Add("Exception_CalculatorAdd", e);
}
En su Entonces usted podría verificar la excepción lanzada y hacer afirmaciones sobre ella;
var exception = ScenarioContext.Current["Exception_CalculatorAdd"];
Assert.That(exception, Is.Not.Null);
Con eso dicho; Estoy de acuerdo con Scoarescoare cuando dice que debe formular el escenario en términos un poco más "favorables a los negocios". Sin embargo, usar SpecFlow para impulsar la implementación de su modelo de dominio, capturar excepciones y hacer afirmaciones sobre ellas puede ser útil.
Por cierto: echa un vistazo al screencast de Rob Conery en TekPub para obtener algunos consejos realmente útiles sobre el uso de SpecFlow: http://tekpub.com/view/concepts/5
En caso de que esté probando las interacciones del usuario, solo aconsejaré lo que ya se ha dicho sobre centrarse en la experiencia del usuario: "Luego se le presenta al usuario un mensaje de error". Pero, en caso de que esté probando un nivel por debajo de la IU, me gustaría compartir mi experiencia:
Estoy usando SpecFlow para desarrollar una capa empresarial. En mi caso, no me importan las interacciones de UI, pero todavía encuentro extremadamente útil el enfoque BDD y SpecFlow.
En la capa de negocios, no quiero que las especificaciones digan "Entonces se le presenta al usuario un mensaje de error", pero en realidad verifica que el servicio responde correctamente a una entrada incorrecta. He hecho durante un tiempo lo que ya se ha dicho de atrapar la excepción en el "Cuándo" y verificarla en el "Entonces", pero esta opción no me parece óptima, porque si reutilizas el paso "Cuando" podrías tragar una excepción donde no lo esperabas.
Actualmente, estoy usando cláusulas explícitas "Then", algunas veces sin el "Cuándo", de esta manera:
Scenario: Adding with an empty stack causes an error
Given I have entered nothing into the calculator
Then adding causes an error X
Esto me permite codificar específicamente la acción y la detección de excepciones en un solo paso. Puedo reutilizarlo para probar tantos casos de error como quiera y no hacer que agregue código no relacionado a los pasos de "Cuándo" no fallan.
Gran pregunta No soy un experto en bdd o en flujo de especificaciones, sin embargo, mi primer consejo sería dar un paso atrás y evaluar su situación.
¿De verdad quieres usar los términos "arrojar" y "excepción" en esta especificación? Tenga en cuenta que la idea de bdd es utilizar un lenguaje ubicuo en el negocio. Idealmente, deberían poder leer estos escenarios e interpretarlos.
Considere cambiar su frase "entonces" para incluir algo como esto:
Scenario: Pressing add with an empty stack displays an error
Given I have entered nothing into the calculator
When I press add
Then the user is presented with an error message
La excepción se sigue lanzando en segundo plano, pero el resultado final es un mensaje de error simple.
Scott Bellware toca este concepto en este podcast del código de Herding: http://herdingcode.com/?p=176
Mi solución implica un par de elementos para implementar, pero al final se verá mucho más elegante:
@CatchException
Scenario: Faulty operation throws exception
Given Some Context
When Some faulty operation invoked
Then Exception thrown with type ''ValidationException'' and message ''Validation failed''
Para que esto funcione, sigue esos 3 pasos:
Paso 1
Marque Escenarios que espera excepciones con alguna etiqueta, p. Ej. @CatchException
:
@CatchException
Scenario: ...
Paso 2
Defina un controlador AfterStep
para cambiar ScenarioContext.TestStatus
para que esté OK
. Es posible que solo desee ignorar errores en los pasos Cuando , por lo que aún puede fallar una prueba en Luego verificando una excepción. Tuve que hacer esto a través de la reflexión ya que la propiedad TestStatus
es interna:
[AfterStep("CatchException")]
public void CatchException()
{
if (ScenarioContext.Current.StepContext.StepInfo.StepDefinitionType == StepDefinitionType.When)
{
PropertyInfo testStatusProperty = typeof(ScenarioContext).GetProperty("TestStatus", BindingFlags.NonPublic | BindingFlags.Instance);
testStatusProperty.SetValue(ScenarioContext.Current, TestStatus.OK);
}
}
Paso 3
Valide TestError
la misma forma que validaría cualquier cosa dentro de ScenarioContext
.
[Then(@"Exception thrown with type ''(.*)'' and message ''(.*)''")]
public void ThenExceptionThrown(string type, string message)
{
Assert.AreEqual(type, ScenarioContext.Current.TestError.GetType().Name);
Assert.AreEqual(message, ScenarioContext.Current.TestError.Message);
}