Windows

Specflowでビヘイビア駆動開発に触れてみた

ソフトウェア開発には、その考え方や特徴によって「○○開発」と名前が付いている開発プロセスがいくつかありますが、今回はビヘイビア駆動開発(BDD)というものに触れる機会があったので、その覚え書きです。

ビヘイビア駆動開発とは?

ビヘイビア駆動開発(以下、BDD)とは、ソフトウェアの「振る舞い」に注目した手法です。あらかじめ、開発したいソフトウェアの仕様を「振る舞い」として定義しておき、それを満たすコードを実装するというやり方を取ります。

事前にテストコードを作成する形になるので、テストファーストの開発手法の1つです。

SpecflowでBDDに触れてみる

一般的なテスト駆動開発と違い、BDDは自然言語に近い構文でテストを記述していくことが特徴的です。これを実現するためのフレームワークとして、今回はSpecflowというツールを使ってみます。

SpecflowはCucumberというBDDフレームワークのC#版で、VisualStudioに組み込んで使うことができます。ということで、さっそくBDDの一端に触れてみましょう(・∀・)

Specflowプラグインの導入

まずは、VisualStudioにSpecflowのプラグインを導入します。VS2010以降(Express版以外)に対応しているので、今回はVS2017(Community版)に導入してみました。導入自体は「ツール」>「拡張機能と更新プログラム」から検索してインストールするだけです。

テストプロジェクトの作成

続いて、テストプロジェクトを作成します。「ファイル」>「新規作成」>「プロジェクト」からC#の単体テストプロジェクトを選択します。

プロジェクトができたら、デフォルトで作成されるテストコード(UnitTest1.cs)は不要なので削除しておきます。

必要なファイルのインストール

Specflowの動作に必要なファイルをインストールしていきます。「ツール」>「NuGetパッケージマネージャー」>「パッケージマネージャーコンソール」を開いて、以下のコマンドを実行します。

PM> Install-Package SpecFlow -ProjectName <プロジェクト名>

プロジェクト中に「App.config」という設定ファイルができていれば、インストールは成功です。

Featureファイルの作成

ここからはテストシナリオの作成に入ります。まずはFeatureファイルというファイルを作ります。プロジェクトで「追加」>「新しい項目」>「Specflow Feature File」を選択します。

すると、以下のように英語で書かれたファイルが追加されますが、実はこれがテストシナリオそのものです。このように自然言語でテストシナリオを記述できるのが、BDDの特徴となります。

Feature: SpecFlowFeature1
	In order to avoid silly mistakes
	As a math idiot
	I want to be told the sum of two numbers

@mytag
Scenario: Add two numbers
	Given I have entered 50 into the calculator
	And I have also entered 70 into the calculator
	When I press add
	Then the result should be 120 on the screen

上記のテストシナリオでは、電卓の足し算を例にいくつかのキーワードでシナリオを定義しています。以下、各キーワードとそれに対応したシナリオの説明です。

キーワード意味シナリオ
Given事前条件電卓に50を入力する
And追加の事前条件電卓に70を入力する
When試行足し算のボタンを押す
Then事後条件(期待する結果)結果が120となっていること

一見するとテストコードに見えませんが、これをSpecflowが解釈してテストとして実行してくれます。これにはちょっとびっくりしました(゚_゚;)

Stepファイルの作成

もちろん、シナリオだけではテストができないので、シナリオと紐付けるテストコードを実装する必要があります。

まずは、テストそのものを実行するテストランナーを指定します。App.configを開き、以下のような記述を書き加えます。今回はVS2017にデフォルトで入っているMSTestを指定しました。

続いて、先ほど作成したFeatureファイルを開き「右クリック」>「Generate Step Definitions」を選択すると、以下のようなウインドウが開きます。シナリオ全てが選択されていることを確認し「Generate」を押下しましょう。

これでfeatureファイルに定義したシナリオに対応したテストコードのひな形が自動生成されます。ちゃんと各キーワードに対応したメソッドが用意されていることが分かります。

using System;
using TechTalk.SpecFlow;

namespace CalcTest
{
    [Binding]
    public class SpecFlowFeature1Steps
    {
        [Given(@"I have entered (.*) into the calculator")]
        public void GivenIHaveEnteredIntoTheCalculator(int p0)
        {
            ScenarioContext.Current.Pending();
        }
        
        [Given(@"I have also entered (.*) into the calculator")]
        public void GivenIHaveAlsoEnteredIntoTheCalculator(int p0)
        {
            ScenarioContext.Current.Pending();
        }
        
        [When(@"I press add")]
        public void WhenIPressAdd()
        {
            ScenarioContext.Current.Pending();
        }
        
        [Then(@"the result should be (.*) on the screen")]
        public void ThenTheResultShouldBeOnTheScreen(int p0)
        {
            ScenarioContext.Current.Pending();
        }
    }
}

テスト対象の作成とテストコード実装

ここまでで、テストシナリオ+テストコードのひな形は作成できました。あとはテスト対象となるプログラムと、それに合わせたテストコードを実装していくだけです。

まずは電卓クラス。今回は簡単のために、2つの数の足し算のみを実装しました。

namespace Calc
{
	public class Calculator
	{
		public int number1;
		public int number2;

		public int Add()
		{
			return number1 + number2;
		}
    }
}

続いてテストコード。こちらは電卓クラスのインスタンスを最初に作成し、各シナリオに対応するメソッド中でそのインスタンスに対する操作をしています。最後の「Then」に対応するコードは計算結果のチェックなので、Assertを入れています。

using TechTalk.SpecFlow;
using Calc;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CalcTest
{
    [Binding]
    public class SpecFlowFeature1Steps
    {
		private Calculator calc = new Calculator();
		private int result;

        [Given(@"I have entered (.*) into the calculator")]
        public void GivenIHaveEnteredIntoTheCalculator(int n)
        {
			calc.number1 = n;
        }
        
        [Given(@"I have also entered (.*) into the calculator")]
        public void GivenIHaveAlsoEnteredIntoTheCalculator(int n)
        {
			calc.number2 = n;
        }
        
        [When(@"I press add")]
        public void WhenIPressAdd()
        {
			result = calc.Add();
        }
        
        [Then(@"the result should be (.*) on the screen")]
        public void ThenTheResultShouldBeOnTheScreen(int expected)
        {
			Assert.AreEqual(expected, result);
		}
    }
}

テスト実行

ようやくテスト実行までこぎ着けました。「テストエクスプローラー」>「すべて実行」からテストを実行してみます。ちゃんと足し算ができているようですね(・∀・)

このように、自然言語に近い形で記述したテストシナリオに従ってテストを実行することができました。

まとめ

今回はだいぶ簡単な例ではありましたが、Specflowを使ってBDDの一端に触れてみました。

個人的に感じたところとしては、テストシナリオ自体を自然言語で書けるところはかなり魅力的でした。あとから振り返っても何をテストしたいのか一目瞭然なので、メンテナンス性も上がりそうです。

なかなかテスト手法に対して学ぶ機会は多くないので、今回はとても良い勉強になりました。興味のある方はこれを参考に試してみてはいかがでしょうか?

ではではノシ

関連記事

C言語 自作物 Linux プログラミング

wordleもどきのCUIアプリをつくってみた

最近、wordleという英単語当てゲームで遊んでいます。シンプルなゲームながら、通勤時間の暇つぶしや友人とのスコア比べなど意外と中毒性があり面白いです。 普通に英単語の勉強にもなるので、もっとたくさん ...

RaspberryPi Linux

Raspberry Pi4+Ubuntu ServerでGitLabを動かしてみる

お仕事でGitLabに触れる機会があったので、学習用に自宅にもGitLabが欲しくなりました。 手元にあるRaspberry Pi4+Dockerならお手軽に立ち上げられるはずと着手したものの、意外と ...

Flutter プログラミング

【Flutter】アプリ内の設定値を実装する方法

アプリ内で独自の設定を作る場合、そのデータを保持する方法を考える必要があります。 SQL、テキストファイルなど選択肢は多々ありますが、shared_preferencesというパッケージを使えば簡単に ...

RaspberryPi Linux

YoctoでRaspberryPi4のイメージをビルドしてみた

昨今、様々なデバイスでLinuxが動くようになっている中、組み込みLinuxのデファクトスタンダードとなりつつあるのが「Yocto」と呼ばれるビルドシステムです。 組み込みの現場ではその名前を聞くこと ...

C++ 自作物

言語処理系をつくろう(第7回):比較演算子を実装する

自作の言語処理系開発日記の第7回です。前回までで変数の実装が終わったので、ここからはいよいよ制御構文を実装…と思ったのですが、制御のためには比較演算子を実装する必要がありました。 ということで、今回は ...

Ryo Yoneyama

とある会社でソフトウェアエンジニアをしています。技術的な備忘録を中心にまとめてます。ネタがあれば日記も書きます。

    -Windows