SHOEISHA iD

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

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

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

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

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

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

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

PropertyChangedイベント

 イベントも単独ではテストできません。今回のPropertyChangedイベントは、状態の変化によって発火しますから、これも前述の戦略2.によって、読み書き可能なNumberプロパティと組み合わせればテストコードが書けます。

 外部から変更できるプロパティはNumberだけですが、Wordプロパティも同時に変化します。従って、PropertyChangedイベントはプロパティ2つ分(=2回)発行されます。

 PropertyChangedイベントは、INotifyPropertyChangedインターフェースの実装として作ります。そこで、まずINotifyPropertyChangedインターフェースからテストファーストします。

テストコード: INotifyPropertyChangedインターフェースのテスト
[TestMethod]
public void PropertyChangedTest01_INotifyPropertyChangedを実装()
{
  FizzBuzzViewModel vm = new FizzBuzzViewModel();
  Assert.IsInstanceOfType(vm, 
    typeof(System.ComponentModel.INotifyPropertyChanged));
}
製品コード: INotifyPropertyChangedインターフェースを実装
public class FizzBuzzViewModel : System.ComponentModel.INotifyPropertyChanged
{
  // (…略…)

  #region INotifyPropertyChangedの実装
   public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
   #endregion
}

 ここでちょっと便利なコードを、TDDせずに追加しておきます。ViewModelでは、PropertyChangedイベントを発火させるコードを何度も書くことになるので、そこをサポートするメソッドです。これは、Visual Studio 2012の「グリッド アプリケーション (XAML)」テンプレートや「分割アプリケーション (XAML)」テンプレートのCommonフォルダに入っている「BindableBase」クラスからコピーしてきたコードです(一部改良済み)。

製品コード: PropertyChangedイベントのヘルパーメソッドを追加(TDDしていない)
#region INotifyPropertyChangedの実装

public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

private bool SetProperty<T>(ref T storage, T value,
   [System.Runtime.CompilerServices.CallerMemberName] 
   string propertyName = null)
 {
   if (object.Equals(storage, value)) return false;
 
   storage = value;
   this.OnPropertyChanged(propertyName);
   return true;
 }
 
 private void OnPropertyChanged(
   [System.Runtime.CompilerServices.CallerMemberName] 
   string propertyName = null)
 {
   var eventHandler = this.PropertyChanged;
   if (eventHandler != null)
   {
     eventHandler(this,
       new System.ComponentModel.PropertyChangedEventArgs(propertyName));
   }
 }
#endregion

 それでは、PropertyChangedイベントを発火させる部分を作りましょう。イベントをテストするコードは、ちょっと工夫が必要です。テストメソッド中でイベントハンドラーをセットし、イベントの発火を待ちます。発火したら、イベントハンドラー内でそのことをメンバー変数に記録し、元のテストメソッドでメンバー変数を検査します。しばらく待っても発火しなかったらREDにします。前回紹介したように、asyncawaitを使ったテストが簡単に書けるようになったので、このようなテストも比較的簡単に書くことができます。

テストコード: PropertyChangedイベントを発火させるテスト
[TestMethod]
[Timeout(100)]
public async Task PropertyChangedTest02_違う数字に変えると2回発火する()
{
  // ファイル冒頭に using System.Threading.Tasks; を追加

  FizzBuzzViewModel vm = new FizzBuzzViewModel();

  // テスト用のイベントハンドラーをアタッチ
  vm.PropertyChanged += vm_PropertyChanged01;

  // Numberプロパティを変更する(これでイベントが発火するはず)
  vm.Number = "15";

  // イベント発生を待機
  while (!_isPropertyChanged01NumberSuccess && _propertyChanged01Fail == null)
    await Task.Delay(10);
  while (!_isPropertyChanged01WordSuccess && _propertyChanged01Fail == null)
    await Task.Delay(10);

  // 想定通りのイベントが発生しなかったら、タイムアウトでREDになる

  // 想定外のプロパティ名で発火していたらRED
  if (_propertyChanged01Fail != null)
    throw _propertyChanged01Fail;
}

bool _isPropertyChanged01NumberSuccess;
bool _isPropertyChanged01WordSuccess;
System.Exception _propertyChanged01Fail;

void vm_PropertyChanged01(object sender,
                          System.ComponentModel.PropertyChangedEventArgs e)
{
  switch (e.PropertyName)
  {
    case "Number":
      _isPropertyChanged01NumberSuccess = true;
      break;
    case "Word":
      _isPropertyChanged01WordSuccess = true;
      break;
    default:
      _propertyChanged01Fail
        = new AssertFailedException("想定外のプロパティ名:" + e.PropertyName);
      break;
  }
}

 製品コードのほうは、先ほど追加したヘルパーメソッドを利用して、Numberプロパティのsetterを次のように変更するだけです。

製品コード: PropertyChangedイベントを発火させる
public string Number
{
  get { return _number; }
  set 
  {
    //_number = value;
    SetProperty<string>(ref _number, value);

    //_word = GetWord();
    SetProperty<string>(ref _word, GetWord(), "Word");
  }
}

Nextメソッド/Previousメソッド

 続いては2つのコマンド(GoNextCommandGoPreviousCommand)を作りたいところですが、コマンドを作るのは複雑な作業です。いっぺんに作るのではなく、まずはコマンドで実行される処理の部分から作っていきましょう。

 それぞれのコマンドで実行される処理を、Nextメソッド/Previousメソッドとします。これらのメソッドはpublicでよいでしょう。スペックは次のようになります。

Nextメソッドのスペック
作るもの Nextメソッド
置き場所 FizzBuzzViewModelクラス
引数 (なし)
返値の型 void
反応
Number
プロパティ
反応
1以上の数字 Numberが1増える
それ以外の文字列 (変化なし)
※ オーバーフローは、後ほどCanExecuteプロパティで制限するので、ここでは気にしない
Previousメソッドのスペック
作るもの Previousメソッド
置き場所 FizzBuzzViewModelクラス
引数 (なし)
返値の型 void
反応
Number
プロパティ
反応
1以上の数字 Numberが1減る
それ以外の文字列 (変化なし)
※ 1未満になるのは、後ほどCanExecuteプロパティで制限するので、ここでは気にしない

 これも前述の戦略2.ですね。メソッドを呼び出すと状態が変わる。その状態はスペック的に外部から観測可能。次のようなテストコードと製品コードになります。

テストコード: Nextメソッド/Previousメソッドのテスト
[TestMethod]
public void NextTest01_数字のとき()
{
  FizzBuzzViewModel vm = new FizzBuzzViewModel();
  vm.Number = "4";
  vm.Next();
  Assert.AreEqual<string>("5", vm.Number);
}

[TestMethod]
public void NextTest02_数字以外の文字列のとき()
{
  FizzBuzzViewModel vm = new FizzBuzzViewModel();
  vm.Number = "x";
  vm.Next();
  Assert.AreEqual<string>("x", vm.Number);
}


[TestMethod]
public void PreviousTest01_数字のとき()
{
  FizzBuzzViewModel vm = new FizzBuzzViewModel();
  vm.Number = "4";
  vm.Previous();
  Assert.AreEqual<string>("3", vm.Number);
}

[TestMethod]
public void PreviousTest02_数字以外の文字列のとき()
{
  FizzBuzzViewModel vm = new FizzBuzzViewModel();
  vm.Number = "x";
  vm.Previous();
  Assert.AreEqual<string>("x", vm.Number);
}
製品コード: Nextメソッド/Previousメソッドを実装
public void Next()
{
  int n = this.GetNumber();
  if (n > 0)
    this.Number = (n + 1).ToString();
}

public void Previous()
{
  int n = this.GetNumber();
  if (n > 0)
    this.Number = (n - 1).ToString();
}
// ここでthis.Numberではなく、メンバー変数_numberに代入するコードを書いてしまった場合は、
// Next/Previous呼び出しでもPropertyChangedイベントが発火するというテストを書く

次のページ
GoNextCommand/GoPreviousCommand

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

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

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

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

メールバックナンバー