web-dev-qa-db-ja.com

SpecFlowに例外を予期させるにはどうすればよいですか?

SpecFlowを使用しており、次のようなシナリオを作成したいと思います。

_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
_

例外をスローするのはcalculator.Add()なので、_[Then]_とマークされたメソッドでこれを処理するにはどうすればよいですか?

40
Roger Lipscombe

素晴らしい質問です。私はbddでもspecflowの専門家でもありませんが、最初のアドバイスは一歩下がってシナリオを評価することです。

この仕様で「スロー」と「例外」という用語を本当に使用しますか? bddのアイデアは、ビジネスでユビキタス言語を使用することであることに注意してください。理想的には、彼らはこれらのシナリオを読んで解釈できるはずです。

「then」フレーズを変更して、次のようなものを含めることを検討してください。

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

例外は引き続きバックグラウンドでスローされますが、最終結果は単純なエラーメッセージです。

スコットベルウェアは、このHerding Codeポッドキャストでこの概念に触れています: http://herdingcode.com/?p=176

40
Scott Coates

SpecFlowの初心者として、これがの方法であるとは言いませんが、1つの方法はScenarioContextを使用することです。スローされた例外をWhen;に格納します。

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

Thenで、スローされた例外をチェックし、それに対してアサートを実行できます。

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

とは言うものの;私はscoarescoareに同意します。彼は、もう少し「ビジネスに適した」表現でシナリオを作成する必要があると言っています。ただし、SpecFlowを使用してドメインモデルの実装を推進し、例外をキャッチしてそれらに対してアサートを実行すると便利な場合があります。

ところで:SpecFlowの使用に関するいくつかの本当に良いヒントについては、TekPubでRob Coneryのスクリーンキャストをチェックしてください: http://tekpub.com/view/concepts/5

37
Kjetil Klaussen

BDDは、機能レベルの動作または/およびユニットレベルの動作で実行できます。

SpecFlowは、機能レベルの動作に焦点を当てたBDDツールです。例外は、機能レベルの動作で指定/監視する必要があるものではありません。ユニットレベルの動作では、例外を指定/監視する必要があります。

SpecFlowシナリオは、技術的でない利害関係者向けの実際の仕様と考えてください。また、例外がスローされることを仕様に書き込むのではなく、そのような場合のシステムの動作を記述します。

技術以外の利害関係者がいない場合、SpecFlowは間違ったツールです。読むことに興味のある人がいない場合は、ビジネスで読み取り可能な仕様を作成するのにエネルギーを無駄にしないでください。

ユニットレベルの動作に焦点を当てたBDDツールがあります。 .NETで最も人気のあるものはMSpecです( http://github.com/machine/machine.specifications )。ユニットレベルのBDDは、標準のユニットテストフレームワークを使用して簡単に実践することもできます。

そうは言っても、あなたは SpecFlowで例外をチェックすることができます

ユニットレベルのbddと機能レベルのbddの詳細については、次のとおりです。 SpecFlow/BDD vsユニットテスト受け入れテストのBDDとユニットテストのBDD(または:ATDD) vs. TDD)

このブログ投稿もご覧ください: BDDツールの分類(ユニットテスト駆動と受け入れテスト駆動)およびBDD履歴のビット

14
jbandi

シナリオを例外がないように変更することは、シナリオをよりユーザー指向にするためのおそらく良い方法です。ただし、それでも機能させる必要がある場合は、次の点を考慮してください。

  1. 操作を呼び出してシナリオコンテキストに渡すステップで、例外をキャッチします(本当にすべてをキャッチする必要がない限り、特定の例外をキャッチすることをお勧めします)。

    [When("I press add")]
    public void WhenIPressAdd()
    {
       try
       {
         _calc.Add();
       }
       catch (Exception err)
       {
          ScenarioContext.Current[("Error")] = err;
       }
    }
    
  2. 例外がシナリオコンテキストに格納されていることを検証します

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

P.S.これは、既存の回答の1つに非常に近いものです。ただし、以下のような構文を使用してScenarioContextから値を取得しようとすると、次のようになります。

var err = ScenarioContext.Current["Error"]

「エラー」キーが存在しない場合、別の例外がスローされます(これにより、正しいパラメーターで計算を実行するすべてのシナリオが失敗します)。そう ScenarioContext.Current.ContainsKeyより適切かもしれません

7
Jake Starr

私のソリューションには、実装する項目がいくつか含まれていますが、最終的にははるかにエレガントに見えます。

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

これを機能させるには、次の3つの手順に従います。

ステップ1

例外が予想されるシナリオをいくつかのタグでマークします。 @CatchException

@CatchException
Scenario: ...

ステップ2

AfterStepハンドラーを定義してScenarioContext.TestStatusOKに変更します。 Whenステップのエラーのみを無視したい場合があるため、Thenで例外を検証するテストに失敗する可能性があります。 TestStatusプロパティは内部であるため、リフレクションを介してこれを行う必要がありました。

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

ステップ

TestError内のすべてを検証するのと同じ方法で、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);
}
5

ユーザーインタラクションをテストしている場合は、ユーザーエクスペリエンスに焦点を当てることについて、すでに言われていることだけをアドバイスします。「ユーザーにはエラーメッセージが表示されます」。ただし、UIより下のレベルをテストしている場合は、私の経験を共有したいと思います。

SpecFlowを使用してビジネスレイヤーを開発しています。私の場合、UIの相互作用は気にしませんが、それでもBDDアプローチとSpecFlowは非常に便利です。

ビジネス層では、「ユーザーにエラーメッセージが表示される」という仕様は必要ありませんが、実際には、サービスが間違った入力に正しく応答することを確認します。 「When」で例外をキャッチして「Then」で検証することについてはすでに述べたことがありますが、「When」ステップを再利用すると飲み込む可能性があるため、このオプションは最適ではありません。あなたがそれを予期していなかった例外。

現在、明示的な「Then」句を使用していますが、「When」がない場合もあります。

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

これにより、アクションと例外検出を1つのステップで具体的にコーディングできます。これを再利用して、必要な数のエラーケースをテストできますが、失敗しない「When」ステップに無関係のコードを追加することはありません。

5