2016年7月24日日曜日

SpecFlowに触れてみた

DevOpやら、Agile方法論やらで何かと話題は出てくるBDD。
そのBDDの実践において、.NET Framework上では、SpecFlowが使いやすそうに思えたので、ちょっと触れてみた。

主に、以下のページを参考に作成:

とりあえず、クラスライブラリに対するテストで、最低限のSpecFlowの機能を体感できる例を以下に記載。

環境の準備

Visual Studio 2015 の準備

ダウンロード:

勉強or個人での利用であれば、Community Edition で。

IDE拡張ツール導入


SpecFlowをVisual Studio 2015 上から利用するにあたって、そのままだと色々と不便なので、以下のURLから、Visual Studio 2015 向けの拡張ツールをインストール。

以下URLのページ中で「Download」ボタンを押下して、インストールしてくださいませ。
SpecFlow01.PNG


インストールしておくと、Visual Studio 上で、プロジェクトに追加するファイルとして、SpecFlow関係のファイルを簡単に追加できたり、SpecFlow関係の処理を直接IDE上から実施できるようになるので、便利。

SpecFlow準備

SpecFlowをとりあえず使えるようにするまでの、最小限の準備作業。

プロジェクト追加

クラスライブラリ向けの プロジェクトを新規作成。
Visual Studio のメニューから、「File > New > Project」をクリック。

テンプレートとして「Class Library」を選択し、名前を入力して、「OK」ボタンをクリック。
※名前は何でも問題なし。


テストプロジェクトの追加

ソリューションエクスプローラ上で、ソリューション名を右クリック、「Add > New Project ...」をクリック。

プロジェクトテンプレートとして「Unit Test Project」を選択し、名前を入力して、「OK」ボタンをクリック。
※名前は何でも問題なし。


SpecFlow初期化


テストプロジェクトにて、Package Manager コンソール上でコマンドを実行し、SpecFlowを初期化する。

実行するコマンド:
Install-Package SpecFlow
実行時画面:
※右上の「Default project」を、後から追加したテストプロジェクトに変更すること。

コマンドを実行すると、必要なDLLやら設定やらが、自動的にプロジェクトに追加されます。

TestProviderの設定を編集(MSTestを使うように修正)


この投稿ではMSTestを使うので、MSTestを使う向けに、設定を編集しておく。
テストプロジェクトのApp.configファイルを開く
<specFlow>タグの間に、<unitTestProvider name="MsTest" />を追加し、保存する。
追加後状態:
 <specFlow>
    <unitTestProvider name="MsTest" />
 </specFlow>

これで、SpecFlowを使用する準備は整ったはず。

テスト対象の参照追加


まだ何も実装ていないが、テスト対象のプロジェクトの参照を、追加する。
テストプロジェクトの「References」を右クリックして、「Add Reference」をクリック

開いたダイアログで、最初に新規作成した側のプロジェクトを選択し、「OK」ボタンをクリック

SpecFlowについて

SpecFlowは、.NET版のCucumberである。Cucumberは、Rubyで記載された、BDD(Behavior-Driven-Development)向けのツールである。日本語で記載した仕様が、そのまま試験として実行できるツール、仕様と実際のコードの橋渡しをするツール、と考えれば良いと思う。

SpecFlowの基本的な動作について、自分で理解できた内容を簡単に図示すると、いかのよーになる。

図の説明

Feature(フィーチャ)

非技術者の人間が理解できるレベルの内容で、テスト対象の仕様を記載したもの。Cucumberと共通の記載ルール(Gherkin)で記載する。

自動生成テストクラス

フィーチャに記載した仕様と、TestProviderの設定に応じて、SpecFlowが自動的に生成するテストクラス。フィーチャの記載が修正されると、勝手にまた再生成する。

Binding(バインディング)

Featureに記載された内容と、実際のソースコードのテストを関連付けている部分。
このソースで、実際にフィーチャの内容を確認する実処理(テスト対象クラスの作成だとか、Assert等)が記載される。

TestProvider

実際にテストを実施するフレームワーク。MSTestやら、NUnitやら。SpecFlowに対する設定で、変更が出来る。一覧はここで。


SpecFlowを使ってみる


ゆるゆると説明したSpecFlowの機能について、実際に実装をしてみる。

  1. フィーチャーを追加する

フォルダを追加
    テストプロジェクトを右クリック >  「Add > New Folder」をクリック。
    フォルダ名に「Features」と入力
    (フォルダ無しでも動作に問題はないです。分かりにくくなりそうなので、フォルダを作っているだけ)

フィーチャーファイルを追加
    追加した「Features」フォルダを右クリック「Add > NewItem」をクリック

    SpecFlow Feature があるはずなので、それを選択の上、
    任意の名前を入力し、「Add」ボタンをクリック。

  1. 仕様の記載

作成したフィーチャーファイルに、サンプルとして以下の内容を入力する。

#language: ja-JP
フィーチャ: サザエさん症候群チェッカー
   サザエさん症候群にかかるかどうかをチェックする機能。
   視聴者が視聴している曜日、時間、チャンネルを判別して、
   サザエさん症候群にかかるかどうかを判定する。

シナリオ: 日曜日にサザエさん症候群にかかる
   前提 今日は 日曜日
     かつ 時間は 18:30
   もし フジテレビ を見ている
   ならば サザエさん症候群にかかる

恐らく、読めば、意味は分るはず。先頭行で「#language: ja-JP」と指定しているので、日本語で記載。
内容の説明:
”フィーチャ”以外の、青字になっている部分に記載された内容が、テスト実行時に読み込まれる。(“フィーチャ”から”シナリオ”直前までの行はただの説明として無視される)
フィーチャファイルの記載ルールは、Cucumber由来の「Gherkin」という言語(いわゆるDSL)で決まっている。

フィーチャーファイルが更新されると、フィーチャーファイル中で、青字になっている「前提」、「もし」、「ならば」の部分に記載された内容を読み取って、テストとして実行するソースコードが自動生成される。
(なお、英語ではそれぞれ、「Given」、「When」、「Then」です)

この時点でテストプロジェクトを右クリックし、「Run SpecFlow Scenarios」を選択し、テストを実行すると、テスト結果として以下のような警告が発生するはず。
これは、記載されたシナリオに対応する実際のテストコード(Binding)が作成されていないために発生する。

  1. Binding を追加する

フィーチャーファイルに記載した「前提」、「もし」、「ならば」の各ステップに対応する、実際のコード記載する。

「Step Definition」ファイルの追加

開いているフィーチャファイルを、エディタ上で右クリックし、「Generate StepDefinition」を選択する
ダイアログが開くので、そのまま「Genrate」ボタンで、featuresフォルダ配下に、ファイルを作成してください。
(日本語のクラス名が気持ち悪く感じるのであれば、”Class name”部分を変更すること。”Style”は、生成されるメソッド名の形式を変更出来るが、日本語だと、余り使わないかも。。)

このファイルの役割について

フィーチャファイルに記載された「前提」、「もし」、「ならば」に対応したコードをそれぞれ記載するクラス。
フィーチャに記載された挙動を満たせるかどうかを、実際にコードを記載して、確認するクラス。

ファイル内容修正

生成されたファイルの内容を書き換えます。(メソッド名、一部の属性の中身も書き換えます)
※この時点ではまだ作っていないクラスも含まれるので、一部エラーが出る

変更内容:

[Binding]
public class サザエさん症候群チェッカーSteps
{
    private SazaeSanSyndromeChecker checker = new SazaeSanSyndromeChecker();

    [Given("今日は (.*)")]
    public void 前提今日は(string day)
    {
       Dictionary<string, DayOfWeek> dayOfWeeks = new Dictionary<string, DayOfWeek>();
       dayOfWeeks.Add("日曜日", DayOfWeek.Sunday);
       dayOfWeeks.Add("月曜日", DayOfWeek.Monday);
       dayOfWeeks.Add("火曜日", DayOfWeek.Tuesday);
       dayOfWeeks.Add("水曜日", DayOfWeek.Wednesday);
       dayOfWeeks.Add("木曜日", DayOfWeek.Thursday);
       dayOfWeeks.Add("金曜日", DayOfWeek.Friday);
       dayOfWeeks.Add("土曜日", DayOfWeek.Saturday);

       checker.SetDayOfWeek(dayOfWeeks[day]);
    }

    [Given("時間は (.*)")]
    public void 前提時間は(string hour)
    {
       DateTime time = DateTime.ParseExact(hour, "HH:mm", new CultureInfo("ja-JP"));

       checker.setTime(time);
    }

    [When("(.*) を見ている")]
    public void もしを見ている(string channel)
    {
       checker.setTVChannel(channel);
    }

    [Then("サザエさん症候群にかかる")]
    public void ならばサザエさん症候群にかかる()
    {
       Assert.IsTrue(checker.IsSazaeSyndrome, "条件を満たすはずが、かかっていない");
    }
}

メソッドに定義された属性([Given],[When],[Then])について

フィーチャファイル上のシナリオ単位で「前提(Given)」→「もし(When)」→「ならば(Then)」の順番に、一致する属性が設定されたメソッドが呼び出される。
各属性で正規表現( (.*) とかなっている部分 ) に一致する部分は、引数としてメソッドに引き渡される。
上記の例だと、[Given]属性として [Given("今日は (.*)")] が設定されているので、
フィーチャファイル上の「前提(Given)」の記載内容「今日は 日曜日」の中から、属性上の (.*) 部分に該当する”日曜日” が、メソッドに引き渡される。


テスト対象クラスの追加

StepDefinitionファイルの実装をエラーなく通せるだけの最小限のクラスを、最初に作成した側のプロジェクトに追加しておく。

プロジェクトを右クリック、「Add > Class...」を選択し、名前に「SazaeSanSyndromeChecker」と入力して、「Add」ボタンをクリック。

追加されたクラスの内容を、以下に修正
public class SazaeSanSyndromeChecker
{
    // 発症条件を満たしていればtrueを返却する
    public bool IsSazaeSyndrome { get { return false; } }

    // 現在の曜日を設定する
    public void SetDayOfWeek(DayOfWeek dayOfWeek)
    {
       // TODO implement
    }

    // 視聴時間を設定する
    public void setTime(DateTime time)
    {
       // TODO implement
    }

    // 視聴中のテレビチャンネルを設定する
    public void setTVChannel(string chnnelName)
    {
       // TODO implement
    }
}

テストプロジェクト側で生成したStep.csファイルに戻って、usingの追加を行い、エラーを解消する。

  1. テスト起動

以前の起動方法と同じように、テストプロジェクトを右クリックし「Run SpecFlow Scenarios」で、テストを実行する。

実装内容は最小限の状態なので、テストに失敗するが、この時点でのテストの失敗は、通常のMSUnitでのテスト失敗時と同様の表示内容(Assertの失敗が出てくるだけ)

上記テスト結果中の「Output」をクリックすると、実際にメソッドに引き渡された値を確認できる:

  1. 実装の追加


とりあえず正常に試験が通せるように、追加した「SazaeSanSyndromeChecker」クラスの実装を以下の通りに修正する。

public class SazaeSanSyndromeChecker
{
    private bool IsSazaeDayOfWeek { get; set; }

    private bool IsSazaeTime { get; set; }

    private bool IsSazaeChannel { get; set; }

    private const int START_HOUR = 18;
    private const int START_MINUTE = 30;

    private const string SAZAE_CHANNEL_NAME = "フジテレビ";

    // 発症条件を満たしていればtrueを返却する
    public bool IsSazaeSyndrome { get { return this.IsSazaeDayOfWeek && this.IsSazaeTime && this.IsSazaeChannel; } }

    // 現在の曜日を設定する
    public void SetDayOfWeek(DayOfWeek dayOfWeek)
    {
       this.IsSazaeDayOfWeek = DayOfWeek.Sunday == dayOfWeek;
    }

    // 視聴時間を設定する
    public void setTime(DateTime time)
    {
       int targetHour = time.Hour;
       int targetMinute = time.Minute;

       this.IsSazaeTime = (START_HOUR == targetHour && targetMinute >= START_MINUTE);
    }

    // 視聴中のテレビチャンネルを設定する
    public void setTVChannel(string channelName)
    {
       this.IsSazaeChannel = SAZAE_CHANNEL_NAME.Equals(channelName);
    }
}

  1. もう一回テスト起動

再度SpecFlowの試験を実施すると、今度はテストの実施が成功するはず。


別のシナリオとステップを追加してみる


シナリオ一つだけだと動きが実感しづらい面もあるので、追加のシナリオを作成し、それに対応するステップを追加する。

別シナリオの追加


フィーチャーファイルを開き、ファイルの末尾に、以下のシナリオを追加する。

追加する内容:

シナリオ: 火曜日にサザエさん症候群にかからない
   前提 今日は 火曜日
     かつ 時間は 18:30
   もし フジテレビ を見ている
   ならば サザエさん症候群にかからない

上記を追加しただけでテストを実行すると、Bindingに追加した「ならば」の部分に対応するステップがないため、実行時に警告となる。

対応するステップの追加

フィーチャファイルを開き、エディタ上で右クリックし、「Generate Step Definitions」を選択する

開いたダイアログで、「Copy methods to clipboard」を選択する

既に作成済みのStep.csファイルを開き、貼り付けをする。
多分、以下のような内容:
   [Then(@"サザエさん症候群にかからない")]
   public void ならばサザエさん症候群にかからない()
   {
       ScenarioContext.Current.Pending();
   }

メソッドの中身を、以下のように修正する。
[Then(@"サザエさん症候群にかからない")]
public void ならばサザエさん症候群にかからない()
{
   Assert.IsFalse(checker.IsSazaeSyndrome, "条件を満たしていないはずが、かかっている");
}

テストを再実行

この状態で、テストプロジェクトでテストを再実施すると、実施結果が再び緑色になったはず。

テスト実施結果のOutputを確認して、曜日として「火曜日」がメソッドに引きわされていることを確認していただきたい。


とりあえず、簡単な説明はここまで。

ソフトウェアの機能仕様をSpecFlow形式で記載/実行できるのであれば、実装漏れが強制的に減らせるし、後々のデグレード確認も簡単になって、品質がかなり向上するように思われる。

記載にあたっての参考資料:

以上。

0 件のコメント:

コメントを投稿