SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

C#で始めるテスト駆動開発入門

状態を持つクラスをテストファーストする

C#で始めるテスト駆動開発入門(10)

ダウンロード CsTdd10.zip (122.5 KB)

例題:FizzBuzzアプリのビューモデル

 前回、FizzBuzzのロジックを紹介しました(テストコードと製品コード)。今回は、そのUIを題材にしてみましょう

 画面そのものをテストファーストで作るのは、(不可能ではないものの)そのコストが見合わないので行いません。かわりに、UIを画面(View)と画面のモデル(ViewModel)に分割し、ViewModelだけをテストファーストで作ります。

 なお、ここでは「ViewModelとはXAMLで記述された画面にバインドされるもの」という程度の定義にしておきます。本来のMVVMパターンにおけるViewModelの意味は、MSDN blogs「Introduction to Model/View/ViewModel pattern for building WPF apps」(英語、2005/10/8)をご覧ください。

 今回のUIは、次の図のようにしましょう。

FizzBuzzアプリのUI
VS

 画面には、数字を入力するTextBoxコントロールと、その数値から求められたFizzBuzzの結果を表示するTextBlockコントロールがあります。また、TextBoxコントロールの左右には、数値を1ずつ増減するためのButtonコントロールを配置します。

 すると、この画面にバインドするViewModelには、図の右に示したようなプロパティ/イベント/コマンドが必要になります。なお、2つのコマンドは、それぞれ内部にCanExecuteプロパティを持っていますが、これはそのコマンドが実行できる状態か否かを表します。コマンドをバインドされたButtonコントロールは、CanExecuteプロパティを見て、自身の表示(ボタンを押せるかどうか)を切り替えます。また、2つのコマンドが発行するCanExecuteChangedイベントは、矢印を描いてありませんが、CanExecuteプロパティが変化したことをButtonコントロールに通知します。

 このViewModelの簡単な説明と相互の影響関係を、簡単に示しておきます。

ViewModelの簡単な説明
パーツ 説明 影響関係
Numberプロパティ
(string)
FizzBuzzへの入力となる数字。
画面に双方向バインド(読み書き可)
独立した状態
Wordプロパティ
(string)
FizzBuzzの出力。
画面に一方向バインド(読み取りのみ)
Numberに従属する状態
PropertyChangedイベント NumberとWordが変化したことを通知する Numberの変化に従属するイベント
GoNextCommand/GoPreviousCommand このコマンドを実行すると、 Numberが1ずつ増減する Numberを変化させる
GoNextCommand/GoPreviousCommandのCanExecuteプロパティ そのコマンドを実行可能かどうか Numberに従属する状態
GoNextCommand/GoPreviousCommandのCanExecuteChangedイベント CanExecuteプロパティが変化したことを通知する CanExecuteの変化に(従って、Numberの変化に)従属するイベント

 上記のスペックを、以降でテストファーストして作っていきます。「状態を持つクラスをテストファーストする戦略」で述べたように、独立した状態から手掛けて、だんだんと複雑なパーツに移っていきます。

Numberプロパティ(独立した状態)

 最初は、もっとも作りやすいNumberプロパティから始めます。Numberプロパティは、他のプロパティに従属していません。上の表にあるように、スペックとしてNumberプロパティは読み書き可能ですから、前述の戦略1.が利用できて、シンプルにテストコードを書けます。プレーンなクラスに、単純なpublicプロパティを実装するだけですから、たやすく作れます(2つのテストコードをまとめて示しますが、実際には1つずつ作っていきます)。

テストコード: Numberプロパティのテスト
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using CsTdd08App.UI; // ←ViewModelを配置する名前空間

namespace CsTdd08Tests.Shared.UI
{
  [TestClass]
  public class FizzBuzzViewModelTest
  {
    [TestMethod]
    public void NumberプロパティTest01_既定値は1()
    {
      // FizzBuzzは1から始まるので、規定値は1とする
      FizzBuzzViewModel vm = new FizzBuzzViewModel();
      Assert.AreEqual<string>("1", vm.Number);
      // → クラス定義を書く
      // → Numberプロパティのgetterだけを仮実装(return "1";)
    }

    [TestMethod]
    public void NumberプロパティTest02_書き込み可能()
    {
      FizzBuzzViewModel vm = new FizzBuzzViewModel();
      vm.Number = "X"; //文字列なら何でも受け付けるものとする
      Assert.AreEqual<string>("X", vm.Number);
      // → Numberプロパティのsetterを仮実装(中身は空)してコンパイル可能に
      // → メンバー変数を設置し、初期化するコードを追加
      // → Numberプロパティのgetter/setterを本実装
    }
  }
}
製品コード: FizzBuzzViewModelクラスの作成と、Numberプロパティの実装
namespace CsTdd08App.UI
{
  public class FizzBuzzViewModel
  {
    private string _number = "1";
    public string Number
    {
      get { return _number; }
      set { _number = value; }
    }
  }
}

Wordプロパティ(従属する状態)

 Wordプロパティは、読み取り専用です。Numberプロパティの変化に応じてWordプロパティの値が決まる(Numberプロパティに従属している)ので、前述の戦略2.を使い、Numberプロパティと一緒にテストコードを書きます。

 Wordプロパティのスペックは次の表のように書けます。FizzBuzzの答が得られない条件のときは、文字列"???"を返すことにします。

Wordプロパティのスペック
作るもの Wordプロパティ
置き場所 FizzBuzzViewModelクラス
返値の型 string
条件と得られる値
Number
プロパティ
得られる値
1以上の数字 FizzBuzzの答
それ以外の文字列 "???"

 テストコードと、それによって実装した製品コードは、次のようになります。この製品コードは、リファクタリング後のものです。

テストコード: Wordプロパティのテスト
[TestMethod]
public void WordプロパティTest01_既定値は1()
{
  FizzBuzzViewModel vm = new FizzBuzzViewModel();
  Assert.AreEqual<string>("1", vm.Word);
  // → Wordプロパティのgetterを仮実装(return "1";)
}

[TestMethod]
public void WordプロパティTest02_3のときFizz()
{
  FizzBuzzViewModel vm = new FizzBuzzViewModel();
  vm.Number = "3";
  Assert.AreEqual<string>("Fizz", vm.Word);
  // → Wordプロパティのgetterを本実装(FizzBuzzPlayerを呼び出す)
  // → GREENになったらリファクタリング
  //   ・GetNumberメソッド、GetWordメソッドの切り出し
  //   ・メンバー変数_wordを導入する
  //   ・_wordを算出するコードをNumberのsetter内に移動
  //     ※移動すると例外が出る。リファクタリングを中止し、先に次のテストへ進む
}

[TestMethod]
public void WordプロパティTest03_0のときは不明()
{
  FizzBuzzViewModel vm = new FizzBuzzViewModel();
  vm.Number = "0";
  Assert.AreEqual<string>("???", vm.Word);
  // → GetWordメソッドを改修(1以上の数字ではないときの分岐を追加)
}
製品コード: Wordプロパティの実装
public class FizzBuzzViewModel
{
  private FizzBuzzPlayer _player = new FizzBuzzPlayer();
  // FizzBuzzPlayerクラスについては前回を参照

  private string _number = "1";
  public string Number
  {
    get { return _number; }
    set 
    {
      _number = value;
      _word = GetWord();
    }
  }

  private string _word = "1";
   public string Word
   { 
     get { return _word; } 
   }
 
   private int GetNumber()
   {
     int n;
     int.TryParse(_number, out n);
     return n;
   }
 
   private string GetWord()
   {
     int n = this.GetNumber();
     if (n > 0)
       return _player.Say(n);
     else
       return "???";
   }
}

 これでプロパティは実装できましたが、まだ画面にはバインドできません。プロパティの変化が、画面に伝わらないからです。そのためには、FizzBuzzViewModelクラスにINotifyPropertyChangedインターフェースを実装する必要があります。それが、次のPropertyChangedイベントの実装です。

次のページ
PropertyChangedイベント

この記事は参考になりましたか?

C#で始めるテスト駆動開発入門連載記事一覧

もっと読む

この記事の著者

biac(ばいあっく)

HONDA R&Dで自動車の設計をやっていた機械屋さんが、技術の進化スピードに魅かれてプログラマーに。以来30年ほど、より良いコードをどうやったら作れるか、模索の人生。わんくま同盟の勉強会(名古屋)で、よく喋ってたりする。2014/10~2019/6 Microsoft MVP (Windows Devel...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/8238 2014/11/12 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー