2010-05-21 10 views
36

Ich verwende SpecFlow, und ich möchte ein Szenario, wie die folgenden schreiben:Wie bekomme ich SpecFlow, um eine Ausnahme zu erwarten?

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 ist calculator.Add(), die eine Ausnahme werfen wird, also wie kann ich damit umgehen in dem Verfahren markiert [Then]?

+0

Haben Sie eine dieser Antworten hilfreich gefunden? –

+0

@scoarescoare: Ja. Das Problem ist, dass die richtige Antwort, die alle erforderlichen Informationen enthält, eine Kombination aus Ihrer und Kjetils ist. Deine Antwort sagt, dass meine Sprache falsch ist, und Kjetil sagt eigentlich, wie man die Ausnahme (oder eine andere Ausgabe) von "Wann" zu "Dann" bekommt. –

+0

Danke für die Nachfrage. Ich habe festgestellt, dass ich mich dasselbe gefragt habe! –

Antwort

38

Große Frage. Ich bin weder ein BDD- noch ein Specflow-Experte, aber mein erster Rat wäre, einen Schritt zurückzutreten und Ihr Szenario zu bewerten.

Wollen Sie wirklich die Begriffe "werfen" und "Ausnahme" in dieser Spezifikation verwenden? Denken Sie daran, die Idee mit BDD ist, eine allgegenwärtige Sprache mit dem Geschäft zu verwenden. Idealerweise sollten sie in der Lage sein, diese Szenarien zu lesen und zu interpretieren.

Betrachten Sie Ihren „und dann“ Ausdruck zu ändern, so etwas umfassen:

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 

Die Ausnahme noch im Hintergrund geworfen wird, aber das Endergebnis ist eine einfache Fehlermeldung.

Scott Bellware berührt dieses Konzept in diesem Podcast Herding Code: http://herdingcode.com/?p=176

+13

Ich würde hinzufügen, dass BDD-Tools wie specflow bedeuten, in Verbindung mit TDD verwendet werden. Also schreiben Sie Ihre Spezifikation wie folgt und dann würden Sie einen Komponententest schreiben, der eine Ausnahme erwartet. – vintem

+0

Große Antwort! Ich bin auch kein Experte, aber es scheint die passende Antwort zu sein. –

33

Als Neuling auf SpecFlow Ich werde Ihnen nicht sagen, dass dies ist der Weg, es zu tun, aber eine Möglichkeit, es zu tun wäre, die ScenarioContext zu verwenden, um die in der geworfene Ausnahme zu speichern, wenn;

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

In Ihrem Dann Sie die ausgelöste Ausnahme überprüfen könnten und tun behauptet darauf;

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

Mit dem gesagt; Ich stimme zu scoarescoare, wenn er sagt, dass Sie das Szenario in ein bisschen mehr "Business-freundliche" Formulierungen formulieren sollten. Die Verwendung von SpecFlow, um die Implementierung Ihres Domänenmodells zu steuern, das Abfangen von Ausnahmen und das Ausführen von Asserts kann jedoch hilfreich sein.

Btw: Schauen Sie sich Rob Conery des Screencasts über bei TekPub für einige wirklich gute Tipps auf SpecFlow mit: http://tekpub.com/view/concepts/5

+2

In specflow 1.7.1.0 können Sie auf ScenarioContext.Current.TestError für Ausnahmen verweisen, die während eines Szenarios abgefangen wurden. –

+0

In meinem Kontext habe ich zwei Arten von 'When': Die normale, die Ausnahmen wie' Wenn ich Add drücken 'und eine, die Ausnahmen behandeln können:' Wenn ich versuche, Add drücken, das ruft die gleiche 'WhenIPressAdd()' Methode, aber umgeben von einem 'try' /' catch' Block und Handhabung, wie Sie es vorschlagen. Jetzt kann das System mir Fehler zufügen, und ich kann sie bei Bedarf fangen und handhaben. – AutomatedChaos

+0

Ich mache Komponententest auf Business-Rules-Ebene, die Ausnahmen werden geworfen und nie gefangen. - Es wird auf einer Ebene darüber gefangen. Ihre Lösung ist für mich sehr nützlich. Vielen Dank! – user3381672

12

BDD auf Merkmalsebene Verhalten oder/und auf Einheitenebene Verhalten geübt werden.

SpecFlow ist ein BDD-Tool, das sich auf das Feature-Level-Verhalten konzentriert. Ausnahmen sollten nicht auf Feature-Level-Verhalten festgelegt werden. Ausnahmen sollten beim Verhalten auf Geräteebene angegeben/eingehalten werden.

Denken Sie an SpecFlow-Szenarien als eine Live-Spezifikation für den nicht technischen Stakeholder. Sie würden auch nicht in die Spezifikation schreiben, dass eine Ausnahme ausgelöst wird, sondern wie sich das System in einem solchen Fall verhält.

Wenn Sie keine nicht technischen Interessengruppen haben, dann ist SpecFlow das falsche Werkzeug für Sie! Verschwenden Sie keine Energie bei der Erstellung von Business-lesbaren Spezifikationen, wenn niemand daran interessiert ist, sie zu lesen!

Es gibt BDD-Tools, die sich auf das Verhalten auf Einheitenebene konzentrieren. In .NET ist MSpec (http://github.com/machine/machine.specifications) das beliebteste. BDD auf Einheitenebene kann auch problemlos mit Standard-Unit-Test-Frameworks durchgeführt werden.

Das sagte, Sie could still check for an exception in SpecFlow.

Hier sind einige weitere Diskussion von BDD auf der Ebene der Einheit gegen BDD auf Feature-Ebene: SpecFlow/BDD vs Unit Testing BDD for Acceptance Tests vs. BDD for Unit Tests (or: ATDD vs. TDD)

Auch Blick auf diesen Blog-Eintrag haben: Classifying BDD Tools (Unit-Test-Driven vs. Acceptance Test Driven) and a bit of BDD history

+0

Alles Gute, danke. –

+0

Ich verstehe die Unterscheidung zwischen ATDD und TDD wie im Blogbeitrag erwähnt, aber das führt mich zu einer Frage. Wie beschrieben, verwendet nicht ein BDD-Tool (wie MSpec) nur ein anderes Unit-Test-Framework? Es scheint mir so zu sein. Außerdem, wenn ich das gleiche Werkzeug für ATDD und TDD verwenden kann, warum sollte ich nicht? Es scheint hier immer noch einige unscharfe Linien zu geben. –

+0

Hi Brian - die meisten der BDD-Tools wurden entwickelt, um ein gemeinsames Verständnis mit einem nicht technischen Interessenvertreter zu erreichen. Es gibt nicht so viele BDD-Tools für eine Einheitsebene/technische Stakeholder, nur weil technische Mitarbeiter normalerweise Unit-Level-BDD mit TDD-Frameworks gut machen können. Ich benutze NUnit für beide im Moment, mit einem englischen Stil DSL darunter für Szenarien. Sie können das tun, wenn es für Sie funktioniert. Das einzige, was ich anders mache, ist, die Szenarios so hoch wie möglich zu halten, damit ich sie wiederverwenden kann - die Wiederverwendung ist viel größer als auf einer Einheitsebene. – Lunivore

6

das Szenario ändern nicht zu Eine Ausnahme ist wahrscheinlich eine gute Möglichkeit, das Szenario benutzerorientierter zu gestalten. wenn Sie noch Allerdings müssen sie arbeiten müssen, beachten Sie bitte die folgenden Schritte aus:

  1. Fang eine Ausnahme (ich empfehle fangen spezifische Ausnahmen wirklich, wenn Sie wirklich brauchen alle zu fangen) in dem Schritt, der eine Operation und Pass ruft es zum Szenario Kontext.

    [When("I press add")] 
    public void WhenIPressAdd() 
    { 
        try 
        { 
        _calc.Add(); 
        } 
        catch (Exception err) 
        { 
         ScenarioContext.Current[("Error")] = err; 
        } 
    } 
    
  2. diese Ausnahme Validieren wird in dem Szenario Kontext

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

P. S. gespeichert Es ist sehr nah an einer der vorhandenen Antworten. Wenn Sie jedoch versuchen Wert von ScenarioContext bekommen Syntax wie folgt:

var err = ScenarioContext.Current["Error"] 

wird es eine weitere Ausnahme für den Fall aus, wenn „Fehler“ Schlüssel nicht existiert (und das wird alle Szenarien fehlschlagen, die Berechnungen mit korrekten Parametern durchführen). So ScenarioContext.Current.ContainsKey kann gerade passender sein

3

Im Fall, dass Sie Benutzerinteraktionen testen, werde ich nur Ratschläge geben, was bereits über die Fokussierung auf die Benutzererfahrung gesagt wurde: "Dann wird dem Benutzer eine Fehlermeldung angezeigt". Aber wenn Sie eine Ebene unter der Benutzeroberfläche testen, möchte ich meine Erfahrung teilen:

Ich verwende SpecFlow, um eine Business-Schicht zu entwickeln. In meinem Fall interessieren mich die UI-Interaktionen nicht, aber ich finde immer noch den BDD-Ansatz und SpecFlow äußerst nützlich.

In der Business-Schicht möchte ich keine Spezifikationen, die sagen "Dann wird der Benutzer mit einer Fehlermeldung angezeigt", aber tatsächlich zu überprüfen, dass der Dienst richtig auf eine falsche Eingabe reagiert. Ich habe schon eine Weile getan, was bereits gesagt wurde, die Ausnahme beim "Wann" zu fangen und beim "Dann" zu überprüfen, aber ich finde diese Option nicht optimal, denn wenn Sie den "Wenn" - Schritt wiederverwenden, könnten Sie schlucken eine Ausnahme, wo du es nicht erwartet hast.

Derzeit verwende ich ausdrücklich „Dann“ Klauseln, einige Male ohne „Wenn“, so:

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

Dies ermöglicht es mir speziell die Aktion und die Ausnahmeerkennung in einem Schritt zu codieren. Ich kann es wiederverwenden, um so viele Fehlerfälle zu testen, wie ich möchte, und es bringt mich nicht dazu, nicht verwandten Code zu den nicht fehlgeschlagenen "Wenn" Schritten hinzuzufügen.

+1

Ich bin neu in BDD, aber ich mag das Muster von "tue etwas in einem When und wirf es in den Kontext und lese es dann im Then" wirklich ab. Ich denke, dies wird immer schwieriger zu halten sein, wenn die Anzahl der Spezifikationen wächst und mehr von ihnen wiederverwendet wird. Ich habe begonnen, das zu tun, was du oben beschrieben hast, und bisher mag ich es. –

1

Meine Lösung beinhaltet ein paar Elemente zu implementieren, aber ganz am Ende wird es aussehen viel eleganter:

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

Um diese Arbeit zu machen, folgen Sie diesen 3 Schritten:

Schritt 1

Markieren Sie Szenarien, für die Sie Ausnahmen mit einem Tag erwarten, z @CatchException:

@CatchException 
Scenario: ... 

Schritt 2

Definieren Sie eine AfterStep Handler ScenarioContext.TestStatus zu ändern OK zu sein. Sie können nur Fehler in für ignorieren, wenn Schritte, so können Sie immer noch einen Test in Dann eine Ausnahme zu überprüfen. Wäre dies durch Reflexion zu tun, wie TestStatus Eigenschaft ist intern:

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

Schritt 3

Validate TestError die gleiche Art und Weise Sie alles innerhalb ScenarioContext bestätigen würde.

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