GoNextCommand/GoPreviousCommand
いよいよ2つのコマンド(GoNextCommand/GoPreviousCommand)を作ります。これも、いっぺんに作るのではなく、ちょっとずつ作ります。コマンドが持つCanExecuteプロパティのことは後回しです。
今回は、コマンドとしてRelayCommandクラスを使います。このクラスは、Visual Studio 2013で「ハブ アプリケーション(ユニバーサル アプリ)」テンプレートなどを使ってプロジェクトを作った場合に、共有プロジェクトの「Common」フォルダーに生成されます。ボタンクリックなどの簡単なイベントを扱うには、たいがいこのRelayCommandクラスで間に合います。
コマンドを実行したときに行われる処理は、すでにNextメソッド/Previousメソッドとして実装しました。従って、ここでテストすべきは、コマンドを実行したときにそれらのメソッドが呼び出されるかどうかだけです。前述の戦略3.を用いて、メソッドが呼び出された回数を調べるためのプローブを製品コードに導入します。
#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
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プロパティ
次は、上で作ったGoNextCommand/GoPreviousCommandのCanExecuteプロパティです。これも、そのプロパティのCanExecuteChangedイベントを同時に作るのは複雑なことになりますから、まずプロパティだけをちゃんと動くようにします。
| Numberプロパティ | GoNextCommand.CanExecuteプロパティ |
|---|---|
1以上int.MaxValue未満の数字 |
true |
| それ以外の文字列 |
false |
| Numberプロパティ | GoPreviousCommand.CanExecuteプロパティ |
|---|---|
| 2以上の数字 |
true |
| それ以外の文字列 |
false |
Numberプロパティに依存するプロパティなので、CanExecuteプロパティのgetterの中でNumberプロパティを参照して値を算出したくなります。しかし、CanExecuteプロパティの場合は、この後でCanExecuteChangedイベントの実装が待っています。そこでは、CanExecuteプロパティが変化したときにイベントを発火させるという処理になります。それを踏まえると、Numberプロパティのsetterの中でCanExecuteプロパティの値を確定させなくてはいけません。そこで、CanExecuteプロパティの値を保持するメンバー変数をここで導入し、Numberプロパティのsetterの中で値をセットするようにします。
このテストコードは、前述の戦略2.ですね。外部からNumberプロパティをセットすると、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にセット
}
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
