Upload
yasuhiko-yamamoto
View
271
Download
2
Embed Size (px)
Citation preview
わんくま同盟 名古屋勉強会 #33 1
【状態を持つクラス】
1メソッドだけ見ていては
上手く外部設計できないパターン
TDD 道場 #21
BluewaterSoft 2014/11/15 biac
わんくま同盟 名古屋勉強会 #33 2
スピーカー紹介: biac as 山本康彦
• 宇宙世紀以前の生まれスプートニク1号より3ヶ月ほど前
• 最初は HONDAクルマの設計/研究を10年くらいやってた
• 今は BluewaterSoftを名乗ってアプリ開発とか技術解説記事とか
• 「NUnitの全貌」⇒CodeZine 2012/4
わんくま同盟 名古屋勉強会 #33 3
【CM】 C#で始めるテスト駆動開発入門
• CodeZine 連載再開♪
• #08「ユニバーサルWindowsアプリのユニットテスト(前編)」
• #09「ユニバーサルWindowsアプリのユニットテスト(後編)」
• #10「状態を持つクラスをテストファーストする」
わんくま同盟 名古屋勉強会 #33 4
TDD = テストファースト + リファクタリング
•テスト ファースト: RED と GREEN の繰り返し
•リファクタリング: GREEN を維持したまま実装を改善
失敗するはずのユニット テストを1つ書き、失敗することを確認 (=RED)
ユニット テストに通るだけの実装を追加し、成功することを確認 (=GREEN)
わんくま同盟 名古屋勉強会 #33 5
TDD 3原則 by Robert C Martin
•ArticleS.UncleBob.TheThreeRulesOfTdd (2005) より。※ 実質は「テスト ファースト 3原則」
1. 失敗するユニットテストを成功させるためにしか、プロダクトコードを書いてはならない。
2. 失敗させるためにしか、ユニットテストを書いてはならない。コンパイルエラーは失敗に数える。
3. ユニットテストを1つだけ成功させる以上に、プロダクトコードを書いてはならない。
わんくま同盟 名古屋勉強会 #33 6
TDD MANTRA
• 『Test-Driven Development: By Example』からの引用
• テスト駆動開発において我々は、・自動テストが失敗している場合に限り、 新しいコードを書く・重複を取り除く
• OneDrive で公開http://1drv.ms/1uz3Z2P
わんくま同盟 名古屋勉強会 #33 8
メソッドの外部設計➡テストファースト
外部設計 external design
・外観メソッドのシグネチャを決定
・反応メソッドの、引数に対する返値や (外から見える) 副作用。
テストファースト外部設計からひとつ取り出して、ユニット テストに変換⇒ それに対応する実装をしたら、次へ
OOP風にいえば「メッセージに対する (外部から見た) 振る舞い」
わんくま同盟 名古屋勉強会 #33 9
外部設計➡内部設計
•メソッドの外部設計⇩
メソッドの内部設計 (=実装)
•テスト ファーストとは、外部設計 ⇒ 内部設計という当たり前の手順を、効率よくやっているだけに過ぎない。
わんくま同盟 名古屋勉強会 #33 10
TDD における外部設計
•外部設計の方法は規定なしTDD の考案者 Kent Beck の本では、頭の中だけだったり、To Do リストに書き出したり
•でも、大事 !!Kent Beck レベルでは、たぶん…「そんなん、できてるだろ、常考!」
でも日本の多くの開発者は、そんな訓練をやってない
わんくま同盟 名古屋勉強会 #33 11
前回は…
•内部設計を想定して、入力の範囲を分割する・例題: 「回文候補生」・演習: ソート
•ブラックボックスとしての同値分割だけでは上手くテストファーストできないパターン
•内部設計を想定して入力値の範囲をさらに分割
わんくま同盟 名古屋勉強会 #33 12
前回の回答例 (ソート)
• 外観static IList<int> WankumaSort(IList<int>)ただし、引数と返値は同一インスタンス
• 応答バブル ソートを想定
入力数列 出力 (返値)
null null
空リスト 引数と同一のインスタンス (以下同じ)
1つ以上 昇順にソートされた数列
詳細パターン 例示 想定される内部操作
交換無し {1, 2} → {1, 2} 比較だけ一巡して終了
1巡だけ交換 {5, 3} → {3, 5} 比較と交換を一巡だけ行う
2巡以上交換 {7, 5, 3} → {3, 5, 7} 比較と交換を二巡以上 (交換不要になるまで) 行う
ブラックボックスとしての同値分割だけでは、このスペックは出てこない
わんくま同盟 名古屋勉強会 #33 14
状態を持つクラス
•状態: ここでは、クラスのインスタンスが保持しているデータ。一般的にはメンバー変数のこと。※ デザインパターンの一つである「State パターン」にいう状態とは異なる
•状態を持つクラスのメソッドは、メソッド単体でスペックを記述できないことが多い
わんくま同盟 名古屋勉強会 #33 15
状態を持つクラスの例: Stack
•.NET の Stack<T> クラス
•例えば、その Push メソッドのスペック
•出力 (メソッドの返値) が無いので、このメソッドだけでは入出力表を書けない
stack.Push(item)このメソッドは、item をスタックに積む
わんくま同盟 名古屋勉強会 #33 16
状態を持つクラスの例: Push はどう表現する?
•前提: 生の状態は、可能な限り見せたくない!⇩
•外部設計として、状態を観測できるメソッドやプロパティはないか?⇩
•PopメソッドやPeekメソッドがある!Push とそれらを組み合わせれば、(たぶん)テストが書ける
わんくま同盟 名古屋勉強会 #33 17
状態を持つクラス: テストファースト戦略
•1. スペック上、状態へのアクセスが必要な場合:プロパティそのものの入出力を定義できる
•2. スペック上、状態の変化が観測可能な場合:状態変更と観測のメソッドをペアで設計
•3. スペック上、状態が観測できない場合:スコープを変えたり、プローブを挿入したり…
わんくま同盟 名古屋勉強会 #33 18
例題: FizzBuzz の画面モデル
•詳しくは、CodeZine「C#で始めるテスト駆動開発入門(10)」を参照
わんくま同盟 名古屋勉強会 #33 19
例題: 戦略 1 - Number プロパティ
•Numberプロパティは、スペック上 getter / setter を持つ
•そのままテストファースト出来る[TestMethod]public void NumberプロパティTest01_既定値は1(){
// FizzBuzzは1から始まるので、規定値は1とするFizzBuzzViewModel vm = new FizzBuzzViewModel();Assert.AreEqual<string>("1", vm.Number);
}
[TestMethod]public void NumberプロパティTest02_書き込み可能(){
FizzBuzzViewModel vm = new FizzBuzzViewModel();vm.Number = "X"; //文字列なら何でも受け付けるものとするAssert.AreEqual<string>("X", vm.Number);
}
わんくま同盟 名古屋勉強会 #33 20
例題: 戦略 2 - Next メソッド
•Next メソッドは値を返さないが、それによる状態変化は Number プロパティで観測可
[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);
}
わんくま同盟 名古屋勉強会 #33 21
例題: 戦略 3 - GoNextCommand (1/2)
•コマンドの実行結果は Next メソッドが呼び出されること ← 外部から観測不可※ コマンドから見て Next メソッドが何をするかは、どうでもいい
#if DEBUG // プローブを使うテストはデバッグビルド時のみ有効[TestMethod]public void GoNextCommandTest01(){
// 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);
}#endif
わんくま同盟 名古屋勉強会 #33 22
例題: 戦略 3 - GoNextCommand (2/2)
•製品コード側のプローブNext メソッドが呼び出された回数を調査するためのプローブの例
public void Next(){#if DEBUG
test_NextCount++;#endif
int n = this.GetNumber();if (n > 0)this.Number = (n + 1).ToString();
}
#if DEBUG#region テスト時だけ利用するコードpublic int test_NextCount { get; set; }#endregion#endif
わんくま同盟 名古屋勉強会 #33 23
ここまでのまとめ
• 前提: クラス内部の状態はできるだけ見せたくない
• スペック上、状態の観測が可能な場合が多い
• 本当に観測不能な場合: ・プローブ・public 化 (非推奨)・リフレクション (面倒)※ 下2つは内部設計の変更に伴う影響範囲が大きくなりがち
わんくま同盟 名古屋勉強会 #33 26
回答例
• ポイントは、状態番号 3 と 6 (全部が赤) を同一とみなすか、別々の状態であるとみなすか※ 同一とみなすと、次のトリガーで 4 と 1 のどっちの状態に遷移するのかを表す状態も持たねばならない
状態番号 ⇨東行 ⇦西行 ⇩南行 ⇧北行
1 青 青 赤 赤
2 黄 黄 赤 赤
3 赤 赤 赤 赤
4 赤 赤 青 青
5 赤 赤 黄 黄
6 赤 赤 赤 赤