SHOEISHA iD

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

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

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

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

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

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

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

GoNextCommand/GoPreviousCommand

 いよいよ2つのコマンド(GoNextCommandGoPreviousCommand)を作ります。これも、いっぺんに作るのではなく、ちょっとずつ作ります。コマンドが持つCanExecuteプロパティのことは後回しです。

 今回は、コマンドとしてRelayCommandクラスを使います。このクラスは、Visual Studio 2013で「ハブ アプリケーション(ユニバーサル アプリ)」テンプレートなどを使ってプロジェクトを作った場合に、共有プロジェクトの「Common」フォルダーに生成されます。ボタンクリックなどの簡単なイベントを扱うには、たいがいこのRelayCommandクラスで間に合います。

 コマンドを実行したときに行われる処理は、すでにNextメソッド/Previousメソッドとして実装しました。従って、ここでテストすべきは、コマンドを実行したときにそれらのメソッドが呼び出されるかどうかだけです。前述の戦略3.を用いて、メソッドが呼び出された回数を調べるためのプローブを製品コードに導入します。

テストコード: GoNextCommand/GoPreviousCommandのテスト
#if DEBUG // プローブを使うテストはデバッグビルド時のみ有効
[TestMethod]
public void GoNextCommandTest01()
{
  // 冒頭に using CsTdd08App.Common; を追加

  // GoNextCommand プロパティを持っている
  FizzBuzzViewModel vm = new FizzBuzzViewModel();
  RelayCommand cmd = vm.GoNextCommand;

  // コマンドを実行すると、Nextメソッドが1回だけ実行される
  vm.test_NextCount = 0;  // ←プローブ
  cmd.Execute(null);
  Assert.AreEqual<int>(1, vm.test_NextCount);
}

[TestMethod]
public void GoPreviousCommandTest01()
{
  // GoPreviousCommand プロパティを持っている
  FizzBuzzViewModel vm = new FizzBuzzViewModel();
  RelayCommand cmd = vm.GoPreviousCommand;

  // コマンドを実行すると、Previousメソッドが1回だけ実行される
  vm.test_PreviousCount = 0;  // ←プローブ
  cmd.Execute(null);
  Assert.AreEqual<int>(1, vm.test_PreviousCount);
}
#endif
製品コード: GoNextCommand/GoPreviousCommandの実装
public class FizzBuzzViewModel : System.ComponentModel.INotifyPropertyChanged
{
  public FizzBuzzViewModel()
   {
    // 冒頭に using CsTdd08App.Common; を追加
    // コマンドの第2引数は、とりあえず常にtrueを返すものとしておく
    this.GoNextCommand = new RelayCommand(() => this.Next(), () => true);
     this.GoPreviousCommand = new RelayCommand(() => this.Previous(), ()=>true);
   }

  // (…略…)

  public void Next()
  {
#if DEBUG
     test_NextCount++;
 #endif
    int n = this.GetNumber();
    if (n > 0)
      this.Number = (n + 1).ToString();
  }

  public void Previous()
  {
#if DEBUG
     test_PreviousCount++;
 #endif
    int n = this.GetNumber();
    if (n > 0)
      this.Number = (n - 1).ToString();
  }


  #region GoNextCommand
  public Common.RelayCommand GoNextCommand { get; private set; }
  #endregion

  #region GoPreviousCommand
  public Common.RelayCommand GoPreviousCommand { get; private set; }
  #endregion


#if DEBUG
   #region テスト時だけ利用するコード
 
   public int test_NextCount { get; set; }
   public int test_PreviousCount { get; set; }
 
   #endregion
 #endif
}

CanExecuteプロパティ

 次は、上で作ったGoNextCommandGoPreviousCommandCanExecuteプロパティです。これも、そのプロパティのCanExecuteChangedイベントを同時に作るのは複雑なことになりますから、まずプロパティだけをちゃんと動くようにします。

GoNextCommandのCanExecuteプロパティのスペック
Numberプロパティ GoNextCommand.CanExecuteプロパティ
1以上int.MaxValue未満の数字 true
それ以外の文字列 false
GoPreviousCommandのCanExecuteプロパティのスペック
Numberプロパティ GoPreviousCommand.CanExecuteプロパティ
2以上の数字 true
それ以外の文字列 false

 Numberプロパティに依存するプロパティなので、CanExecuteプロパティのgetterの中でNumberプロパティを参照して値を算出したくなります。しかし、CanExecuteプロパティの場合は、この後でCanExecuteChangedイベントの実装が待っています。そこでは、CanExecuteプロパティが変化したときにイベントを発火させるという処理になります。それを踏まえると、Numberプロパティのsetterの中でCanExecuteプロパティの値を確定させなくてはいけません。そこで、CanExecuteプロパティの値を保持するメンバー変数をここで導入し、Numberプロパティのsetterの中で値をセットするようにします。

 このテストコードは、前述の戦略2.ですね。外部からNumberプロパティをセットすると、CanExecuteプロパティの値が決まり、それはスペック的に観測可能です。

テストコード: GoNextCommand/GoPreviousCommandのCanExecuteプロパティ
// GoNextCommand.CanExecuteプロパティを実装するためのテストコード

[TestMethod]
public void GoNextCommand_CanExecuteTest01_NumberがMaxValueのときfalse()
{
  FizzBuzzViewModel vm = new FizzBuzzViewModel();
  vm.Number = int.MaxValue.ToString();
  Assert.IsFalse(vm.GoNextCommand.CanExecute(null));
  // → メンバー変数_canGoNextを導入、初期値をfalseにしてGREENにする
}

[TestMethod]
public void GoNextCommand_CanExecuteTest02_NumberがMaxValue未満のときtrue()
{
  FizzBuzzViewModel vm = new FizzBuzzViewModel();
  vm.Number = (int.MaxValue - 1).ToString();
  Assert.IsTrue(vm.GoNextCommand.CanExecute(null));
  // → CanExecuteはNumberプロパティを変えたときにtrue/falseが確定する。
  //    ならば、Numberプロパティのsetterで_canGoNextにセットすればよい
}

[TestMethod]
public void GoNextCommand_CanExecuteTest03_Numberが1以上の数字でないときfalse()
{
  FizzBuzzViewModel vm = new FizzBuzzViewModel();
  vm.Number = "X";
  Assert.IsFalse(vm.GoNextCommand.CanExecute(null));
}

[TestMethod]
public void GoNextCommand_CanExecuteTest04_初期値はtrue()
{
  FizzBuzzViewModel vm = new FizzBuzzViewModel();
  Assert.IsTrue(vm.GoNextCommand.CanExecute(null));
  // 前のCanExecuteTest01で初期値をfalseにしてしまったのは間違いだった
}


// GoPreviousCommand.CanExecuteプロパティを実装するためのテストコード

[TestMethod]
public void GoPreviousCommand_CanExecuteTest01_Numberが1のときfalse()
{
  FizzBuzzViewModel vm = new FizzBuzzViewModel();
  vm.Number = "1";
  Assert.IsFalse(vm.GoPreviousCommand.CanExecute(null));
  // → メンバー変数_canGoPreviousを導入、初期値をfalseにしてGREENにする
}

[TestMethod]
public void GoPreviousCommand_CanExecuteTest02_Numberが2以上のときtrue()
{
  FizzBuzzViewModel vm = new FizzBuzzViewModel();
  vm.Number = "2";
  Assert.IsTrue(vm.GoPreviousCommand.CanExecute(null));
  // → 前と同様、Numberプロパティのsetterで_canGoPreviousにセット
}
製品コード: GoNextCommand/GoPreviousCommandのCanExecuteプロパティの実装
public FizzBuzzViewModel()
{
  this.GoNextCommand = new RelayCommand(() => this.Next(), //() => true);
                                        () => this._canGoNext);
  this.GoPreviousCommand = new RelayCommand(() => this.Previous(), //() => true);
                                            () => this._canGoPrevious);
}

private string _number = "1";
public string Number
{
  get { return _number; }
  set 
  {
    SetProperty<string>(ref _number, value);
    SetProperty<string>(ref _word, GetWord(), "Word");

    int n = GetNumber();
     _canGoNext = (0 < n) && (n < int.MaxValue);
     _canGoPrevious = (1 < n);
  }
}

// (…略…)

#region GoNextCommand
public Common.RelayCommand GoNextCommand { get; private set; }
private bool _canGoNext = true;
#endregion

#region GoPreviousCommand
public Common.RelayCommand GoPreviousCommand { get; private set; }
private bool _canGoPrevious = false;
#endregion

次のページ
CanExecuteChangedイベント

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

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」など、さまざまなカンファレンスを企画・運営しています。

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

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

メールバックナンバー