ユニットテストの保守性を作りこむ, xpjugkansai2011

135
ユニットテストの 保守性を作りこむ ~設計・実装の工夫で支える ユニットテストの継続的活用~ rev.1.1 2011/1/29 @XP祭り関西2011 井芹 洋輝

Upload: h-iseri

Post on 24-May-2015

14.401 views

Category:

Technology


2 download

TRANSCRIPT

Page 1: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの 保守性を作りこむ

~設計・実装の工夫で支える ユニットテストの継続的活用~

rev.1.1 2011/1/29 @XP祭り関西2011

井芹 洋輝

Page 2: ユニットテストの保守性を作りこむ, xpjugkansai2011

謝辞

• 登壇者として声をかけていただいた細谷さん、

• 機会を提供して頂いたXPJUG関西の皆様

• 素晴らしい場を作り上げている登壇者・参加者・運営者の方々

深くお礼申し上げます。

Page 3: ユニットテストの保守性を作りこむ, xpjugkansai2011

自己紹介

• 井芹洋輝(いせりひろき)

• 扱っているもの

– 組込み開発/ソフトウェアテスト/開発者テスト

• 所属

– WACATE実行委員/TDD研究会/ATECなど

• 対外活動

– JaSST’11 Tokyo/WACATE2011冬/Androidテスト祭り等 – ソフトウェアテストPRESS総集編/Ultimate agile Stories

Page 4: ユニットテストの保守性を作りこむ, xpjugkansai2011

概要

• ユニットテストの保守性を作りこむアプローチを、いくつかの具体例を交えて俯瞰的に見ていきます

Page 5: ユニットテストの保守性を作りこむ, xpjugkansai2011

概要

1. 背景 – ユニットテストの現状と課題

2. 目標 – ユニットテスト継続活用における目標

3. ユニットテストの保守性向上アプローチ – テストコードの実装の工夫

– テストコードの構造設計の工夫

– テスト設計の工夫

4. まとめ

Page 6: ユニットテストの保守性を作りこむ, xpjugkansai2011

背景

Page 7: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの用途 【現在編】

Page 8: ユニットテストの保守性を作りこむ, xpjugkansai2011

軽快なチェック

Page 9: ユニットテストの保守性を作りこむ, xpjugkansai2011

//コードから無視する文字を削除する

string reformat(string line)

{

...(複雑なロジック)...

}

複雑なメソッドを書いた きちんと動くか不安だ

Page 10: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(CodeAnalyzer, reformatContainComment)

{

EXPECT_EQ("#hoge", reformat("#hoge /* fuga */"));

}

TEST(CodeAnalyzer, reformatContainSpace)

{

EXPECT_EQ("void function()", reformat("void function ()"));

}

TEST(CodeAnalyzer, reformatContainEmptyLine)

{

EXPECT_EQ("test;¥n", reformat("test;¥n¥n¥n¥n"));

}

//コードから無視する文字を削除する

string reformat(string line)

{

...(複雑なロジック)...

}

安心できるまでテストを書く

Page 11: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(CodeAnalyzer, reformatContainComment)

{

EXPECT_EQ("#hoge", reformat("#hoge /* fuga */"));

}

TEST(CodeAnalyzer, reformatContainSpace)

{

EXPECT_EQ("void function()", reformat("void function ()"));

}

TEST(CodeAnalyzer, reformatContainEmptyLine)

{

EXPECT_EQ("test;¥n", reformat("test;¥n¥n¥n¥n"));

}

//コードから無視する文字を削除する

string reformat(string line)

{

...(複雑なロジック)...

}

安心できるまでテストを書く

リスクや不安を即時に解消して前進できる

Page 12: ユニットテストの保守性を作りこむ, xpjugkansai2011

リファクタリング

Page 13: ユニットテストの保守性を作りこむ, xpjugkansai2011

//うるう年か判定する

bool isLeapYear(unsigned int year)

{

if (year % 4 == 0)

{

if (year % 100 == 0)

{

if (year % 400 == 0)

{

return true;

}

return false;

}

return true;

}

return false;

}

実装が汚い

Page 14: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(TestYear, isLeapYearDivisibleBy4)

{

EXPECT_EQ(true, isLeapYear(8));

EXPECT_EQ(false, isLeapYear(9));

EXPECT_EQ(true, isLeapYear(0));

}

TEST(TestYear, isLeapYearDivisibleBy400)

{

EXPECT_EQ(true, isLeapYear(800));

EXPECT_EQ(false, isLeapYear(900));

}

TEST(TestYear, isLeapYearDivisibleBy100)

{

EXPECT_EQ(false, isLeapYear(500));

}

//うるう年か判定する

bool isLeapYear(unsigned int year)

{

if (year % 4 == 0)

{

if (year % 100 == 0)

{

if (year % 400 == 0)

{

return true;

}

return false;

}

return true;

}

return false;

}

1. テストで保護する

Page 15: ユニットテストの保守性を作りこむ, xpjugkansai2011

//うるう年か判定する

bool isLeapYear(unsigned int year)

{

if (year % 400 == 0)

{

return true;

}

if ((year % 4 == 0) && (year % 100 != 0))

{

return true;

}

return false;

}

2. テストでチェックしつつ

リファクタリング

TEST(TestYear, isLeapYearDivisibleBy4)

{

EXPECT_EQ(true, isLeapYear(8));

EXPECT_EQ(false, isLeapYear(9));

EXPECT_EQ(true, isLeapYear(0));

}

TEST(TestYear, isLeapYearDivisibleBy400)

{

EXPECT_EQ(true, isLeapYear(800));

EXPECT_EQ(false, isLeapYear(900));

}

TEST(TestYear, isLeapYearDivisibleBy100)

{

EXPECT_EQ(false, isLeapYear(500));

}

1. テストで保護する

Page 16: ユニットテストの保守性を作りこむ, xpjugkansai2011

//うるう年か判定する

bool isLeapYear(unsigned int year)

{

if (year % 400 == 0)

{

return true;

}

if ((year % 4 == 0) && (year % 100 != 0))

{

return true;

}

return false;

}

2. テストでチェックしつつ

リファクタリング

TEST(TestYear, isLeapYearDivisibleBy4)

{

EXPECT_EQ(true, isLeapYear(8));

EXPECT_EQ(false, isLeapYear(9));

EXPECT_EQ(true, isLeapYear(0));

}

TEST(TestYear, isLeapYearDivisibleBy400)

{

EXPECT_EQ(true, isLeapYear(800));

EXPECT_EQ(false, isLeapYear(900));

}

TEST(TestYear, isLeapYearDivisibleBy100)

{

EXPECT_EQ(false, isLeapYear(500));

}

1. テストで保護する

安全に記述改善

Page 17: ユニットテストの保守性を作りこむ, xpjugkansai2011

学習テスト

Page 18: ユニットテストの保守性を作りこむ, xpjugkansai2011

boost::xpressive

初使用で よくわからない

Page 19: ユニットテストの保守性を作りこむ, xpjugkansai2011

boost::xpressive

TEST(regex, regexFileseek)

{

namespace xp = boost::xpressive;

string target; xp::smatch match;

xp::sregex rex = xp::sregex::compile ("(¥¥.c|¥¥.h)$");

target = "test";

EXPECT_EQ(false, xp::regex_search(target, match, rex));

target = "test.c";

EXPECT_EQ(true, xp::regex_search(target, match, rex));

target = "test.h";

EXPECT_EQ(true, xp::regex_search(target, match, rex));

target = "./../source/_svn/text-base/main.c.svn-base";

EXPECT_EQ(false, xp::regex_search(target, match, rex));

target = "test.cpp";

EXPECT_EQ(false, xp::regex_search(target, match, rex));

}

テストで動作を確認する。不安がなくなるまで試す

Page 20: ユニットテストの保守性を作りこむ, xpjugkansai2011

boost::xpressive

TEST(regex, regexFileseek)

{

namespace xp = boost::xpressive;

string target; xp::smatch match;

xp::sregex rex = xp::sregex::compile ("(¥¥.c|¥¥.h)$");

target = "test";

EXPECT_EQ(false, xp::regex_search(target, match, rex));

target = "test.c";

EXPECT_EQ(true, xp::regex_search(target, match, rex));

target = "test.h";

EXPECT_EQ(true, xp::regex_search(target, match, rex));

target = "./../source/_svn/text-base/main.c.svn-base";

EXPECT_EQ(false, xp::regex_search(target, match, rex));

target = "test.cpp";

EXPECT_EQ(false, xp::regex_search(target, match, rex));

}

テストで動作を確認する。不安がなくなるまで試す

動作の勉強とチェック

コードのドキュメントとして活用

Page 21: ユニットテストの保守性を作りこむ, xpjugkansai2011

デバッギングテスト

Page 22: ユニットテストの保守性を作りこむ, xpjugkansai2011

class MacroWord

バグがでた

Page 23: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST_F(TestMacroWord, macroDataMacroWordBug21)

{

MacroData macroData;

macroData.push_back("AUTO_DEBUG_1");

macroData.push_back("AUTO_DEBUG_2");

macroData.push_back("AUTO_DEBUG_2");

macroData.push_back("AUTO_DEBUG_2");

macroData.push_back(“BB_H_");

MacroWord macroWord(macroData);

EXPECT_EQ("AUTO_DEBUG_1", macroWord.data_[0].macroName_);

EXPECT_EQ(1, analyzer.data_[0].number);

EXPECT_EQ("AUTO_DEBUG_2", macroWord.data_[1].macroName_)

EXPECT_EQ(3, analyzer.data_[1].number);

EXPECT_EQ(“BB_H_", macroWord.data_[2].macroName_);

EXPECT_EQ(1, analyzer.data_[2].number);

}

class MacroWord

1. テスト上でバグを再現する

Page 24: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST_F(TestMacroWord, MacroWordBug21)

{

MacroData macroData;

macroData.push_back("AUTO_DEBUG_2");

macroData.push_back("AUTO_DEBUG_2");

macroData.push_back("AUTO_DEBUG_2");

MacroWordProxy::wordCount(macroData);

EXPECT_EQ(3, MacroWordProxy::getSize(0));

}

class MacroWord

1. テスト上でバグを再現する

2. テストでバグを絞込み特定する

Page 25: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST_F(TestMacroWord, MacroWordBug21)

{

MacroData macroData;

macroData.push_back("AUTO_DEBUG_2");

macroData.push_back("AUTO_DEBUG_2");

macroData.push_back("AUTO_DEBUG_2");

MacroWordProxy::wordCount(macroData);

EXPECT_EQ(3, MacroWordProxy::getSize(0));

}

class MacroWord

1. テスト上でバグを再現する

2. テストでバグを絞込み特定する

3. 修正後テストがパスすることを確認する

Page 26: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST_F(TestMacroWord, macroWordBug21)

{

MacroData macroData;

macroData.push_back("AUTO_DEBUG_2");

macroData.push_back("AUTO_DEBUG_2");

macroData.push_back("AUTO_DEBUG_2");

MacroWordProxy::wordCount(macroData);

EXPECT_EQ(3, MacroWordProxy::getSize(0));

}

class MacroWord

1. テスト上でバグを再現する

2. テストでバグを絞込み特定する

3. 修正後テストがパスすることを確認する

バグを再現 バグを特定 バグ修正のチェック

Page 27: ユニットテストの保守性を作りこむ, xpjugkansai2011

その他ユニットテストの活用

テスト駆動開発

CIによる自動回帰テスト

RED

GREEN REFACT

OR

Page 28: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの効能

• バグを検出する

• バグ発生箇所を絞り込む

• バグ混入を監視する

• 機能的な保証を行う

• 設計支援を行う

• ドキュメントとなる

Page 29: ユニットテストの保守性を作りこむ, xpjugkansai2011

ツール・方法論が発達した今ではプログラミングとユニットテストが一体となり、プログラマはプログラミングの最初から最後まで継続的にユニットテストのサポートを受けられるようになっている

Page 30: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの継続活用 における課題

Page 31: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの継続活用においては保守性がクリティカルな問題となりえる 保守性に問題を持つユニットテストは、再利用の度に冗長なコストを累積させる

Page 32: ユニットテストの保守性を作りこむ, xpjugkansai2011

可読性の悪いテスト

Page 33: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(TestHoge, Hoge)

{

stringstream ss;

int i, j;

Inspector inspector("hoge", "fuga");

EXPECT_EQ(false, inspector.isEmpty());

inspector.pushLine("this is test.");

inspector.pushLine("this is test.");

EXPECT_EQ(6, inspector.getWords());

for (j = 0; j < 100; j++) {

for (i = 0; i < j; i++) {

ss << j;

ss << " ";

ss << i;

inspector.pushLine(ss.str());

EXPECT_EQ(INFO, inspector.getState()); }

inspector.addSection();

EXPECT_EQ(j, inspector.getSection()); }

EXPECT_EQ(0, inspector.runInspection());

}

Page 34: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(TestHoge, Hoge)

{

stringstream ss;

int i, j;

Inspector inspector("hoge", "fuga");

EXPECT_EQ(false, inspector.isEmpty());

inspector.pushLine("this is test.");

inspector.pushLine("this is test.");

EXPECT_EQ(6, inspector.getWords());

for (j = 0; j < 100; j++) {

for (i = 0; i < j; i++) {

ss << j;

ss << " ";

ss << i;

inspector.pushLine(ss.str());

EXPECT_EQ(INFO, inspector.getState()); }

inspector.addSection();

EXPECT_EQ(j, inspector.getSection()); }

EXPECT_EQ(0, inspector.runInspection());

}

何を検証しているのか分からない

バグはどこにあるのか分からない

Assertion Roulette

正しいのかどうか わからない

Page 35: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(TestHoge, Hoge)

{

stringstream ss;

int i, j;

Inspector inspector("hoge", "fuga");

EXPECT_EQ(false, inspector.isEmpty());

inspector.pushLine("this is test.");

inspector.pushLine("this is test.");

EXPECT_EQ(6, inspector.getWords());

for (j = 0; j < 100; j++) {

for (i = 0; i < j; i++) {

ss << j;

ss << " ";

ss << i;

inspector.pushLine(ss.str());

EXPECT_EQ(INFO, inspector.getState()); }

inspector.addSection();

EXPECT_EQ(j, inspector.getSection()); }

EXPECT_EQ(0, inspector.runInspection());

}

何を検証しているのか分からない

バグはどこにあるのか分からない

Assertion Roulette

テストの再利用コストを増大させる

正しいのかどうか わからない

Page 36: ユニットテストの保守性を作りこむ, xpjugkansai2011

変更性の悪いテスト (テストコードの重複/

Fragile Test)

Page 37: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(testHoge, InspectionFugaPiyo)

{

MotorStatus motorStatus(0, 0);

MaintenanceData mtData;

MaintenanceType mtType(createRegionID(EU));

setInitialData(mtData, mtType);

InspectionFuga inspector;

inspector.set(createMaintenanceInfo(mtData, mtType), motorStatus);

}

TEST(testHoge, InspectionFugaFuga)

{

MotorStatus motorStatus(-1, 2);

MaintenanceData mtData;

MaintenanceType mtType(createRegionID(ASIA));

setInitialData(mtData, mtType);

InspectionFuga inspector;

inspector.set(createMaintenanceInfo(mtData, mtType), motorStatus);

}

Page 38: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(testHoge, InspectionFugaHoge)

{

MotorStatus motorStatus(133, 232);

MaintenanceData mtData;

MaintenanceType mtType(createRegionID(JAPAN));

setInitialData(mtData, mtType);

InspectionFuga inspector;

inspector.set(createMaintenanceInfo(mtData, mtType), motorStatus);

}

似たような記述が多数存在・・

Page 39: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(testHoge, InspectionFugaHoge)

{

MotorStatus motorStatus(133, 232);

MaintenanceData mtData;

MaintenanceType mtType(createRegionID(JAPAN));

setInitialData(mtData, mtType);

InspectionFuga inspector;

inspector.set(createMaintenanceInfo(mtData, mtType), motorStatus);

}

Fragile Test

テストが製品コードの変更コストを増大させる

テストがテストコードの変更コストを増大させる

テストコードの変更箇所も特定困難

製品コードの変更コストが大きい

Page 40: ユニットテストの保守性を作りこむ, xpjugkansai2011

補足(Fragile Test)

• 製品コードとテストコードの過度な依存関係によって、製品コード/テストコードの変更コストが増大してしまう問題をFragile Testsという

• Fragile Tests問題は継続的なユニットテストやTDDが抱える大きな問題であり、常に対策し続けなくてはならない

Page 41: ユニットテストの保守性を作りこむ, xpjugkansai2011

テストコードもレガシーコード化する ユニットテストを継続活用する環境では、レガシーコード化したユニットテストは最悪テストの効果をマイナスに反転させる

Page 42: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストのコストモデル (継続的にテストを作る場合)

時間

ユニットテスト

規模

時間

ユニットテスト

の利益

時間

ユニットテスト

保守コスト

Page 43: ユニットテストの保守性を作りこむ, xpjugkansai2011

時間

ユニットテスト

規模

時間

ユニットテスト

の利益

時間

ユニットテスト

保守コスト

レガシーコード化

レガシーコード化

ユニットテストのコストモデル (継続的にテストを作る場合)

Page 44: ユニットテストの保守性を作りこむ, xpjugkansai2011

時間

ユニットテスト

規模

時間

ユニットテスト

の利益

時間

ユニットテスト

保守コスト

レガシーコード化

レガシーコード化

使えないテストの放棄という手段に・・・

ユニットテストのコストモデル (継続的にテストを作る場合)

Page 45: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストはプログラミングを強力に支援し得るが、一方でプログラミングを強力に阻害し得る ユニットテストはソフトウェアの保守性を支えるが、一方でソフトウェアの保守性を阻害し得る

Page 46: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの継続活用 のための目標

Page 47: ユニットテストの保守性を作りこむ, xpjugkansai2011

目標

時間

ユニットテスト

規模

時間

ユニットテスト

の利益

時間

ユニットテスト

運用コスト

保守性改善

Page 48: ユニットテストの保守性を作りこむ, xpjugkansai2011

目標

時間

ユニットテスト

規模

時間

ユニットテスト

の利益

時間

ユニットテスト

運用コスト

保守性改善

保守性改善により

保守コストの増大を抑える

Page 49: ユニットテストの保守性を作りこむ, xpjugkansai2011

テストとプロダクトの扱いに違いはない。テストもプロセスを持ち構造的設計、機能的設計を行う必要がある。テストはプロダクトと同じように保守性を作りこむ余地がある。

Page 50: ユニットテストの保守性を作りこむ, xpjugkansai2011

テストコードの

構造設計

テストコードの

実装

テストの

上位設計

テスト設計

テスト実装

テスト設計 テスト構造設計

テストコードの実装

Page 51: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの 保守性を支える原則

• 変更に対する堅牢性に優れる

• 可読性に優れる

• 独立性に優れる

• 自己完結している

• 完全自動化している

• 細粒度なテストケース

Page 52: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの 保守性を支える原則

• 変更に対する堅牢性に優れる

• 可読性に優れる

• 独立性に優れる

• 自己完結している

• 完全自動化している

• 細粒度なテストケース

テスト対象・テスト設計が変更されても、テストコードの変更が小さく済む

Page 53: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの 保守性を支える原則

• 変更に対する堅牢性に優れる

• 可読性に優れる

• 独立性に優れる

• 自己完結している

• 完全自動化している

• 細粒度なテストケース

テストコードからテストの設計や意図を容易に読み取れる

Page 54: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの 保守性を支える原則

• 変更に対する堅牢性に優れる

• 可読性に優れる

• 独立性に優れる

• 自己完結している

• 完全自動化している

• 細粒度なテストケース

構造軸:他のテストメソッド、テストクラスの影響を受けない 時間軸:テストの実行順序の影響を受けない

Page 55: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの 保守性を支える原則

• 変更に対する堅牢性に優れる

• 可読性に優れる

• 独立性に優れる

• 自己完結している

• 完全自動化している

• 細粒度なテストケース

Setup、テスト実行、結果の検証、Teardownの一連の処理が細か粒度で完結している

Page 56: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの 保守性を支える原則

• 変更に対する堅牢性に優れる

• 可読性に優れる

• 独立性に優れる

• 自己完結している

• 完全自動化している

• 細粒度なテストケース

テストを実行する処理を完全に自動化できる

Page 57: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの 保守性を支える原則

• 変更に対する堅牢性に優れる

• 可読性に優れる

• 独立性に優れる

• 自己完結している

• 完全自動化している

• 細粒度なテストケース テストメソッドが十分に細分化されている

Page 58: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの 保守性作りこみアプローチ

1. 実装の工夫

2. 構造設計の工夫

3. 設計の工夫

Page 59: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの保守性作りこみアプローチ1

実装の工夫

Page 60: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの実装はプログラミング行為そのものである 保守性を高めるパターンを適用し継続的に記述改善する必要がある

Page 61: ユニットテストの保守性を作りこむ, xpjugkansai2011

コードの共通化

• 重複する記述は共通化する

• 便利なロジックは汎用化する

Page 62: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(Buyer, addSameStatus)

{

Buyer buyer;

Customer customer1("Taro", "Yamada", 15, 2, "HOGE|FUGA");

customer1.addCategory(STATE_ACTIVE);

Customer customer2("Taro", "Yamada", 15, 2, "HOGE|FUGA|HOGEHOGE");

customer2.addCategory(STATE_ACTIVE);

buyer.add(customer1);

buyer.add(customer2);

EXPECT_EQ(0, buyer.getSection());

}

似た記述が大量に散在

Page 63: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(Buyer, addSameStatus)

{

Buyer buyer;

Customer customer1("Taro", "Yamada", 15, 2, "HOGE|FUGA");

customer1.addCategory(STATE_ACTIVE);

Customer customer2("Taro", "Yamada", 15, 2, "HOGE|FUGA|HOGEHOGE");

customer2.addCategory(STATE_ACTIVE);

buyer.add(customer1);

buyer.add(customer2);

EXPECT_EQ(0, buyer.getSection());

}

似た記述が大量に散在

Customer createCustomer(string status)

{

Customer customer("Taro", "Yamada", 15, 2, status);

customer.addCategory(STATE_ACTIVE);

return customer;

}

Parameterized Creation Method

Page 64: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(Buyer, addSameStatus)

{

Buyer buyer;

Customer customer1 = createCustomer("HOGE|FUGA");

Customer customer2 = createCustomer("HOGE|FUGA|HOGEHOGE");

buyer.add(customer1);

buyer.add(customer2);

EXPECT_EQ(0, buyer.getSection());

}

Customer createCustomer(string status)

{

Customer customer("Taro", "Yamada", 15, 2, status);

customer.addCategory(STATE_ACTIVE);

return customer;

}

Parameterized Creation Method

重複部分をCreation Methodで共通化

Page 65: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(TestItemList, ItemPush)

{

EXPECT_EQ(expectItem.getName(), item.getName());

EXPECT_EQ(expectItem.getID(), item.getID());

EXPECT_EQ(expectItem.getSize(), item.getSize());

}

TEST(TestItemList, itemPush)

{

EXPECT_EQ(expectItem2.getName(), item2.getName());

EXPECT_EQ(expectItem2.getID(), item2.getID());

EXPECT_EQ(expectItem2.getSize(), item2.getSize());

}

似た記述が大量に散在

Page 66: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(TestItemList, itemPush)

{

EXPECT_EQ(expectItem.getName(), item.getName());

EXPECT_EQ(expectItem.getID(), item.getID());

EXPECT_EQ(expectItem.getSize(), item.getSize());

}

TEST(TestItemList, itemPush)

{

EXPECT_EQ(expectItem2.getName(), item2.getName());

EXPECT_EQ(expectItem2.getID(), item2.getID());

EXPECT_EQ(expectItem2.getSize(), item2.getSize());

}

似た記述が大量に散在

#define EXPECT_ITEM_EQ(expect, actual) ¥

EXPECT_EQ((expect).getName(), (actual).getName()); ¥

EXPECT_EQ((expect).getID(), (actual).getID()+1); ¥

EXPECT_EQ((expect).getSize(), (actual).getSize());

Custom Assertion

Page 67: ユニットテストの保守性を作りこむ, xpjugkansai2011

#define EXPECT_ITEM_EQ(expect, actual) ¥

EXPECT_EQ((expect).getName(), (actual).getName()); ¥

EXPECT_EQ((expect).getID(), (actual).getID()+1); ¥

EXPECT_EQ((expect).getSize(), (actual).getSize());

Custom Assertion

TEST(TestItemList, itemPush)

{

EXPECT_ITEM_EQ(expectItem, item);

}

TEST(TestItemList, test_itemPush)

{

EXPECT_ITEM_EQ(expectItem2, item2);

}

重複部分を拡張Assertionで共通化

Page 68: ユニットテストの保守性を作りこむ, xpjugkansai2011

可読性の向上

• 重要なものを目立たせる

• 適切な名前に置き換える

• 小さくする

Page 69: ユニットテストの保守性を作りこむ, xpjugkansai2011

Customer customer2("Taro", "Yamada", 15, 2, "HOGE|FUGA|HOGEHOGE");

何でもいいダミー値 テストしたい値

Page 70: ユニットテストの保守性を作りこむ, xpjugkansai2011

Customer customer2("Taro", "Yamada", 15, 2, "HOGE|FUGA|HOGEHOGE");

Customer customer2 = createCustomer("HOGE|FUGA|HOGEHOGE");

Parameterized Creation Method

何でもいいダミー値 テストしたい値

ダミー値を隠し、重要なパラメータを強調する

重要なものを目立たせる

※隠ぺいはトレードオフなので要注意

Page 71: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(testHoge, Fuga)

{

MotorStatus motorStatus(133, 232);

InspectionFuga inspector;

inspector.set(createMaintenanceInfo(motorStatus);

EXPECT_EQ(START, inspector.getState());

EXPECT_EQ(true, inspector.isEmpty());

inspector.initialize();

EXPECT_EQ(INFO, inspector.getState());

EXPECT_EQ(false, inspector.isEmpty());

}

Page 72: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(testHoge, Fuga)

{

MotorStatus motorStatus(133, 232);

InspectionFuga inspector;

inspector.set(createMaintenanceInfo(motorStatus);

EXPECT_EQ(START, inspector.getState());

EXPECT_EQ(true, inspector.isEmpty());

inspector.initialize();

EXPECT_EQ(INFO, inspector.getState());

EXPECT_EQ(false, inspector.isEmpty());

}

1つのテストメソッドで色々検証しているため少し読みにくい

分割で改善する

Page 73: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(testHoge, Fuga)

{

InspectionFuga inspector = createInspectionFugaDummy();

EXPECT_EQ(START, inspector.getState());

EXPECT_EQ(true, inspector.isEmpty());

inspector.initialize();

EXPECT_EQ(INFO, inspector.getState());

EXPECT_EQ(false, inspector.isEmpty());

}

分割時に重複するメソッドをあらかじめ抜き出す

Page 74: ユニットテストの保守性を作りこむ, xpjugkansai2011

TEST(testHoge, fugaConstractor)

{

InspectionFuga inspector = createInspectionFugaDummy();

EXPECT_EQ(START, inspector.getState());

EXPECT_EQ(true, inspector.isEmpty());

}

TEST(testHoge, fugaInitialize)

{

InspectionFuga inspector = createInspectionFugaDummy();

inspector.initialize();

EXPECT_EQ(INFO, inspector.getState());

EXPECT_EQ(false, inspector.isEmpty());

}

テストメソッドを小さくする

意図を読み取りやすい名前をつける

Page 75: ユニットテストの保守性を作りこむ, xpjugkansai2011

その他工夫

• テストメソッドをコンパクトに記述する

• テストコードの影響範囲を限定する

– 時間軸、構造軸

• 危ういコードを分離する

– 変更頻度の高いインターフェースをラッピングする

– テスト用インタフェースを用意する

Page 76: ユニットテストの保守性を作りこむ, xpjugkansai2011

その他工夫

• テストコードの保守性は製品コード・テストコード両方から支えられる

• 堅牢なユニットテストには堅牢な製品コードのインターフェースが不可欠である

製品 コード

テスト コード

テストが内部メンバを細かく参照する形は

Fragile Testsになりえる

Page 77: ユニットテストの保守性を作りこむ, xpjugkansai2011

その他工夫

• テストコードの保守性は製品コード・テストコード両方から支えられる

• 堅牢なユニットテストには堅牢な製品コードのインターフェースが不可欠である

製品 コード

テスト コード

堅牢なインターフェースを製品コードが提供することで

テストコードの堅牢性を高められる

堅牢な

インターフェース

Page 78: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの実装はプログラミング行為そのものである 製品コード・テストコード両方に対して、保守性を高めるパターンを適用し継続的に記述改善する必要がある

Page 79: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの保守性作りこみアプローチ2

構造設計の工夫

Page 80: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストのコードはAssertionの羅列ではない テストコードも構造を持ち、それがユニットテストの保守性に影響を与えるため、構造設計の視点が必要になる

Page 81: ユニットテストの保守性を作りこむ, xpjugkansai2011

内部設計

Page 82: ユニットテストの保守性を作りこむ, xpjugkansai2011

テストのツリー構造

ユニットテスト

Test Suite

Test Case

Assertion

Assertion

Test Case

… Test Suite

Page 83: ユニットテストの保守性を作りこむ, xpjugkansai2011

テストのツリー構造

ユニットテスト

Test Suite

Test Case

Assertion

Assertion

Test Case

… Test Suite

少数のAssertionでTestCaseを構成する (1条件/1テストケースなど

テスト設計の最小構成単位を反映)

細粒度・可読性

テスト設計やテストの意図が

分かりやすくなる

Page 84: ユニットテストの保守性を作りこむ, xpjugkansai2011

テストのツリー構造

ユニットテスト

Test Suite

Test Case

Assertion

Assertion

Test Case

… Test Suite

独立・無干渉

独立・無干渉

独立性、自己完結性の確保

変更や流用の影響を局所化できる

Page 85: ユニットテストの保守性を作りこむ, xpjugkansai2011

テストのツリー構造

ユニットテスト

Test Suit

Test Case

Assertion

Assertion

Test Case

… Test Suit

Test Suite:テスト対象Class = n : 1 ※Fixture/外部コンポーネント等で分離

通常は1:1

可読性など

構造に対する網羅を

チェックしやすくする

Page 86: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの内部構造は一般的にツリー構造をとる そのため葉のコンパクト化、枝の独立性向上、適切な分割といった構造化設計の工夫が重要となる それによりモジュール性や可読性が向上し、ユニットテストの保守性が作りこまれる

Page 87: ユニットテストの保守性を作りこむ, xpjugkansai2011

外部インターフェース設計

Page 88: ユニットテストの保守性を作りこむ, xpjugkansai2011

外部インターフェース

• 外部コンポーネントは保守性を低下させるリスクになる

テスト

テスト対象

テスト

ブラックボックス

外部IF

Page 89: ユニットテストの保守性を作りこむ, xpjugkansai2011

外部インターフェース

• 外部コンポーネントは保守性を低下させるリスクになる

テスト

テスト対象

テスト

ブラックボックス 状態を持つ

ブラックボックスを介してテストが結合

外部IF

Page 90: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの保守性を損なう外部コンポーネントは、外部インターフェースの改善で分離する必要がある

外部コンポーネントを使用しないテスト

ブラックボックスを使用するテスト

StubやMockなど

テスト対象

ブラックボックス

Page 91: ユニットテストの保守性を作りこむ, xpjugkansai2011

構造設計による改善

Page 92: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの内部構造や外部インターフェースはその保守性に影響するため、構造設計・アーキテクチャ設計を俯瞰的に洗練する視点が要求される ただし留意点が2つある

Page 93: ユニットテストの保守性を作りこむ, xpjugkansai2011

1:柔軟なズームアウト/ ズームインが必要

• ユニットテストを継続活用する場合、ユニットテストはしばしばボトムアップに実装されるため、構造設計は実装状況に応じて柔軟に行わなければならない

構造設計

実装

ズームアウト

ズームイン

Page 94: ユニットテストの保守性を作りこむ, xpjugkansai2011

2:改善対象として製品コード/ テストコードを区別しない

• ユニットテストの障害は、製品コード/テストコード両方から除去する

• 両者のインターフェースを統合的に洗練させる

Page 95: ユニットテストの保守性を作りこむ, xpjugkansai2011

テスト対象が

外部コンポーネントに依存している

void CameraCtrl::hoge()

{

_motorCtrl.run();

}

void CameraCtrl::fuga()

{

_motorCtrl.stop();

}

class CameraCtrl

実装

構造設計

外部コンポーネントを制御

Page 96: ユニットテストの保守性を作りこむ, xpjugkansai2011

Test Suite

MotorCtrl (外部コンポーネント)

CameraCtrl

テストが 外部コンポーネントを

共有している

実装

構造設計

ズームアウト

Page 97: ユニットテストの保守性を作りこむ, xpjugkansai2011

Test Suite

Test Suite

外部コンポーネント

CameraCtrl

テスタビリティの作りこみ

外部コンポーネントを 置換可能にする

実装

構造設計

MockやStub等

Page 98: ユニットテストの保守性を作りこむ, xpjugkansai2011

Test Suite

Test Suite

MotorCtrl

CameraCtrl

Dependency Injection

void CameraCtrl::CameraCtrl()

{

_motorCtrl.open(...):

...

}

void CameraCtrl::CameraCtrl(MemoryCtrl memoryCtrl)

{

_motorCtrl = motorCtrl;

_motorCtrl.open(...):

...

}

実装

構造設計

ズームイン

Page 99: ユニットテストの保守性を作りこむ, xpjugkansai2011

外部コンポーネントを用いるテスト

右以外のテスト

MotorCtrl

CameraCtrl

Mock、 Stub等

構造設計

実装

Dependency Injection

Page 100: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストのテストコードはAssertionのリストではない テストコードも構造を持ち、それがユニットテストの保守性に影響を与える 継続的な構造設計の改善で、テストの保守性を高めよう

Page 101: ユニットテストの保守性を作りこむ, xpjugkansai2011

実装・構造設計の参考図書

• xUnit Test Patterns

– –Refactoring Test Code

• 読書会サイトに翻訳情報あり – <http://www.fieldnotes.jp/xutp/>

– 「xutp読書会」で検索

Page 102: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの保守性作りこみアプローチ3

設計の工夫

Page 103: ユニットテストの保守性を作りこむ, xpjugkansai2011

設計の工夫

1. テスト設計を洗練する

2. トップダウンのテスト設計で保守性を作りこむ

Page 104: ユニットテストの保守性を作りこむ, xpjugkansai2011

テスト設計の洗練

Page 105: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの継続的活用 においての留意点

• 作りすぎたテストを継続的に保守しようとすると保守性に悪影響を与える

TEST(…)

{

EXPECT_EQ(…, Hoge());

} void Hoge()

{

}

TEST(…)

{

EXPECT_EQ(…, Hoge());

} TEST(…)

{

EXPECT_EQ(…, Hoge());

}

製品コード

テストコード

製品コードを変更する

と大量のテストがビルドエラー

テスト設計を変更する

と修正が複数にまたがる

Page 106: ユニットテストの保守性を作りこむ, xpjugkansai2011

保守性を高めるにはコンパクトなテストで十分な網羅性を確保する必要がある その実現にテスト設計技法やテスティングリテラシーの素養は有効である

Page 107: ユニットテストの保守性を作りこむ, xpjugkansai2011

境界値分析

• 入出力値をグルーピング(例えば同値分割)して、グループの境界の値を抽出する

• 欠陥が境界付近に偏在するという経験則に立脚

Page 108: ユニットテストの保守性を作りこむ, xpjugkansai2011

境界値分析

• 「6歳未満は無料。6歳以上12歳以下は半額。13以上は定額」

Page 109: ユニットテストの保守性を作りこむ, xpjugkansai2011

境界値分析

• 「6歳未満は無料。6歳以上12歳以下は半額。13以上は定額」

-∞ +∞

ありえない

無料

半額

定額

0

-1

5

6 12

13

Page 110: ユニットテストの保守性を作りこむ, xpjugkansai2011

カバレッジ分析

• 制御フローの網羅度を基準にテスト設計を行う

Page 111: ユニットテストの保守性を作りこむ, xpjugkansai2011

start

入力:ループ回数

end

任意の回数

ループ

カバレッジ分析

Page 112: ユニットテストの保守性を作りこむ, xpjugkansai2011

start

入力:ループ回数

end

任意の回数

ループ

カバレッジ分析

カバレッジレベル6(C6)

テストで用いるループ回数: 0回

1回

頻出する代表値

最大回

Page 113: ユニットテストの保守性を作りこむ, xpjugkansai2011

テストの目的に応じて柔軟に 設計技法を使い分ける

テスト設計

仕様

コード構造

その他

Page 114: ユニットテストの保守性を作りこむ, xpjugkansai2011

テストの目的に応じて柔軟に 設計技法を使い分ける

4で割り切れる N Y Y Y

100で割り切れる N N Y Y

400で割り切れる N N N Y

うるうどし N Y N Y

//うるう年か判定する

bool isLeapYear(unsigned int year)

{

if (year % 400 == 0)

{

return true;

}

if ((year % 4 == 0) && (year % 100 != 0))

{

return true;

}

return false;

}

テスト設計

仕様

コード構造

その他

機能的な保証

テストファースト

etc..

機械的なリファクタリング

テスタビリティの評価

etc..

Page 115: ユニットテストの保守性を作りこむ, xpjugkansai2011

テストの目的に応じて柔軟に 設計技法や考え方を応用する

仕様が厳密なので仕様ベースのテスト設計ですまそう

任意の値でいいけど取り合えず境界値を

指定しよう

仕様が不明だけど単純な分岐構造なのでC3のテストを用意してリファクタリングしよう

TEST(…)

{

EXPECT_EQ(?, ?);

}

/**

* @brief …詳細な関数仕様…

*/

void fuga(…) {

}

bool hoge(unsigned int year)

{

if (year % 400 == 0)

{

return true;

}

if ((year % 4 == 0) && (year % 100 != 0))

{

return true;

}

return false;

}

Page 116: ユニットテストの保守性を作りこむ, xpjugkansai2011

テスト設計の技法や考え方のレパートリーが増えればより洗練されたテストが書けるようになり、ユニットテストの保守性向上につながる

Page 117: ユニットテストの保守性を作りこむ, xpjugkansai2011

テスト設計技法 参考図書

Page 118: ユニットテストの保守性を作りこむ, xpjugkansai2011

2.トップダウンのテスト設計で 保守性を作りこむ

Page 119: ユニットテストの保守性を作りこむ, xpjugkansai2011

テストコードの

構造設計

テストコードの

実装

テスト設計

テスト実装

テスト設計フロー テストコード実装フロー

ユニットテストの継続的活用におけるテスト設計フロー

テスト設計フロー

Page 120: ユニットテストの保守性を作りこむ, xpjugkansai2011

テスト設計

テスト実装

テスト条件、テスト設計技法等を整理する

テストケースを求める、等

テストケースの選定を行う

テストに用いる具体値を求める、等

テスト設計フロー

Page 121: ユニットテストの保守性を作りこむ, xpjugkansai2011

俯瞰的なテスト設計もユニットテストの保守性の作りこみにとって必須である テストコードの構造設計と同様、柔軟なズームアウト/ズームインで洗練されたテスト設計を実現しよう

Page 122: ユニットテストの保守性を作りこむ, xpjugkansai2011

テストコードの

構造設計

テストコードの

実装

テスト設計

テスト実装

テスト設計フロー テストコード実装フロー

ユニットテストの継続的活用におけるテスト設計フロー

テスト条件の抽出による 外部インターフェースの洗練

Page 123: ユニットテストの保守性を作りこむ, xpjugkansai2011

テストコードの

構造設計

テストコードの

実装

テスト設計

テスト設計プロセス テストコードの実装

●テスト条件リスト

・DB

・タッチパネル

・メカニカルボタン

・UART通信

….

通常テスト

DB

DB

テスト

DB

スタブ UART

UART

テスト

UART

スタブ …

ユニットテストの制約となる

外部コンポーネントを抽出し、構造設計に反映させる

1. テスト条件を抽出 2. トップダウンでインターフェース設計

テスト実装

全体整合のとれた外部インターフェースを構築

Page 124: ユニットテストの保守性を作りこむ, xpjugkansai2011

テストコードの

構造設計

テストコードの

実装

テスト設計

テスト実装

テスト設計フロー テストコード実装フロー

ユニットテストの継続的活用におけるテスト設計フロー

テスト対象の安定度分析による ユニットテストの作りこみ

Page 125: ユニットテストの保守性を作りこむ, xpjugkansai2011

テストコードの

構造設計

テストコードの

実装

テスト設計

テスト設計プロセス テストコードの実装 安定したプログラムユニットを抽出し、イテレーティブな

ユニットテストの作りこみに反映する

テストの品質

開発時間

ゴールとなる水準 ファイル 変更step/week

Hoge.cpp 0

Fuga.cpp 23

PIyo.cpp 133

… …

Hoge Fuga Piyo …

1 ○ ○

2 ○ ○

3

1. テスト対象の安定度評価 2. 変更リスクの少ないテストを作りこみ

テストの作りすぎによるデメリットを軽減する

テスト実装

Page 126: ユニットテストの保守性を作りこむ, xpjugkansai2011

テスト計画/テストの上位設計の重要性

• 開発中継続的にユニットテストを構築していく場合

Page 127: ユニットテストの保守性を作りこむ, xpjugkansai2011

開発中継続的にユニットテストを構築していく場合、テスト計画・テスト上位設計は特に重要である 望ましいテストは何か、いつどのようなテストを確保すべきか、どのようにテストを管理するかトップダウンで決めることで、柔軟かつ洗練されたテストを確保できる

Page 128: ユニットテストの保守性を作りこむ, xpjugkansai2011

テスト計画・テスト上位設計

• テスト目的、テスト対象 • 望ましいテストの構造

– テストクラスの粒度 • Feature/Class/Fixture/Method/Story

– 望ましいAssertionの数 – テストスイートの粒度

• テストをどのように配置するか – テストレベル/テストタイプ/テストフェーズ/テストの目的

• 期待するテストの網羅性 – ソフトウェアのどこをテストするか

• コンポーネント/パッケージ/外部コンポーネント

– カバレッジはどの程度か • 構造的カバレッジ/機能的カバレッジ

Page 129: ユニットテストの保守性を作りこむ, xpjugkansai2011

テスト計画・テスト上位設計

• テストの品質の管理・保証方法

– カバレッジやテストコード等の管理

• どう維持するか/いつどのように保証するか

• テスト環境の整備

– Test Doubleの開発管理

– ツールや環境の管理

• フェーズや計画

– どの段階でどの程度のテストを確保するか

Page 130: ユニットテストの保守性を作りこむ, xpjugkansai2011

テストコードと同じく、テスト設計も上位構造を持つ 保守性の障害となるテスト条件や制約、テスト設計の抽象構造を抽出し、下位の設計に反映させていこう

Page 131: ユニットテストの保守性を作りこむ, xpjugkansai2011

まとめ

Page 132: ユニットテストの保守性を作りこむ, xpjugkansai2011

ユニットテストの実装はプログラミング行為そのもの 保守性を高めるパターンを適用し、継続的に記述改善しよう

Page 133: ユニットテストの保守性を作りこむ, xpjugkansai2011

テストコードはAssertの羅列ではない 保守性に優れたアーキテクチャ・構造をテストコードに持たせよう また構造のズームアウト/ズームインを柔軟に行って、継続的にテストコードの設計を改善させていこう

Page 134: ユニットテストの保守性を作りこむ, xpjugkansai2011

テスト設計はテストコードの保守性を左右する 保守性に優れたテストを書くためテスト設計技法、リテラシーをどんどん身につけよう テスト設計でもトップダウンでテスト設計の改善をすすめよう テスト計画・上位設計でより良いテストを効率的に確保しよう

Page 135: ユニットテストの保守性を作りこむ, xpjugkansai2011

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