わんくま同盟 名古屋勉強会 #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 7
今年のテーマ
•テスト ファーストに必要なスキルがある。
それは…
メソッドの外部設計
(external design)
わんくま同盟 名古屋勉強会 #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 13
1メソッドだけ見ていては
外部設計が上手く書けないパターン
今日やるのは…
わんくま同盟 名古屋勉強会 #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 24
演習: 信号機の状態
• 十字路交差点の信号機
矢印信号は無し。深夜の点滅も無し。
• 取りうる状態を全て書き出せ (3分)
⇨東行
⇦西行
⇧北行
⇩南行
わんくま同盟 名古屋勉強会 #33 25
筆記用具のご用意を♪
十字路交差点の信号機
全部が赤になるタイミングがあるよ~
演習タイム
信号機の状態を書き出してみる
わんくま同盟 名古屋勉強会 #33 26
回答例
• ポイントは、状態番号 3 と 6 (全部が赤) を同一と
みなすか、別々の状態であるとみなすか
※ 同一とみなすと、次のトリガーで 4 と 1 のどっちの状
態に遷移するのかを表す状態も持たねばならない
状態
番号 ⇨東行 ⇦西行 ⇩南行 ⇧北行
1 青 青 赤 赤
2 黄 黄 赤 赤
3 赤 赤 赤 赤
4 赤 赤 青 青
5 赤 赤 黄 黄
6 赤 赤 赤 赤
わんくま同盟 名古屋勉強会 #33 27
ご清聴ありがとうございました

わんくま名古屋#33(20141115) TDD道場#21

  • 1.
    わんくま同盟 名古屋勉強会 #331 【状態を持つクラス】 1メソッドだけ見ていては 上手く外部設計できないパターン TDD 道場 #21 BluewaterSoft 2014/11/15 biac
  • 2.
    わんくま同盟 名古屋勉強会 #332 スピーカー紹介: biac as 山本 康彦 • 宇宙世紀以前の生まれ スプートニク1号より3ヶ月ほど前 • 最初は HONDA クルマの設計/研究を10年くら いやってた • 今は BluewaterSoft を名乗ってアプリ開発とか技術 解説記事とか • 「NUnitの全貌」⇒ CodeZine 2012/4
  • 3.
    わんくま同盟 名古屋勉強会 #333 【CM】 C#で始めるテスト駆動開発入門 • CodeZine 連載再開♪ • #08「ユニバーサル Windowsアプリのユニッ トテスト(前編)」 • #09「ユニバーサル Windowsアプリのユニッ トテスト(後編)」 • #10「状態を持つクラスを テストファーストする」
  • 4.
    わんくま同盟 名古屋勉強会 #334 TDD = テスト ファースト + リファクタリング •テスト ファースト: RED と GREEN の繰り返し •リファクタリング: GREEN を維持したまま実装を 改善 失敗するはずのユニット テストを1つ書き、 失敗することを確認 (=RED) ユニット テストに通るだけの実装を追加し、 成功することを確認 (=GREEN)
  • 5.
    わんくま同盟 名古屋勉強会 #335 TDD 3原則 by Robert C Martin •ArticleS.UncleBob.TheThreeRulesOfTdd (2005) より。 ※ 実質は「テスト ファースト 3原則」 1. 失敗するユニットテストを成功させるためにしか、 プロダクトコードを書いてはならない。 2. 失敗させるためにしか、ユニットテストを書いて はならない。コンパイルエラーは失敗に数える。 3. ユニットテストを1つだけ成功させる以上に、プロ ダクトコードを書いてはならない。
  • 6.
    わんくま同盟 名古屋勉強会 #336 TDD MANTRA • 『Test-Driven Development: By Example』からの引用 • テスト駆動開発において 我々は、 ・自動テストが失敗してい る場合に限り、 新しい コードを書く ・重複を取り除く • OneDrive で公開 http://1drv.ms/1uz3Z2P
  • 7.
    わんくま同盟 名古屋勉強会 #337 今年のテーマ •テスト ファーストに必要なスキルがある。 それは… メソッドの外部設計 (external design)
  • 8.
    わんくま同盟 名古屋勉強会 #338 メソッドの外部設計 ➡ テスト ファースト 外部設計 external design ・外観 メソッドのシグネチャを決定 ・反応 メソッドの、引数に対する返値や (外から見える) 副作用。 テスト ファースト 外部設計からひとつ取り出して、 ユニット テストに変換 ⇒ それに対応する実装をしたら、次へ OOP風にいえば「メッセージに対する (外部から見た) 振る舞い」
  • 9.
    わんくま同盟 名古屋勉強会 #339 外部設計 ➡ 内部設計 •メソッドの外部設計 ⇩ メソッドの内部設計 (=実装) •テスト ファーストとは、 外部設計 ⇒ 内部設計 という当たり前の手順を、 効率よくやっているだけに過ぎない。
  • 10.
    わんくま同盟 名古屋勉強会 #3310 TDD における外部設計 •外部設計の方法は規定なし TDD の考案者 Kent Beck の本では、頭 の中だけだったり、To Do リストに書 き出したり •でも、大事 !! Kent Beck レベルでは、たぶん… 「そんなん、できてるだろ、常考!」 でも日本の多くの開発者は、そんな訓 練をやってない
  • 11.
    わんくま同盟 名古屋勉強会 #3311 前回は… •内部設計を想定して、入力の範囲を分 割する ・例題: 「回文候補生」 ・演習: ソート •ブラックボックスとしての同値分割だけでは上手 くテストファーストできないパターン •内部設計を想定して入力値の範囲をさらに分割
  • 12.
    わんくま同盟 名古屋勉強会 #3312 前回の回答例 (ソート) • 外観 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} 比較と交換を二巡以上 (交換不要になるまで) 行う ブラックボックスとしての 同値分割だけでは、このス ペックは出てこない
  • 13.
    わんくま同盟 名古屋勉強会 #3313 1メソッドだけ見ていては 外部設計が上手く書けないパターン 今日やるのは…
  • 14.
    わんくま同盟 名古屋勉強会 #3314 状態を持つクラス •状態: ここでは、クラスのインスタンス が保持しているデータ。一般的にはメ ンバー変数のこと。 ※ デザインパターンの一つである「State パター ン」にいう状態とは異なる •状態を持つクラスのメソッドは、メ ソッド単体でスペックを記述できない ことが多い
  • 15.
    わんくま同盟 名古屋勉強会 #3315 状態を持つクラスの例: Stack •.NET の Stack<T> クラス •例えば、その Push メソッドのスペッ ク •出力 (メソッドの返値) が無いので、こ のメソッドだけでは入出力表を書けな い stack.Push(item) このメソッドは、item をスタックに積む
  • 16.
    わんくま同盟 名古屋勉強会 #3316 状態を持つクラスの例: Push はどう表現する? •前提: 生の状態は、可能な限り見せたくない! ⇩ •外部設計として、状態を観測できるメソッ ドやプロパティはないか? ⇩ •PopメソッドやPeekメソッドがある! Push とそれらを組み合わせれば、(たぶん) テストが書ける
  • 17.
    わんくま同盟 名古屋勉強会 #3317 状態を持つクラス: テストファースト戦略 •1. スペック上、状態へのアクセスが必要な 場合: プロパティそのものの入出力を定義できる •2. スペック上、状態の変化が観測可能な場 合: 状態変更と観測のメソッドをペアで設計 •3. スペック上、状態が観測できない場合: スコープを変えたり、プローブを挿入した り…
  • 18.
    わんくま同盟 名古屋勉強会 #3318 例題: FizzBuzz の画面モデル •詳しくは、CodeZine「C#で始めるテスト駆 動開発入門(10)」を参照
  • 19.
    わんくま同盟 名古屋勉強会 #3319 例題: 戦略 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); }
  • 20.
    わんくま同盟 名古屋勉強会 #3320 例題: 戦略 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); }
  • 21.
    わんくま同盟 名古屋勉強会 #3321 例題: 戦略 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
  • 22.
    わんくま同盟 名古屋勉強会 #3322 例題: 戦略 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
  • 23.
    わんくま同盟 名古屋勉強会 #3323 ここまでの まとめ • 前提: クラス内部の状態はで きるだけ見せたくない • スペック上、状態の観測が可 能な場合が多い • 本当に観測不能な場合: ・プローブ ・public 化 (非推奨) ・リフレクション (面倒) ※ 下2つは内部設計の変更に伴う影 響範囲が大きくなりがち
  • 24.
    わんくま同盟 名古屋勉強会 #3324 演習: 信号機の状態 • 十字路交差点の信号機 矢印信号は無し。深夜の点滅も無し。 • 取りうる状態を全て書き出せ (3分) ⇨東行 ⇦西行 ⇧北行 ⇩南行
  • 25.
    わんくま同盟 名古屋勉強会 #3325 筆記用具のご用意を♪ 十字路交差点の信号機 全部が赤になるタイミングがあるよ~ 演習タイム 信号機の状態を書き出してみる
  • 26.
    わんくま同盟 名古屋勉強会 #3326 回答例 • ポイントは、状態番号 3 と 6 (全部が赤) を同一と みなすか、別々の状態であるとみなすか ※ 同一とみなすと、次のトリガーで 4 と 1 のどっちの状 態に遷移するのかを表す状態も持たねばならない 状態 番号 ⇨東行 ⇦西行 ⇩南行 ⇧北行 1 青 青 赤 赤 2 黄 黄 赤 赤 3 赤 赤 赤 赤 4 赤 赤 青 青 5 赤 赤 黄 黄 6 赤 赤 赤 赤
  • 27.
    わんくま同盟 名古屋勉強会 #3327 ご清聴ありがとうございました