27
わんくま同盟 名古屋勉強会 #33 1 【状態を持つクラス】 1メソッドだけ見ていては 上手く外部設計できないパターン TDD 道場 #21 BluewaterSoft 2014/11/15 biac

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

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 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

ご清聴ありがとうございました