TDD Boot Camp

古川 剛啓
http://www.flickr.com/photos/utest/5609991407
アジェンダ
1. TDD概論
2. ワークショップ1(Fizz Buzz)
3. 振り返り
注意事項
•

今日は学びの場です。納期とかコストとか関係
ありません。気楽に行きましょう。

•

失敗しても問題ありません。失敗から学ぶこと
は多々あります。どんどん失敗しましょう。

•

何か1つでも持って帰って貰えたら嬉しいです。
プロジェクト終盤でありがちな状況

✓単体テストを省略した
✓いつの間にかテストが壊れている
✓ テストが直ぐ壊れるのでテストするのを
やめた

✓ テストに時間が掛かるのでテストしてな
い

✓そもそもユニットテストなんかしてない
http://www.flickr.com/photos/bagger2009/3630270346
http://www.flickr.com/photos/eivindw/2062037429
http://www.flickr.com/photos/edgarallanbro/7197914274

設計にセーフティネットを掛ける
よく見るV字モデル
要件定義

受入試験

基本設計

システム試験

詳細設計

結合試験

ユニットテスト /
コーディング

ここでやる。
対象はメソッド。
TDDの利点
• テストカバレッジが向上する
• テスタビリティが向上する
• メソッドや構造をシンプルに保つことが出来る
• 書いたコードに自信が持てる
• テストメソッドを読めばメソッドの使い方が分かる
• その他 Twitter #TDDやっててよかったのまとめ
http://togetter.com/li/258537

等
TDDの欠点
• コードを書くのに時間が掛る
• 簡単ではない
• 設計しなくなる

http://www.flickr.com/photos/ismarbadzic/7250079044/
ユニットテストを難しくする幾つかのこと

✓ コードがハードウェアに依存している
✓ データベースやらネットワークに
アクセスして状態が安定しない

✓ クラス間の結合が強くて連鎖的に
インスタンス化する必要がある

✓ GUIばっか
http://www.flickr.com/photos/tcrazyfish/6864732291
http://www.flickr.com/photos/planetschwa/99535218
SOLIDを意識する
S Single Responsibility Principle
単一責任の原則
O Open-Closed Principle
オープン・クローズドの原則
L Liskov Substitution Principle
リスコフの置換原則
I Interface Segregation Principle
インターフェース分離の原則
D Dependency Inversion Principle
依存関係逆転の原則
ちゃんとレイヤー化する
薄く保つ
GUI等

ユーザーインターフェース層
(又はプレゼンテーション層)

ドメイン層
(又はモデル層)

インフラストラクチャ層

デバイスへのI/F等
DB

HDD
TDDをやると本当は何がいいの?

http://www.flickr.com/photos/mikedefiant/429402831
今までのプログラミング
(Debug Later Programming)
Td

Tfind

Tfix
時間

バグ混入

バグ顕在化

バグ発見

バグFix

バグが混入してから顕在化するまでに時間が掛かると、
バグがある場所を探すまでに時間が掛かる
テスト駆動開発
(Test Driven Development)
Td

Tfind

Tfix
時間

バグ混入

バグ顕在化 バグ発見

バグFix

バグ混入から顕在化するまでの時間を短くし、
バグがある場所を探すまでの時間を短縮する
ユニットテストフレームワークを使う
JUnit, Google Test, CppUTest・・・
Mockフレームワークを使う
jMock, GMock・・・

http://www.flickr.com/photos/kazk/198640938
と言うことで・・・
Google Testをインストールしてみよう
• 以下のサイトからGoogle Mockのソースを
ダウンロードする(Google Testのソースも
含まれている)
http://code.google.com/p/googlemock/
• zipファイルを適当な場所に解凍する
• READMEに従って、ビルド&インストール
で、実際に始める前に・・・
テストメソッドの構造
 準備
 オブジェクトの生成や状態のセット等を
行う
 実行
 テスト対象メソッドを実行する
 検証
 実行結果を検証し、Red/Greenを判定
する
 後始末
 オブジェクトを破棄する
テストコードのポイント
 良いユニットテストはRepeatable (繰り返し可能、再現可能)
 テストダブルを使いこなす
 外部環境との界面にインターフェイスを作成し、テストダブルで置き換える
 良いユニットテストは独立 (Independent) していなければならない
 後始末を忘れずに行い、テストを独立させる
 static を避け、テストメソッド間の依存関係を断つ
 Assertion Roulette に注意する
 目指すのは「テストメソッド毎にアサーションひとつ」(しかし、やりすぎは禁物)
 カスタムアサーションを使う
 パラメタライズドテスト(Parameterized Test)を使いこなす
 Fragile Test (脆いテスト) に注意する
 テストだけに使う部分の可視性を下げる
 private メソッドを扱いたくなったら要注意
 テストを設計ツールとして使う
 テストコードのノイズを減らす
 日本語テストメソッドを試してみる
 シンプルなコードとテスト失敗時の情報のバランスを考える
これらのポイントを考えながらテストコードを書くことで、テスト容易性を考慮した設計が見えてく
るはずです。
出展: CodeIQ MAGAZINE
https://codeiq.jp/magazine/2013/11/1475/
準備
(1) 先ずは、テスト対象となるクラス/モジュール
を準備する。
(2) テストクラスを準備する。
(3) ビルドするためのMakefileを書く

ツールを準備しました!!
./prepairFiles [--lang=c | -lang=cpp]
[--target=targetName] [--without_Makefile]
className ・・・
準備
hogeディレクトリ下でコマンドを実行すると以下
のファイルが生成される
(classNameは"fuga")
hoge/
|- src/
| |- Makefile
| |- productionSources.mk
| |- productionObjects.mk
| |- testSources.mk
| |- testObjects.mk
| |- fuga.h
| |- fuga.cpp (fuga.c)
|

|
|- test/
|- fugaTest.h
|- fugaTest.cpp
|- testMain.cpp
準備
fuga.h (c++)

fuga.h (c)

#ifndef FUGA_H_
#define FUGA_H_

#ifndef FUGA_H_
#define FUGA_H_

class fuga {
public:
fuga();
virtual ~fuga();
};

#endif

#endif

fuga.cpp
#include "fuga.h"
fuga::fuga() {
}

fuga::~fuga() {
}

fuga.c
#include "fuga.h"
準備
fugaTest.h (c)
#ifndef FUGATEST_H_
#define FUGATEST_H_

#include <gtest/gtest.h>
extern "C" {
#include "fuga.h"
}
class fugaTest : public ::testing::Test {
protected:
void SetUp();
void TearDown();

public:
fugaTest();
virtual ~fugaTest();
};
#endif
準備
fugaTest.h (c++)
#ifndef FUGATEST_H_
#define FUGATEST_H_

#include <gtest/gtest.h>
#include "fuga.h"
class fugaTest : public ::testing::Test {
protected:
fuga* sut;
void SetUp();
void TearDown();
public:
fugaTest();
virtual ~fugaTest();
};
#endif
準備
fugaTest.cpp
#include "fugaTest.h"
fugaTest::fugaTest() {
}
fugaTest::~fugaTest() {
}
void fugaTest::SetUp() {
sut = new fuga(); // c用には無い
}
void fugaTest::TearDown() {
delete sut; // c用には無い
}
TEST_F (fugaTest, testNameIsHere_ChangeThis) {
/* Write a test code here. */
}
準備
ファイルが生成されたら、srcディレクトリに移動
(cd src)して、

make test
--targetに指定した名前+Testの実行ファイルが
ビルドされる。(デフォルトは、「a.outTest」)

main関数が無いからmakeはできないので注意
よく使うアサーション
2つの値を比較する
致命的なアサーション

致命的ではないアサーション

ASSERT_EQ(expected, actual);EXPECT_EQ(expected, actual);
ASSERT_NE(val1, val2);
EXPECT_NE(val1, val2);
ASSERT_LT(val1, val2);
EXPECT_LT(val1, val2);
ASSERT_LE(val1, val2);
EXPECT_LE(val1, val2);
ASSERT_GT(val1, val2);
EXPECT_GT(val1, val2);
ASSERT_GE(val1, val2);
EXPECT_GE(val1, val2);

致命的なアサーション
ASSERT_STREQ(expected_str, actual_str);
ASSERT_STRNE(str1, str2);

検証内容
expected == actual
val1 != val2
val1 < val2
val1 <= val2
val1 > val2
val1 >= val2

致命的ではないアサーション

検証内容

EXPECT_STREQ(expected_str, actual_str);

2つの C 文字列の内容が等しい

EXPECT_STRNE(str1, str2);

2つの C 文字列の内容が等しくない

ASSERT_STRCASEEQ(expected_str, actual_str);EXPECT_STRCASEEQ(expected_str, actual_str); 大文字小文字を無視した場合,2つの
C 文字列の内容が等しい
ASSERT_STRCASENE(str1, str2);

EXPECT_STRCASENE(str1, str2);

大文字小文字を無視した場合,2つの C 文字列
の内容が等しくない
Google Testの使い方
戻り値がある関数をテストする
テストクラス

関数 f()

関数の戻り値を期待値と比較する

例
EXPECT_EQ(1, Factorial(0));
EXPECT_EQ(1, sut->Factorial(0));

(c)
(c++)

注
• c++のstd::stringは、このアサーションでテストする
• cの文字列は、「ASSERT_STREQ」や「EXPECT_STREQ」等を使用する
Google Testの使い方
変数を更新する関数をテストする
テストクラス

関数 f()

変数の値を期待値と比較する

例
Factorial(0);
EXPECT_EQ(1, result);

(c)

sut->Factorial(0);
EXPECT_EQ(1, sut->getResult());

(c++)

変数 v
よく使うアサーション
明示的な失敗
FAIL();

例外アサーション
致命的なアサーション

致命的ではないアサーション

検証内容

ASSERT_THROW(statement, exception_type);EXPECT_THROW(statement, exception_type); statement が与えられた型の例外を投げる
ASSERT_ANY_THROW(statement);
EXPECT_ANY_THROW(statement);
statement が任意の型の例外を投げる
ASSERT_NO_THROW(statement);
EXPECT_NO_THROW(statement);
statement が例外を投げない
Google Testの使い方
例外がある関数をテストする
テストクラス

関数 f()

例外発生の有無や例外の型を期待値と比較する

例
ASSERT_THROW( sut->parseArguments(argc, argv), std::invalid_argument );

注
このアサーションは例外をキャッチする
依って、try/catchで囲んでも期待する動作をしない
Tips
関数の呼び出しをテストする (c)
テストクラス

関数 f()

関数 g()

関数 g()

関数 f()が、関数 g()を正しく呼び出していることを確認する場合、
• 関数 g()をモックモジュールの関数 g()に置き換える
• モックモジュールの関数 g()で引数の値の保存等、必要な処理
を実施
• アサーションにより保存した値等をチェック
注
• 同じ関数を持つオブジェクトファイルをリンクするとエラー(duplicate symbol)が出るので
プロダクションコードはライブラリにしてモックモジュールより後にリンクする
• テスト対象のモジュールが変わったら、再ビルドが必要かも
Tips
privateメソッドをテストする (c++)
テストクラス

テスト対象クラス
+ 関数 f()
# 関数 g()

△
Spyクラス
+ 関数 g()

privateメソッドをどうしてもテストしたい場合、
• privateメソッドをprotectedメソッドに変更する
• テスト対象クラスを継承するSpyクラスを定義し、その中で
publicに変更した同名のメソッドを定義する
• Spyクラスのメソッドは、元のメソッドを呼び出すだけ
注
• privateメソッドをテストする必要性を検討する必要あり
• privateメソッドは、publicメソッドから呼び出されているので何らかの形でテスト済みなハズ
Tips
標準出力への出力内容をテストする (c/c++)
テストクラス

関数 f()

stdout/stderrへの出力をテストしたい場合、
• Google Testに付属の関数を使用する
• stdoutへの出力を変数に保存する
• stdoutへの出力をファイルに保存する

標準出力
Tips

(Google Testに付属の関数を使用する) (c/c++)
テストクラス

関数 f()

標準出力

#include <gtest/internal/gtest-port.h>

TEST_F(aaaTest, stdoutTest) {
testing::internal::CaptureStdout();
sayHello();
ASSERT_STREQ("Hello", testing::internal::GetCapturedStdout().c_str());
}

CaptureStdout()とGetCapturedStdout()の間は、標準出力に出力
されるはずだった文字列は保存され、 GetCapturedStdout()で取
り出すことができる。

標準エラー出力は、
CaptureStderr()とGetCapturedStderr()を使用する。
Tips

(stdoutへの出力を変数に保存する) (c++)
テストクラス

関数 f()

標準出力

std::string CWorkerSpy::output() {
std::stringbuf buf; // リダイレクト先の変数
std::streambuf* oldbuf = std::cout.rdbuf(&buf); // 標準出力のstreamを保存
CWorker::output();
std::cout.rdbuf(oldbuf); // 標準出力のstreamを復帰
return buf.str();
}

• テスト対象クラスのSpyクラスを定義
• アサーションでテストできるようにstringを返すラッパーメソッドを
定義
• 標準出力のstreamを保存
• 元のメソッドを呼び出す
• 標準出力のstreamを復帰
Tips

(stdoutへの出力をファイルに保存する) (c++)
テストクラス

関数 f()

標準出力

void CWorkerSpy::output() {
std::ofstream ofs("output.txt"); // リダイレクト先のファイル
std::streambuf* oldrdbuf = std::cout.rdbuf(ofs.rdbuf()); // 標準出力のstreamを保存
CWorker::output();
std::cout.rdbuf(oldrdbuf);
}

•
•
•
•
•

テスト対象クラスのSpyクラスを定義
ラッパーメソッドを定義
標準出力のstreamを保存
元のメソッドを呼び出す
標準出力のstreamを復帰
Google Testによるパラメータ化テスト
•パラメータ化テストとは、1つのテストメソッド内でテスト条件(引
数や期待値)を変えながらテストするもの。
•限界値分析や同値分割をやる際に有効

•テストクラスを継承すると共にWithParamInterface<T>を実装
•Tには、テスト対象メソッドの引数の型を指定
::testing::WithParamInterface<T>

△

::testing::Test

△
テストクラス

△
パラメータ化テストクラス
Google Testによるパラメータ化テスト
(引数が1つ)
#include <gtest/gtest.h>
#include "../test/hogeTest.h"
class IsEvenParametricTest : public hogeTest,
public ::testing::WithParamInterface<int> {
};

TEST_Pに変更
#include "IsEvenParametricTest.h"

パラメータを取得

TEST_P (IsEvenParametricTest, testNameIsHere_ChangeThis) {
/* Write a test code here. */
EXPECT_EQ(true, sut->isEven(GetParam()));
}

INSTANTIATE_TEST_CASE_P(EvenTest, IsEvenParametricTest,
testing::Values(2, 4)
);

パラメータを準備
Google Testによるパラメータ化テスト
(複数の引数だったり、戻り値もだったり)
#include <gtest/gtest.h>
#include <boost/tuple/tuple.hpp>
#include "../test/hogeTest.h"

複数のパラメータの場合は
tupleを使用

class IsEvenParametricTest2 : public hogeTest,
public ::testing::WithParamInterface< boost::tuple<bool, int> > {
};

#include "IsEvenParametricTest2.h"

パラメータを取得

TEST_P (IsEvenParametricTest2, testNameIsHere_ChangeThis) {
/* Write a test code here. */
const bool expect = boost::get<0>(GetParam());
const int num
= boost::get<1>(GetParam());
EXPECT_EQ(expect, sut->isEven(num));
}
INSTANTIATE_TEST_CASE_P(EvenTest, IsEvenParametricTest2,
testing::Values(
boost::make_tuple(true, 2),
boost::make_tuple(false, 3))
);

パラメータを準備
Red ー Green ー リファクタリング サイクル

テストメソッドを書く

テストをPassする最低限
のコードを実装する

リファクタリング
例:三角形のチェッククラスを考える
TriangleクラスのcheckTriangleメソッド

• 3辺が等しい三角形、”EqualateralTriangle”を返す
• 2辺が等しい三角形、”IsoscelesTriangle”を返す
• 三角形にならない時、”NotTriangle”を返す
例:三角形のチェッククラスを考える
Step 1
テストメソッド
三辺が同じ長さの時、”EqualateralTriangle”が返される事を確認
する
TEST_F(TriangleTest, AllSidesAreSameLength) {
EXPECT_EQ("EqualateralTriangle", sut->checkTriangle(1,1,1));
}
std::string Triangle::checkTriangle(int a, int b, int c) {
return "EqualateralTriangle";
}
例:三角形のチェッククラスを考える
Step 2
テストメソッド
二辺が同じ長さの時、”IsoscelesTriangle”が返される事を確認す
る
TEST_F(TriangleTest, TwoSidesAreSameLength_rhs_lhs) {
EXPECT_EQ("IsoscelesTriangle", sut->checkTriangle(1,1,2));
}
std::string Triangle::checkTriangle(int a, int b, int c) {
if((a == b) && (b == c) && (c == a)) {
return "EqualateralTriangle";
}
return "IsoscelesTriangle";
}
例:三角形のチェッククラスを考える
Step 3
テストメソッド
三角形にならない時、”NotTriangle”が返される事を確認する
TEST_F(TriangleTest, LhsLengthTooShort) {
EXPECT_EQ("NotTriangle", sut->checkTriangle(1,3,4));
}

std::string Triangle::checkTriangle(int a, int b, int c) {
if((a == b) && (b == c) && (c == a)) {
return "EqualateralTriangle";
}
if(a == b) {
return "IsoscelesTriangle";
}
return "NotTriangle";
}
例:三角形のチェッククラスを考える
Step 4
リファクタリング(引数名の変更)

std::string Triangle::checkTriangle(int lhs, int rhs, int bottom) {
if((lhs == rhs) && (rhs == bottom) && (bottom == lhs)) {
return "EqualateralTriangle";
}
if(lhs == rhs) {
return "IsoscelesTriangle";
}
return "NotTriangle";
}
継続的にTDDをやるための材料
• 単体テストは設計(実装)の一部だと理解する
• 単体テストフレームワークを使用する
• テストコードも構成管理する
• テストカバレッジを「Doneの定義」に含める
• CI (Continuous Integration)サーバを導入する

http://www.flickr.com/photos/roboppy/312248677
最後に
• TDD は簡単なプラクティスではありません。
• しかし、コード品質は確実に向上します。
• 一度や二度の失敗で諦めずに取り組みましょう。

継続は、力なり
http://www.flickr.com/photos/picsbyperi/7457301384
ワークショップのルール
• 先ずは、ペアで自己紹介して下さい。
• ナビゲータとドライバを決めて下さい。

• ナビゲータがテストコードを、
ドライバがプロダクションコードを書いて下さい。
• ナビゲータとドライバは適当に交代して下さい。

• 時間は30分です。
ワークショップ1
FizzBuzzクラス(又は関数)を作る
• 引数が3の倍数の時、”Fizz”を返す
• 引数が5の倍数の時、”Buzz”を返す
• 引数が3と5の公倍数の時、”FizzBuzz”を返す
• それ以外の時、引数の整数を文字列にして返す
ワークショップ2
FizzBuzzを作る
さっき作ったクラス(関数)を使ってFizzBuzzを
作ってください
• 引数は、1~100で
• ディスプレイに表示するように
ワークショップ3
FizzBuzzを作る
さっき作ったFizzBuzzを改修してください
• 出力先をファイルに変更してください。
• ファイル名は任意でOKです。
ワークショップ4
FizzBuzzを作る
さっき改修したFizzBuzzを更に改修してください
• 出力先をファイルだけでなく、ディスプレイにも
表示するように変更してください。

• ファイル名は任意でOKです。
ワークショップ5
FizzBuzzを作る
さっき改修したFizzBuzzを更に改修してください
• 数字はディスプレイに、Fizz、Buzz、及びFizzBuzz
はファイルに出力するようにしてください

• ファイル名は任意でOKです。

TDDワークショップ(第2回)