2013年11月14日木曜日

SpecFlow を使ってみた2

前回の続き

前回は、空のテストが動くところまで書いたので、ここからはテスト内容の記述と機能の実装に入ります。

テストの詳細追加
まずは、Customer を確認するためのテストを行います。

まず、テストクラス(Steps.cs)に Customer のクラスを格納する変数を用意します。
(当然この段階では、ビルドエラーとなります。)
    private Customer _customer;
次に、インスタンス化の確認為のテストを記述します。
「a user has entered information about a customer」
への部分へテストコードを追加します。
        [Given(@"a user has entered information about a customer")]
        public void GivenAUserHasEnteredInformationAboutACustomer() {
            _customer = new _customer();
        }
以前記述していた「ScenarioContext.Current.Pending();」は、不要となるため削除します。

当然、ここでもビルドエラーとなります。

テスト対象のプロジェクト作成
次に、テスト対象となるためのプロジェクトを作成します。
ソリューションエクスプローラーのソリューションで
右クリックして、追加 → 新しいプロジェクト とし、
Windows フォームでも WPF アプリケーションでも良いので作成します。

Entity Framework の参照設定を追加
今回の例では、EntityFramework を使ってテストを行うため、
プロジェクトの参照に EntityFramework を追加します。

メニューから、
ツール → ライブラリ パッケージマネージャ → ソリューションの NuGet パッケージの管理...
を選択し、オンラインで、「EntityFramework」を検索してインストールします。

追加対象を聞いてくるので、テストプロジェクトと、テスト対象のプロジェクト両方に追加でをするようにします。

Customer クラスの作成
テスト対象のプロジェクトに、Customer を扱うためのクラスを作成します。
プロジェクトを右クリックし、追加 → 新しい項目 → クラス としてクラスを追加します。

クラス名は Customer とします。

以下のように、クラスの内容を記述します。
    public class Customer {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
テストクラスからテスト対象クラスが見えるように設定する
Customer クラスが作成できたので、テストプロジェクトへ戻り、
テスト対象のクラスを扱えるように参照設定を追加します。

テストのプロジェクトの参照設定を右クリックして、「参照の追加」を押します。
ソリューションのプロジェクトで、テスト対象のプロジェクトが有るので、
チェックを付け、OK で参照追加します。

これで、テストプロジェクトから、テスト対象プロジェクトが参照できるようになりました。

Customer クラスを使えるようにする
Steps.cs の using に、テスト対象のプロジェクトを設定します。
これで、テストクラスで指定していた Customer クラスのエラーがなくなります。

テストの実施
再度テストを実行します。
AddCustomer.feature ファイルで右クリックし、コンテキストメニューを表示させ
「Run SpecFlow Senarios」を実行します。

ここで、一つ目のテストが通った事が確認できます。
(詳しく確認するのであれば、 Steps ファイルの該当箇所にブレークポイントを設定して、
Debug SpecFlow Senarios で確認するとよく分かります)

以降、失敗 → 実装 → 成功 の繰り返しで機能を完成させていく形となります。
例題には、残り 3 つあるが、その内の 2 つについては簡単に記述しておきます。
        [Given(@"she has provided a first name and a last name as required")]
        public void GivenSheHasProvidedAFirstNameAndALastNameAsRequired() {
            _customer.FirstName = "Julie";
            _customer.LastName = "Lerman";
        }

        [When(@"she completes entering more information")]
        public void WhenSheCompletesEnteringMoreInformation() {
        }
「she has provided a first name and a last name as required」については、
名前のセットを行っています。
「she completes entering more information」については、特にする事無いようです。

EntityFramework の部分のテスト
最後に、データを保存するための機能
「that customer should be stored in the system」の部分を実装します。
まずは、テストのクラスに DB を扱うためのクラスを宣言します。
        private CustomerRepository _repository; 
さっきと同様、今の段階で、CustomerRepository は存在しないので、ビルドエラーとなります。
あと、テスト内容も記述します(本当は細かく見ていくんでしょうが、参考通り一気に書きます)。
        [Then(@"that customer should be stored in the system")]
        public void ThenThatCustomerShouldBeStoredInTheSystem() {
            _repository = new CustomerRepository();
            _repository.Add(_customer);
            _repository.Save();
            Assert.IsNotNull(_repository.FindById(_customer.Id));
        }
と言う事で、テスト対象のクラスに、CustomerRepository のクラスを追加します。
Customer クラスと同様に、追加 → 新しい項目 → クラス で追加します。
    public class CustomerRepository {
        public void Add(Customer customer) { throw new NotImplementedException(); }
        public int Save() { throw new NotImplementedException(); }
        public Customer FindById(int id) { throw new NotImplementedException(); }
    }
この状態で、再度テストを実行して結果を確認します。
すると、
    結果  のメッセージ:   
    テスト メソッド UnitTestProject.Features.Add.AddCustomerFeature.HappyPath が例外をスローしました:
    System.NotImplementedException: メソッドまたは操作は実装されていません。

の内容が表示されます。

これは、最初のインスタンス化の部分はパスして、次の Add メソッドで例外が発生しているため、
テストエラーとなっています。
Add メソッドの内容が「throw new NotImplementedException();」なので当然と言えば当然ですね。

参考 Web の説明がリポジトリメソッドを実装していない云々と言う部分が理解できなかったのですが、EntityFramework の Add メソッド等を使えるように、CustomerRepository に、更に DbContext を継承した CustomerContext のクラスを作成し Add メソッド等を完成させます。
(EntityFrameworkを使うので、using System.Data.Entity; を追加します。)
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    using System.Data.Entity;

    namespace WpfApplication1 {
        public class CustomerRepository {
            private CustomerContext _context = new CustomerContext();
            public void Add(Customer customer) {
                _context.Customers.Add(customer); 
                //throw new NotImplementedException();
            }
            public int Save() {
                return _context.SaveChanges(); 
                //throw new NotImplementedException();
            }
            public Customer FindById(int id) {
                return _context.Customers.Find(id);
                //throw new NotImplementedException();
            }
        }

        public class CustomerContext : DbContext {
            public DbSet Customers { get; set; }
        }
    }
この状態で、テストを実行するとエラー無くテストが完了します。
ただ、この状態では Save メソッドの部分で誤検知があるとの事。

誤検知部分の修正
誤検知の内容としては、以前の内容は Customer の ID が存在すれば OK と言う物ですが、
DB に保存に成功しても失敗しても ID には何らかの値が入っているために、
この存在チェックではテストの意味がないと言う事です。

この部分、正当な検査方法(正常に保存できたら 0 より大きな値が返ってくる)に変更します。
        [Then(@"that customer should be stored in the system")]
        public void ThenThatCustomerShouldBeStoredInTheSystem() {
            _repository = new CustomerRepository();
            _repository.Add(_customer);
            _repository.Save();
            //Assert.IsNotNull(_repository.FindById(_customer.Id));
            Assert.IsNotNull(_customer.Id>0);
        }
これで、正常なテストとなりました。


以上が、SpecFlow の使い方等々です。
長々と書き、またこれと言った画像も無いため、分かりにくく申し訳無いです。

数日経ってからの更新なので、アクセス数等を確認してみましたが、
この話題ではあまりアクセス数が有りませんでした。
(他の記事も決して多いわけでは有りませんが。。。)

ひょっとしたら、古い技術なのかも知れませんが、どなたかの参考になれば幸いです。

0 件のコメント:

コメントを投稿