Comment puis-je obtenir SpecFlow pour attendre une exception?

J'utilise SpecFlow, et j'aimerais écrire un scénario tel que le suivant:

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

C'est calculator.Add() qui va lancer une exception, alors comment puis-je gérer cela dans la méthode marquée [Then]?

38
demandé sur Roger Lipscombe 2010-05-21 12:46:01

6 réponses

Grande question. Je ne suis ni un expert BDD ou specflow, cependant, mon premier conseil serait de prendre du recul et d'évaluer votre scénario.

Voulez-vous vraiment utiliser les termes "jeter" et "exception" dans cette spécification? Gardez à l'esprit l'idée avec bdd est d'utiliser un langage omniprésent avec l'entreprise. Idéalement, ils devraient être capables de lire ces scénarios et de les interpréter.

Pensez à changer votre phrase "alors" pour inclure quelque chose comme ceci:

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

Le exception est toujours levée en arrière-plan, mais le résultat final est un simple message d'erreur.

Scott Bellware touche ce concept dans ce podcast code Herding: http://herdingcode.com/?p=176

39
répondu Scott Coates 2011-05-08 14:56:04

Comme un débutant à SpecFlow je ne vais pas vous dire que c'est le moyen de le faire, mais une façon de le faire serait d'utiliser le ScenarioContext pour le stockage de l'exception levée dans le Lorsque;

try
{
    calculator.Add(1,1);
}
catch (Exception e)
{
    ScenarioContext.Current.Add("Exception_CalculatorAdd", e);
}

Dans votre Alors vous pouvez vérifier l'exception levée et faire des affirmations dessus;

var exception = ScenarioContext.Current["Exception_CalculatorAdd"];
Assert.That(exception, Is.Not.Null);

Cela dit; je suis d'accord avec scoarescoare quand il dit que vous devriez formuler le scénario dans des formulations un peu plus "favorables aux affaires". Cependant, en utilisant SpecFlow pour conduire le la mise en œuvre de votre modèle de domaine, la capture d'exceptions et les affirmations peuvent être utiles.

Btw: consultez le screencast de Rob Conery sur TekPub pour de très bons conseils sur L'utilisation de SpecFlow: http://tekpub.com/view/concepts/5

36
répondu Kjetil Klaussen 2017-02-15 17:43:31

BDD peut être pratiqué sur le comportement au niveau de l'entité ou / et sur le comportement au niveau de l'unité.

SpecFlow est un outil BDD qui se concentre sur le comportement au niveau des fonctionnalités. Les Exceptions ne sont pas quelque chose que vous devez spécifier / observer sur le comportement au niveau des entités. Les Exceptions doivent être spécifiées / observées sur le comportement au niveau de l'unité.

Pensez aux scénarios SpecFlow comme une spécification en direct pour les parties prenantes non techniques. Vous n'écririez pas non plus dans la spécification qu'une exception est levée, mais comment le système se comporte dans un tel cas.

Si vous n'avez pas de parties prenantes non techniques, alors SpecFlow est le mauvais outil pour vous! Ne gaspillez pas d'énergie dans la création de spécifications lisibles pour les entreprises s'il n'y a personne intéressé à les lire!

Il existe des outils BDD qui se concentrent sur le comportement au niveau de l'unité. Dans. NET, le plus populaire est MSpec (http://github.com/machine/machine.specifications ). BDD au niveau de l'unité peut également facilement être des pratiques avec des tests unitaires standard Framework.

Cela dit, vous peut toujours vérifier une exception dans SpecFlow.

Voici un peu plus de discussion sur bdd au niveau de l'unité vs bdd au niveau des fonctionnalités: SpecFlow / BDD vs tests unitaires BDD pour les tests D'acceptation vs BDD pour les Tests unitaires (ou: ATDD vs TDD)

Regardez aussi cet article de blog: classification des outils BDD (tests unitaires par rapport aux tests D'acceptation) et un peu d'historique BDD

13
répondu jbandi 2010-05-31 13:25:54

Changer le scénario pour ne pas avoir d'exception est probablement un bon moyen d'avoir le scénario plus orienté utilisateur. Cependant, si vous avez encore besoin de le faire fonctionner, veuillez considérer ce qui suit:

  1. Attraper une exception (je recommande vraiment d'attraper des exceptions spécifiques sauf si vous avez vraiment besoin de tout attraper) dans l'étape qui appelle une opération et la transmet au contexte du scénario.

    [When("I press add")]
    public void WhenIPressAdd()
    {
       try
       {
         _calc.Add();
       }
       catch (Exception err)
       {
          ScenarioContext.Current[("Error")] = err;
       }
    }
    
  2. Validez que l'exception est stockée dans le scénario contexte

    [Then(@"it should throw an exception")]
    public void ThenItShouldThrowAnException()
    {
          Assert.IsTrue(ScenarioContext.Current.ContainsKey("Error"));
    }
    

P.S. C'est très proche de l'une des réponses existantes. Cependant, si vous essayez d'obtenir de la valeur à partir de ScenarioContext en utilisant la syntaxe comme ci-dessous:

var err = ScenarioContext.Current["Error"]

Il lancera une autre exception au cas où la clé "erreur" n'existerait pas (et cela échouera tous les scénarios qui effectuent des calculs avec des paramètres corrects). Donc ScenarioContext.Current.ContainsKey peut être juste plus approprié

7
répondu Dmitriy Chernyavsky 2011-12-17 15:42:43

Dans le cas où vous testez les interactions utilisateur, Je ne conseillerai que ce qui a déjà été dit sur la focalisation sur l'expérience utilisateur: "alors l'utilisateur est présenté avec un message d'erreur". Mais, dans le cas où vous testez un niveau inférieur à l'interface utilisateur, je voudrais partager mon expérience:

J'utilise SpecFlow pour développer une couche métier. Dans mon cas, je ne me soucie pas des interactions de L'interface utilisateur, mais je trouve toujours extrêmement utile L'approche BDD et SpecFlow.

Dans la couche métier, Je ne veux pas de spécifications cela dit "alors l'utilisateur est présenté avec un message d'erreur", mais en vérifiant que le service répond correctement à une mauvaise entrée. J'ai fait pendant un moment ce qui a déjà été dit d'attraper l'exception au "Quand" et de la Vérifier au "alors", mais je trouve cette option pas optimale, parce que si vous réutilisez L'étape" quand " vous pourriez avaler une exception où vous ne l'attendiez pas.

Actuellement, j'utilise des clauses "Then" explicites, parfois sans le "When", ceci façon:

Scenario: Adding with an empty stack causes an error
     Given I have entered nothing into the calculator
     Then adding causes an error X

Cela me permet de coder spécifiquement l'action et la détection d'exception en une seule étape. Je peux le réutiliser pour tester autant de cas d'erreur que je veux et cela ne me fait pas ajouter du code non lié aux étapes "When" qui ne échouent pas.

4
répondu Francesc Castells 2011-06-14 00:20:52

Ma solution implique quelques éléments à implémenter, mais à la toute fin, elle aura l'air beaucoup plus élégante:

@CatchException
Scenario: Faulty operation throws exception
    Given Some Context
    When Some faulty operation invoked
    Then Exception thrown with type 'ValidationException' and message 'Validation failed'

Pour que cela fonctionne, suivez ces 3 étapes:

Étape 1

Marquez les scénarios dans lesquels vous attendez des exceptions avec une balise, par exemple @CatchException:

@CatchException
Scenario: ...

Étape 2

Définissez un gestionnaire AfterStep pour changer ScenarioContext.TestStatus en OK. Vous pouvez seulement ignorer les erreurs dans for Quand étapes, de sorte que vous pouvez toujours échouer un test dans , Puis vérification d'une exception. J'ai dû le faire par réflexion car la propriété TestStatus est interne:

[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);
    }
}

Étape 3

Validez TestError de la même manière que vous valideriez tout ce qui se trouve dans 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);
}
4
répondu Vitaliy Ulantikov 2017-09-05 20:07:57