TDD Boot Camp Tokyo
for C++ 2014-01 補講
@imagire
TDDで気になること
• どこまでテストをすれば良いの?
• 何をテストすれば良いの?

– テストしやすい物やテストしにくい物は?
より良いテストをするために
• テスト手法
• テスト技法
より良いテストをするために
• テスト手法
• テスト技法
4フェーズテストパターン
•

簡潔・読みやすい・きちんと構成されたテストを作ることができる
– テストをきれいに保つには、このパターンで作るのが良い
namespace UnitTest1
{
TEST_CLASS(TestClassName)
{
public:
TEST_METHOD(TestMethodName) {
// Set up – 準備
TestClass sut = new TestClass();
// Exercise – 実行
int actual = sut.calc(0);
// Verify – 検証
int expected = 0;
Assert::AreEqual (expected, actual);
// TearDown – 後処理
delete sut;
}
};
}
Microsoft.VisualStudio.TestTools.CppUnit
TestFramework の構成
namespace UnitTest1

TEST_MODULE_INITIALIZE(methodName)
TEST_CLASS (className1)
TEST_METHOD_INITIALIZE(methodName#)
TEST_METHOD (methodName1)
TEST_METHOD (methodName2)
…
TEST_METHOD_CLEANUP (methodName#)
TEST_CLASS (className2)
…

}

TEST_MODULE_CLEANUP(methodName)
テストしやすいシステム
• 副作用が無い

– オブジェクトの状態が変化しない(書込みしない)

• 同じ状態・パラメータなら同じ結果となる
– ランダム性を持たない
入力

出力

System Under Test
副作用があるシステム
• Ex. リストのpush_backのテスト(データを追加)
– 上手く実行されたかどうかは、その後のオブ
ジェクトの状態を調べて判断
// setup
std::list<std::string> list;
// exercise
list.push_back(“hello world!”);
// verify
int actual_size = list.size();
std::string actual_text = list.back();
Assert::AreEqual (1, actual_size);
Assert::AreEqual (“hello world!”, actual_text.c_str());

入力
出力
調査

System Under Test
更新
同じ結果とならないテスト
• 別のシステムが関与している場合が多い

– テストの時は都合の良い結果になる関数に差替
入力

System Under Test

呼出
応答

Rand()

出力

本番時
入力

出力

本番用
Rand()

System Under Test
テスト時

テスト用
Rand()
テストダブル
テストダブルの作り方例
• インターフェイスと実装を分離しテスト時
に実装クラスを差し替える

入力

System Under Test

呼出

Rand() 本番用
Rand()

出力

応答

Rand() テスト用
テストダブルの作り方例
• インターフェイスと実装を分離しテスト時
に実装クラスを差し替える
– テスト対象のクラス自体にテストのための仕組
みの埋め込み
• 煩わしい

– テストコードから差し替えられる仕組みの作成
• 堅牢性の低下

入力

System Under Test

呼出

Rand() 本番用
Rand()

出力

応答

面倒くさい!

Rand() テスト用
テストダブルのライブラリ
• オブジェクトを差し替えてのテスト

– 上手くつかえば不要な依存関係がなくなり、テスト
が高速で確実
•
•
•
•
•
•

ハードウェアの依存性をなくす
難しい入力を再現
時間のかかる外部システムのアクセスを高速化
時刻など変化しうる依存性の排除
開発中の外部システムの依存を排除
設定困難な環境への依存性の排除

– 作成が大変で退屈。バグが起きうる。

• 簡単に作成するための仕組み

– Google C++ Mocking Framework, CppUMock,
Boost.Test
テストダブルのバリエーション
• テストダミー

– リンクエラーを起こさないための簡単な実装

• テストスタブ

– 現在のテストケースの指示通りに値を返す

• テストスパイ

– 正しいパラメータが渡されたか記録して検証する

• モックオブジェクト

– 呼び出された関数・順序・パラメータが正しいか記録・
検証する。複数の呼び出しに対応

• フェイク オブジェクト

– 省略された実装。直ぐに他の実装を行う為や設定が面
倒・時間がかかる部分を置き換える

• フェイク爆弾

– 呼び出されるとテストを失敗させる。通られると困る場
所のテスト
より良いテストをするために
• テスト手法
• テスト技法
テストの書き方が分かっても…
• どれだけテストをしないといけないの?
– テストしなければバグは見つからない

32bit =
4,294,967,295通り
基本的なテストの方針
少ない手間で早く沢山危険なバグを検出する
• テストしなければバグは見つからない

– もれなくテストを行う
– もれなくテストを行うためには、思いつきでテストを上げてはいけない

• 単調なテストを物量に任せてこなしても検出率は上がらないし疲労するだけ

• 大事なところをテストする

– もっともバグを検出しやすいテスト技法を用いる
– バグが起きてはいけない順にテストを行う
テスト容易性を考慮して
アプリケーションの設計を行う
テストのしやすさを考えておけばアプリも良い設計になるはず
• 操作性:

– ソフトウェアがうまく動けば動くほどテストはどんどん効率的になる

• 観測性:

– 見えるものしかテストできない

• 制御性:

– ソフトウェアをうまくコントロールすれば、テストを自動化し最適化
できる

• 分解性:

– テストの適用範囲を制限することにより、より速やかに問題を切り分
け手際よく再テストを行える

• 単純性:

– テストする項目が少なければ少ないほどテストを速やかに行える

• 安定性:

– 変更が少なければ少ないほど、テストへの障害が少なくなる

• 理解容易性:

– より多くの情報があれば、それだけ手際よくテストが行える
テストの設計時に気を付けること
• テストが爆発する原因は組み合わせ

– テストする項目が無限にあると主張する人の多くが、
テストの際に何を列挙すれば良いかわかっていない
– テストの際には、考慮すべき項目をはじめに明示してから
組み合わせるべき

• 組み合わせそのものでなく、理解不足が原因の場合が多々ある

• 質の高いテストの特徴
–
–
–
–
–

不具合を検出できる可能性が高い
重複がない
最善である
単純すぎず、複雑過ぎない
検出の方法が明記してあること

テストを書き出した時は、これら特徴を備えているか見直してみよう!
テストケース設計
• テストの効率を上げるために、最低限のテ
ストでもれなくテストするには技法も大切
–
–
–
–
–
–

同値クラステスト
境界値テスト
デシジョンテーブル
ペア構成テスト
制御パステスト
…
同値クラステスト
• パラメータの値が多くの値を取り扱う場合でも、
実際には幾つかのグループに分けられる
– ex: イベントの受付け
•
•
•
•
•

~ 9:45: 受付されない
9:45 ~10:00: 受付可能
10:00 ~11:00: 遅刻対応
11:00 ~18:00: 追い返し
18:00 ~: 出品・閉会

受付されない

受付
可能

遅刻
対応

追い返し

閉会

• 各範囲内の5つの時間を調べれば、それぞれの機能が働いているか調べられる
– 1秒おきに検証する必要はない

– 同値クラス内のあるテストケースで欠陥が検出された場合に、同じ同値クラス内のど
のテストケースでも同じ欠陥が検出される場合に有効
• 非常に少ない数で状況を抑える事ができる
• 境界時間を間違えていた場合には無力
境界値テスト
• 境界でバグは発生しやすい
– 境界値前後で判定が変わる

• 前後の値以外では極端な変化は起きないので、問題は出にくい
• 境界値前後で値が変わらなければ逆に境界値の設定がおかしい
と判断しやすい

– 不等号を判断し間違えることがある。だって人
間だもの
• if (etime<now)
• if (!(etime>now))
境界値テスト
• 境界値のすぐ上と下をとる
9:44 9:45 9:45 9:59 10:0 10:0 10:5 11:0 11:0 17:5 18:0 18:0
:59 :00 :01 :59: 0:00 0:01 9:59 0:00 0:01 9:59 0:00 0:01
受付 前

中

中

中

中

中

中

中

後

後

後

後

研修 前

前

前

前

中

中

中

中

中

中

後

後

9:45

①

②

18:00

11:00

10:00

③

④

受付時間
研修時間
⑤
デシジョンテーブル:
テストケースを減らすには
• 条件の集合によって複雑なビジネスルール
を表現する
– Ex: 来場時の条件によって、研修に参加できる
か決める
ルール
1

ルール
2

ルール
3

ルール
4

ルール
5

ルール
6

ルール
7

ルール
8

ルール
9

受付

前

前

前

中

中

中

後

後

後

研修

前

中

後

前

中

後

前

中

後

○

?

?

○

△

?

?

×

×

条件

アクション
参加
デシジョンテーブルの圧縮
• ?をどうでも良いとみなすと、受付前/後は研修の状況によら
ず同じアクションをすると見なせる →1つにまとめられる
ルール1

ルール2

ルール3

ルール4

ルール5

ルール6

ルール7

ルール8

ルール9

条件
受付

前

前

前

中

中

中

後

後

後

研修

前

中

後

前

中

後

前

中

後

○

?

?

○

△

?

?

×

×

アクション
参加

ルール1

ルール4

ルール5

ルール6

ルール7

受付

前

中

中

中

後

研修

DC(Don’t
Care)

前

中

後

DC(Don’t
Care)

○

○

△

?

×

条件

アクション
参加

・各ルールに関してテストケースを少なくとも1つ作成する
・実装の指針にはなるが、圧縮したものでテストケースが尽くされたと考えるのは危険(実装漏れが起きうるので
ペア構成テスト
不具合について良く知られている事
• 3つ以上の特別な組み合わせによって
はじめて起きる不具合は少ない
– ほとんどの欠陥が機能が単に動かないか、
特定のペアの場合に起きるようである
– テストの数を効率よく減らすことができる(が、完璧で
はない)
ペア構成テスト
• パラメータのバリエーションについて、全てのパラメータ
の場合をテストするのではなく、2つのパラメータに注目し
た際にすべてのパラメータの組が現れるようにテストケー
スを作成する
– 任意のパラメータの組で、全てのパラメータの場合が現れるように
する
– 全ての場合をテストするのと比べて対数のオーダーでしか増えない
Ex: 2つの値を取る3つのパラメータの組み合わせ
• 全てのケースを網羅:23=8
プリン
• ペア構成テスト:4
– 全ての2つの列について、
全バリエーションが存在する

4回の注文で、3つのトッピングの
全ての2つの組み合わせは試せる!

ケース1

○

ケース2

クッキー

チョコ
シロップ

○

○

○

ケース3
ケース4

○
○
ペア構成テストの作成方法
• 直行表

– 変数を識別する
– 各変数が取りうる値の数を求める
– 列の数が変数の数と同じ以上で、各変数の取りうる値の数に対応する値が、
列に含まれるような直行表を公開されている情報から見つける
– 直行表にテスト問題を割りつける
– テストケースを作成する

• 全ペアアルゴリズム法

– サイトからプログラムを拾ってくる
– 変数が取りうる値をexcelで作成し、テキストで保存する
– プログラムを実行する
状態遷移図に関するテストを考えよう
• とある状態遷移図
キャンセル

B
A

C

E

H

G

J

F

D
キャンセル

I
制御パステスト
•

命令網羅(Statement coverage(SC)(C0基準)
–

•

判定条件網羅(Decision coverage(DC)) /分岐網羅(Branch Coverage(BC))(C1基準)
–

•

–

C1基準を満たすようにCCの組み合わせを選ぶ

複数条件網羅(Multiple condition coverage(MCC)) (C2基準)
–

•

コード上のすべての条件判定において、1つの条件判定の結果が、真になる場合を最低1回、偽にな
る場合を最低1回 実行する
「A or B」のような判定で、C1なら、「A or B」の結果のtrueかfalseを試すが、CCでは、Aがtrueか
falseとBがtrueかfalseの場合を実行する。したがって、「true or false」、「false or tru」と選ぶと、
CCの条件は満たすが、C1基準は満たさないような場合も起きうる

判定条件/条件網羅(Decision/Condition coverage(DC/CC))
–

•

コード上のすべての条件判定を一度は実行する

条件網羅(condition coverage(CC))
–

•

コード上のすべての命令を最低一度は実行する

コード上のすべての条件判定の組み合わせを網羅する

経路網羅(Path Coverage(PC)) (C∞基準)
–

コード上のすべての経路を最低1回は実行する
C0基準
• コード上のすべての状態を最低一度は訪れる
キャンセル

B
A

C

E

H

G

J

F

D
キャンセル

I
C1基準
• コード上のすべての分岐を一度は実行する
キャンセル

B
A

C

E

H

G

J

F

D
キャンセル

I
より高い基準
• ループが発生するので、無限にテストケー
スが生じる
キャンセル

B
A

C

E

H

G

I

F

D
キャンセル

J
ありがとうございました
• 参考資料

– 「ソフトウェアテスト技術」、経営情報学会
第3回 情報システム工学研究部会、西 康晴
– 下記書籍

TDD Boot Camp Tokyo for C++ 2014-01 補講