2016年7月31日日曜日

SpecFlowのHook(イベント前後に自動実施されるコード)の簡単説明

SpecFlowにて、共通的な処理を記載したり、細かい情報のロギング等に便利そうな機能、Hookの一通りの説明。

Hookとは

SpecFlowによる試験の実施時の特定のタイミング(フィーチャのテスト実施開始前後、シナリオの試験実施開始前後とか)に、自動的に実行されるメソッド。メソッドに、以下の属性を付与することで、実現する。

Hook属性一覧

属性タグ
いつ実施されるのか
[BeforeTestRun]
[AfterTestRun]
テスト全体の実施前後。
複数のフィーチャがある場合には、全てのフィーチャの実施より手前と、全てのフィーチャの試験が終わった後に実施される。
この属性タグをつけるメソッドは、静的メソッドである必要性がある。
※注意※
[AfterTestRun]は、テスト実施フレームワーク(MSTest, NUnitとか)のdllがアンロードされたタイミングで実施される。いつ実施されるのかは、各フレームワークによって異なる。
[BeforeFeature]
[AfterFeature]
各フィーチャのテスト実施前後。
フィーチャにシナリオが複数ある場合には、シナリオの試験実施が始まる前と、そのフィーチャのすべてのシナリオの実施が完了した後に実施される。
この属性タグをつけるメソッドは、静的メソッドである必要性がある。
[BeforeScenario] or [Before]
[AfterScenario] or [After]
フィーチャ内の各シナリオのテスト実施前後
各シナリオの実施前後に一度ずつ呼び出される。
[BeforeScenarioBlock]
[AfterScenarioBlock]
各シナリオ内の「前提」「もし」「ならば」のブロックの前後
(「前提」「もし」「ならば」に複数行の記載があったとしても、一度しか実施されない)
[BeforeStep]
[AfterStep]
各シナリオ内の「前提」「もし」「ならば」の各行の前後
(「前提」「もし」「ならば」に複数行の記載がある場合、その行数だけ実施される)

実装時の操作、挙動の説明

Hookクラスの簡単な挙動の説明と、クラス/メソッドの作成法等を説明する。

Hook クラスの新規作成

「StepFlow」の初期準備が終わったプロジェクトを右クリックし、「Add > Class...」を選択する。
テンプレートで「SpecFlow Hooks(event bindings)」を選択する。
任意の名前を入力し、「Add」ボタンをクリックする。

上記作業で、以下のような、SpecFlowのHook向けの空クラスが追加される。
[Binding]
public sealed class Hooks1
{
    // For additional details on SpecFlow hooks see http://go.specflow.org/doc-hooks

    [BeforeScenario]
    public void BeforeScenario()
    {
       //TODO: implement logic that has to run before executing each scenario
    }

    [AfterScenario]
    public void AfterScenario()
    {
       //TODO: implement logic that has to run after executing each scenario
    }
}


(もし、手動でクラスを作る場合には、[Binding]属性の付与し忘れに注意。[Binding]
属性無しだと、SpecFlow実行時に、Hookとして認識されない)

とりあえず、全種類のHookの動作を試せるだけのコード例:
[Binding]
public sealed class SampleHooks
{
    private int counter { get; set; }

    [BeforeTestRun]
    public static void doBeforeTestRun()
    {
       System.Console.WriteLine("Before Test Run:");

       // 動作を確認したい場合には、以下の例外を出力するコードのコメントアウトを解除のこと
       // throw new Exception("After Test Run");
    }

    [AfterTestRun]
    public static void doAfterTestRun()
    {
       System.Console.WriteLine("After Test Run:");

       // 動作を確認したい場合には、以下の例外を出力するコードのコメントアウトを解除のこと
       // throw new Exception("After Test Run");
    }

    [BeforeFeature]
    public static void doBeforeFeature()
    {
       System.Console.WriteLine("Before Feature:");
    }

    [AfterFeature]
    public static void doAfterFeature()
    {
       System.Console.WriteLine("After Feature:");
    }

    [BeforeScenario]
    public void doBeforeScenario()
    {
       System.Console.WriteLine("Before Scenario:" + ++this.counter);
    }

    [AfterScenario]
    public void doAfterScenario()
    {
       System.Console.WriteLine("After Scenario:" + ++this.counter);
    }

    [BeforeScenarioBlock]
    public void doBeforeSB()
    {
       System.Console.WriteLine("Before Scenario Block:" + ++this.counter);
    }

    [AfterScenarioBlock]
    public void doAfterSB()
    {
       System.Console.WriteLine("After Scenario Block:" + ++this.counter);
    }
    [BeforeStep]
    public void doBeforeStep()
    {
       System.Console.WriteLine("Before Step:" + ++this.counter);
    }

    [AfterStep]
    public void doAfterStep()
    {
       System.Console.WriteLine("After Step:" + ++this.counter);
    }
}


試験実行時のHookクラスのインスタンス生成について


Hookの属性([before]とか[after]とか)が付いたクラスのインスタンスは、各シナリオの試験実行時に、一回ずつ新規生成される。各シナリオで、Hookの属性がついているクラスは、毎度全てインスタンス化され、全てのHook関係の属性がついたメソッドが実行される。

試験実行時のHookの実行順序の指定

同じHook属性のメソッドが複数個定義されていた場合、順序を指定しない限りは、どちらが先に実行されるかは、定義されていない。
もし、順序を固定したい場合には、”Order”を指定する。

以下のように指定:
[BeforeScenario(Order = 100)]
public void BeforeScenarioSecond()
{
    System.Console.WriteLine("Second");
}

[BeforeScenario(Order = 50)]
public void BeforeScenarioFirst()
{
    System.Console.WriteLine("First");
}

同じHook属性内では、Order の数値が小さい順に実行される。上記の例では、「BeforeScenarioFirst」メソッドが、「BeforeScenarioSecond」メソッドよりも必ず手前に実行される。

その他、特記事項

Hook属性を付けたメソッドは、基本的にどのシナリオ/フィーチャからも呼び出されてしまうので、このままの状態だと、特定のフィーチャ、シナリオ向けの処理は記載しにくい。

「特定のフィーチャ、シナリオだけで実行されるHook」、「Hook属性を付けたメソッド内で、どのフィーチャ、どのシナリオで実行されているのか知る」等は、スコープバインディング、Context、という、別のSpecFlowの機能と組み合わせて実現する。

スコープバインディングは、ちょっと調べたみたので、こっちの投稿に簡単説明を記載。
Contextも、ちょっと調べてみたので、こっちの投稿に簡単説明を記載。

以上。