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」ボタンを押下して、インストールしてくださいませ。
インストールしておくと、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
SpecFlowを使ってみる
ゆるゆると説明したSpecFlowの機能について、実際に実装をしてみる。
フィーチャーを追加する
フォルダを追加
テストプロジェクトを右クリック > 「Add > New Folder」をクリック。
フォルダ名に「Features」と入力
(フォルダ無しでも動作に問題はないです。分かりにくくなりそうなので、フォルダを作っているだけ)
フィーチャーファイルを追加
追加した「Features」フォルダを右クリック「Add > NewItem」をクリック
SpecFlow Feature があるはずなので、それを選択の上、
任意の名前を入力し、「Add」ボタンをクリック。
仕様の記載
作成したフィーチャーファイルに、サンプルとして以下の内容を入力する。
#language: ja-JP
フィーチャ: サザエさん症候群チェッカー
サザエさん症候群にかかるかどうかをチェックする機能。
視聴者が視聴している曜日、時間、チャンネルを判別して、
サザエさん症候群にかかるかどうかを判定する。
シナリオ: 日曜日にサザエさん症候群にかかる
前提 今日は 日曜日
かつ 時間は 18:30
もし フジテレビ を見ている
ならば サザエさん症候群にかかる
恐らく、読めば、意味は分るはず。先頭行で「#language: ja-JP」と指定しているので、日本語で記載。
内容の説明:
”フィーチャ”以外の、青字になっている部分に記載された内容が、テスト実行時に読み込まれる。(“フィーチャ”から”シナリオ”直前までの行はただの説明として無視される)
フィーチャファイルの記載ルールは、Cucumber由来の「Gherkin」という言語(いわゆるDSL)で決まっている。
フィーチャーファイルが更新されると、フィーチャーファイル中で、青字になっている「前提」、「もし」、「ならば」の部分に記載された内容を読み取って、テストとして実行するソースコードが自動生成される。
(なお、英語ではそれぞれ、「Given」、「When」、「Then」です)
この時点でテストプロジェクトを右クリックし、「Run SpecFlow Scenarios」を選択し、テストを実行すると、テスト結果として以下のような警告が発生するはず。
これは、記載されたシナリオに対応する実際のテストコード(Binding)が作成されていないために発生する。
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の追加を行い、エラーを解消する。
テスト起動
以前の起動方法と同じように、テストプロジェクトを右クリックし「Run SpecFlow Scenarios」で、テストを実行する。
実装内容は最小限の状態なので、テストに失敗するが、この時点でのテストの失敗は、通常のMSUnitでのテスト失敗時と同様の表示内容(Assertの失敗が出てくるだけ)
上記テスト結果中の「Output」をクリックすると、実際にメソッドに引き渡された値を確認できる:
実装の追加
とりあえず正常に試験が通せるように、追加した「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);
}
}
もう一回テスト起動
再度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 件のコメント:
コメントを投稿