More Related Content Similar to ユニットテストの保守性を作りこむ, xpjugkansai2011 (6) ユニットテストの保守性を作りこむ, xpjugkansai20113. 自己紹介
• 井芹洋輝(いせりひろき)
• 扱っているもの
– 組込み開発/ソフトウェアテスト/開発者テスト
• 所属
– WACATE実行委員/TDD研究会/ATECなど
• 対外活動
– JaSST’11 Tokyo/WACATE2011冬/Androidテスト祭り等
– ソフトウェアテストPRESS総集編/Ultimate agile Stories
5. 概要
1. 背景
– ユニットテストの現状と課題
2. 目標
– ユニットテスト継続活用における目標
3. ユニットテストの保守性向上アプローチ
– テストコードの実装の工夫
– テストコードの構造設計の工夫
– テスト設計の工夫
4. まとめ
10. //コードから無視する文字を削除する
string reformat(string line)
{
...(複雑なロジック)...
}
安心できるまでテストを書く
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"));
}
…
11. //コードから無視する文字を削除する
string reformat(string line)
{
...(複雑なロジック)...
}
安心できるまでテストを書く
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"));
}
… リスクや不安を即時に解消して前進できる
14. 1. テストで保護する
TEST(TestYear, isLeapYearDivisibleBy4)
//うるう年か判定する {
bool isLeapYear(unsigned int year)
EXPECT_EQ(true, isLeapYear(8));
{
EXPECT_EQ(false, isLeapYear(9));
if (year % 4 == 0)
EXPECT_EQ(true, isLeapYear(0));
{
}
if (year % 100 == 0)
{
TEST(TestYear, isLeapYearDivisibleBy400)
if (year % 400 == 0)
{
{
EXPECT_EQ(true, isLeapYear(800));
return true;
EXPECT_EQ(false, isLeapYear(900));
}
}
return false;
}
TEST(TestYear, isLeapYearDivisibleBy100)
return true;
{
}
EXPECT_EQ(false, isLeapYear(500));
return false;
}
}
15. 1. テストで保護する
TEST(TestYear, isLeapYearDivisibleBy4)
{
EXPECT_EQ(true, isLeapYear(8));
//うるう年か判定する EXPECT_EQ(false, isLeapYear(9));
bool isLeapYear(unsigned int year) EXPECT_EQ(true, isLeapYear(0));
{ }
if (year % 400 == 0)
{ TEST(TestYear, isLeapYearDivisibleBy400)
return true; {
} EXPECT_EQ(true, isLeapYear(800));
if ((year % 4 == 0) && (year % 100 != 0)) EXPECT_EQ(false, isLeapYear(900));
{ }
return true;
} TEST(TestYear, isLeapYearDivisibleBy100)
return false; {
} EXPECT_EQ(false, isLeapYear(500));
2. テストでチェックしつつ }
リファクタリング
16. 1. テストで保護する
TEST(TestYear, isLeapYearDivisibleBy4)
{
EXPECT_EQ(true, isLeapYear(8));
//うるう年か判定する EXPECT_EQ(false, isLeapYear(9));
bool isLeapYear(unsigned int year) EXPECT_EQ(true, isLeapYear(0));
{ }
if (year % 400 == 0)
{ TEST(TestYear, isLeapYearDivisibleBy400)
return true; {
} EXPECT_EQ(true, isLeapYear(800));
if ((year % 4 == 0) && (year % 100 != 0)) EXPECT_EQ(false, isLeapYear(900));
{ }
return true;
} TEST(TestYear, isLeapYearDivisibleBy100)
return false; {
} EXPECT_EQ(false, isLeapYear(500));
2. テストでチェックしつつ }
リファクタリング
安全に記述改善
19. 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));
}
20. 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));
}
コードのドキュメントとして活用
23. class MacroWord
1. テスト上でバグを再現する
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);
}
24. class MacroWord
1. テスト上でバグを再現する
2. テストでバグを絞込み特定する
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));
}
25. class MacroWord
1. テスト上でバグを再現する
2. テストでバグを絞込み特定する
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));
}
3. 修正後テストがパスすることを確認する
26. class MacroWord
1. テスト上でバグを再現する
2. テストでバグを絞込み特定する
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));
}
3. 修正後テストがパスすることを確認する
バグを再現 バグを特定 バグ修正のチェック
28. ユニットテストの効能
• バグを検出する
• バグ発生箇所を絞り込む
• バグ混入を監視する
• 機能的な保証を行う
• 設計支援を行う
• ドキュメントとなる
33. 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());
}
34. 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
35. 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
テストの再利用コストを増大させる
37. 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);
…
}
38. TEST(testHoge, InspectionFugaHoge)
{
MotorStatus motorStatus(133, 232);
MaintenanceData mtData;
MaintenanceType mtType(createRegionID(JAPAN));
setInitialData(mtData, mtType);
InspectionFuga inspector;
inspector.set(createMaintenanceInfo(mtData, mtType), motorStatus);
…
}
似たような記述が多数存在・・
39. 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
テストが製品コードの変更
テストコードの変更箇所
も特定困難
コストを増大させる
テストがテストコードの変更
製品コードの変更コスト
が大きい コストを増大させる
42. ユニットテストのコストモデル
(継続的にテストを作る場合)
ユニットテスト
の利益
ユニットテスト
規模
時間
ユニットテスト
保守コスト
時間
時間
43. ユニットテストのコストモデル
(継続的にテストを作る場合)
ユニットテスト
の利益
ユニットテスト
規模 レガシーコード化
時間
ユニットテスト
保守コスト
レガシーコード化
時間
時間
44. ユニットテストのコストモデル
(継続的にテストを作る場合)
ユニットテスト
の利益
ユニットテスト
規模 レガシーコード化
時間
ユニットテスト
保守コスト
レガシーコード化
時間
使えないテストの放棄とい
う手段に・・・ 時間
47. 目標
ユニットテスト
の利益
ユニットテスト
規模
時間
ユニットテスト
運用コスト
時間
保守性改善
時間
48. 目標
ユニットテスト
の利益
ユニットテスト
規模
時間
ユニットテスト
運用コスト
時間
保守性改善
保守性改善により
保守コストの増大を抑える 時間
50. テスト構造設計 テスト設計
テストコードの実装
テストコードの テストの
構造設計 上位設計
テストコードの テスト設計
実装 テスト実装
51. ユニットテストの
保守性を支える原則
• 変更に対する堅牢性に優れる
• 可読性に優れる
• 独立性に優れる
• 自己完結している
• 完全自動化している
• 細粒度なテストケース
52. ユニットテストの
保守性を支える原則
• 変更に対する堅牢性に優れる
• 可読性に優れる テスト対象・テスト設計が変更
されても、テストコードの変更
• 独立性に優れる
が小さく済む
• 自己完結している
• 完全自動化している
• 細粒度なテストケース
53. ユニットテストの
保守性を支える原則
• 変更に対する堅牢性に優れる
• 可読性に優れる テストコードからテストの設計
や意図を容易に読み取れる
• 独立性に優れる
• 自己完結している
• 完全自動化している
• 細粒度なテストケース
54. ユニットテストの
保守性を支える原則
• 変更に対する堅牢性に優れる
• 可読性に優れる 構造軸:他のテストメソッド、テ
• 独立性に優れる ストクラスの影響を受けない
時間軸:テストの実行順序の影
• 自己完結している 響を受けない
• 完全自動化している
• 細粒度なテストケース
55. ユニットテストの
保守性を支える原則
• 変更に対する堅牢性に優れる
• 可読性に優れる
Setup、テスト実行、結果の検証、
• 独立性に優れる Teardownの一連の処理が細か
• 自己完結している 粒度で完結している
• 完全自動化している
• 細粒度なテストケース
56. ユニットテストの
保守性を支える原則
• 変更に対する堅牢性に優れる
• 可読性に優れる
• 独立性に優れる
• 自己完結している テストを実行する処理を完全に
• 完全自動化している 自動化できる
• 細粒度なテストケース
57. ユニットテストの
保守性を支える原則
• 変更に対する堅牢性に優れる
• 可読性に優れる
• 独立性に優れる
• 自己完結している
• 完全自動化している
• 細粒度なテストケース テストメソッドが十分に細分化さ
れている
62. 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());
}
63. 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());
}
Parameterized Creation Method
Customer createCustomer(string status)
{
Customer customer("Taro", "Yamada", 15, 2, status);
customer.addCategory(STATE_ACTIVE);
return customer;
}
64. TEST(Buyer, addSameStatus)
{
Buyer buyer;
Customer customer1 = createCustomer("HOGE|FUGA");
Customer customer2 = createCustomer("HOGE|FUGA|HOGEHOGE");
buyer.add(customer1);
重複部分をCreation
buyer.add(customer2); Methodで共通化
EXPECT_EQ(0, buyer.getSection());
}
Parameterized Creation Method
Customer createCustomer(string status)
{
Customer customer("Taro", "Yamada", 15, 2, status);
customer.addCategory(STATE_ACTIVE);
return customer;
}
65. 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());
}
66. 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());
}
Custom Assertion
#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());
67. TEST(TestItemList, itemPush)
{
…
EXPECT_ITEM_EQ(expectItem, item);
}
重複部分を拡張Assertionで共通化
TEST(TestItemList, test_itemPush)
{
…
EXPECT_ITEM_EQ(expectItem2, item2);
}
Custom Assertion
#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());
70. Customer customer2("Taro", "Yamada", 15, 2, "HOGE|FUGA|HOGEHOGE");
何でもいいダミー値 テストしたい値
Parameterized Creation Method
Customer customer2 = createCustomer("HOGE|FUGA|HOGEHOGE");
ダミー値を隠し、重要なパラメータを強調する
重要なものを目立たせる
※隠ぺいはトレードオフなので要注意
71. 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());
}
72. 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つのテストメソッドで色々検証しているため少し読みにくい
分割で改善する
73. 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());
}
分割時に重複するメソッドをあらかじめ抜き出す
74. 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());
}
テストメソッドを小さくする
意図を読み取りやすい名前をつける
76. その他工夫
• テストコードの保守性は製品コード・テ
ストコード両方から支えられる
• 堅牢なユニットテストには堅牢な製品コ
ードのインターフェースが不可欠である
テスト 製品
コード コード
テストが内部メンバを細かく参照する形は
Fragile Testsになりえる
77. その他工夫
• テストコードの保守性は製品コード・テ
ストコード両方から支えられる
• 堅牢なユニットテストには堅牢な製品コ
ードのインターフェースが不可欠である
堅牢な
インターフェース
テスト 製品
コード コード
堅牢なインターフェースを製品コードが提供することで
テストコードの堅牢性を高められる
82. テストのツリー構造
Assertion
Test Case
Assertion
Test Suite
Test Case
ユニットテ
スト
Test Suite …
…
83. テストのツリー構造
細粒度・可読性
Assertion
Test Case
Assertion
Test Suite
ユニットテ 少数のAssertionでTestCaseを構成する
Test Case
スト (1条件/1テストケースなど
Test Suite テスト設計の最小構成単位を反映)
…
… テスト設計やテストの意図が
分かりやすくなる
84. テストのツリー構造
独立性、自己完結性の確保
Assertion
Test Case
Assertion
Test Suite 独立・無干渉
Test Case
ユニットテ 独立・無干渉
スト
Test Suite …
変更や流用の影響を局所化できる
…
85. テストのツリー構造
可読性など
Test Suite:テスト対象Class = n : 1 Assertion
※Fixture/外部コンポーネント等で分離 Test Case
通常は1:1
Assertion
Test Suit
Test Case
ユニットテ
スト
Test Suit …
構造に対する網羅を…
チェックしやすくする
93. 1:柔軟なズームアウト/
ズームインが必要
• ユニットテストを継続活用する場合、ユ
ニットテストはしばしばボトムアップに
実装されるため、構造設計は実装状況に
応じて柔軟に行わなければならない
構造設計
ズームアウト
ズームイン
実装
96. Test Suite
テストが
外部コンポーネントを
共有している
CameraCtrl 構造設計
ズームアウト
実装
MotorCtrl
(外部コンポーネント)
97. 外部コンポーネ
ント
Test Test
Suite Suite MockやStub等
テスタビリティの作りこみ
CameraCtrl 外部コンポーネントを
置換可能にする
構造設計
実装
98. Test Test MotorCtrl
Suite Suite
Dependency Injection
void CameraCtrl::CameraCtrl()
CameraCtrl {
_motorCtrl.open(...):
...
}
構造設計
void CameraCtrl::CameraCtrl(MemoryCtrl memoryCtrl)
ズームイン {
_motorCtrl = motorCtrl;
_motorCtrl.open(...):
実装 ...
}
99. Mock、 外部コンポー
右以外のテス
ト
ネントを用い MotorCtrl
Stub等 るテスト
Dependency Injection
CameraCtrl
実装
構造設計
101. 実装・構造設計の参考図書
• xUnit Test Patterns
– –Refactoring Test Code
• 読書会サイトに翻訳情報
あり
– <http://www.fieldnotes.jp/xutp/>
– 「xutp読書会」で検索
105. ユニットテストの継続的活用
においての留意点
• 作りすぎたテストを継続的に保守しようとす
ると保守性に悪影響を与える
テストコード
TEST(…)
{
製品コード …
EXPECT_EQ(…, Hoge());
void Hoge() }
{ TEST(…)
… {
} …
EXPECT_EQ(…, Hoge());
}
製品コードを変更する TEST(…)
{
と大量のテストがビルドエラー …
EXPECT_EQ(…, Hoge());
}
テスト設計を変更する
と修正が複数にまたがる
114. テストの目的に応じて柔軟に
設計技法を使い分ける
4で割り切れる N Y Y Y
機能的な保証
仕様 100で割り切れる N N Y Y テストファースト
400で割り切れる N N N Y
etc..
うるうどし N Y N Y
テスト設計 その他
//うるう年か判定する
bool isLeapYear(unsigned int year)
{
if (year % 400 == 0) 機械的なリファクタリング
{
return true; テスタビリティの評価
} etc..
if ((year % 4 == 0) && (year % 100 != 0))
コード構造 {
return true;
}
return false;
}
115. テストの目的に応じて柔軟に
設計技法や考え方を応用する
TEST(…)
任意の値でいいけど {
取り合えず境界値を …
指定しよう EXPECT_EQ(?, ?);
}
/**
仕様が厳密なので仕様ベー * @brief …詳細な関数仕様…
スのテスト設計ですまそう */
void fuga(…) {
}
bool hoge(unsigned int year)
仕様が不明だけど単純な分 {
if (year % 400 == 0)
岐構造なのでC3のテストを用 {
意してリファクタリングしよう return true;
}
if ((year % 4 == 0) && (year % 100 != 0))
{
return true;
}
return false;
}
120. テスト設計フロー
テスト条件、テスト設計技法等を整理する
テスト設計 テストケースを求める、等
テスト実装 テストケースの選定を行う
テストに用いる具体値を求める、等
123. 2. トップダウンでインターフェース設計 1. テスト条件を抽出
DB UART ●テスト条件リスト
通常テスト
テスト テスト ・DB
・タッチパネル
…
・メカニカルボタン
・UART通信
DB UART
… DB UART ….
スタブ スタブ
ユニットテストの制約となる
テストコードの実装 テスト設計プロセス
外部コンポーネントを抽出し、構造設計に反映させる
テストコードの
テスト設計
構造設計
テストコードの
実装 テスト実装
全体整合のとれた外部インターフェースを構築
125. 2. 変更リスクの少ないテストを作りこみ 1. テスト対象の安定度評価
テストの品質 Hoge Fuga Piyo …
ファイル 変更step/week
ゴールとなる水準
1 ○ ○
Hoge.cpp 0
2 ○ ○ Fuga.cpp 23
PIyo.cpp 133
3
… …
…
開発時間
安定したプログラムユニットを抽出し、イテレーティブな
テストコードの実装 テスト設計プロセス
ユニットテストの作りこみに反映する
テストコードの
テスト設計
構造設計
テストコードの
テスト実装
実装
テストの作りすぎによるデメリットを軽減する
128. テスト計画・テスト上位設計
• テスト目的、テスト対象
• 望ましいテストの構造
– テストクラスの粒度
• Feature/Class/Fixture/Method/Story
– 望ましいAssertionの数
– テストスイートの粒度
• テストをどのように配置するか
– テストレベル/テストタイプ/テストフェーズ/テストの目的
• 期待するテストの網羅性
– ソフトウェアのどこをテストするか
• コンポーネント/パッケージ/外部コンポーネント
– カバレッジはどの程度か
• 構造的カバレッジ/機能的カバレッジ