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

2,442 views

Published on

会社で実施したTDDワークショップ 第2回めの資料です。

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
2,442
On SlideShare
0
From Embeds
0
Number of Embeds
75
Actions
Shares
0
Downloads
4
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

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

  1. 1. TDD Boot Camp 古川 剛啓 http://www.flickr.com/photos/utest/5609991407
  2. 2. アジェンダ 1. TDD概論 2. ワークショップ1(Fizz Buzz) 3. 振り返り
  3. 3. 注意事項 • 今日は学びの場です。納期とかコストとか関係 ありません。気楽に行きましょう。 • 失敗しても問題ありません。失敗から学ぶこと は多々あります。どんどん失敗しましょう。 • 何か1つでも持って帰って貰えたら嬉しいです。
  4. 4. プロジェクト終盤でありがちな状況 ✓単体テストを省略した ✓いつの間にかテストが壊れている ✓ テストが直ぐ壊れるのでテストするのを やめた ✓ テストに時間が掛かるのでテストしてな い ✓そもそもユニットテストなんかしてない http://www.flickr.com/photos/bagger2009/3630270346
  5. 5. http://www.flickr.com/photos/eivindw/2062037429
  6. 6. http://www.flickr.com/photos/edgarallanbro/7197914274 設計にセーフティネットを掛ける
  7. 7. よく見るV字モデル 要件定義 受入試験 基本設計 システム試験 詳細設計 結合試験 ユニットテスト / コーディング ここでやる。 対象はメソッド。
  8. 8. TDDの利点 • テストカバレッジが向上する • テスタビリティが向上する • メソッドや構造をシンプルに保つことが出来る • 書いたコードに自信が持てる • テストメソッドを読めばメソッドの使い方が分かる • その他 Twitter #TDDやっててよかったのまとめ http://togetter.com/li/258537 等
  9. 9. TDDの欠点 • コードを書くのに時間が掛る • 簡単ではない • 設計しなくなる http://www.flickr.com/photos/ismarbadzic/7250079044/
  10. 10. ユニットテストを難しくする幾つかのこと ✓ コードがハードウェアに依存している ✓ データベースやらネットワークに アクセスして状態が安定しない ✓ クラス間の結合が強くて連鎖的に インスタンス化する必要がある ✓ GUIばっか http://www.flickr.com/photos/tcrazyfish/6864732291
  11. 11. http://www.flickr.com/photos/planetschwa/99535218
  12. 12. SOLIDを意識する S Single Responsibility Principle 単一責任の原則 O Open-Closed Principle オープン・クローズドの原則 L Liskov Substitution Principle リスコフの置換原則 I Interface Segregation Principle インターフェース分離の原則 D Dependency Inversion Principle 依存関係逆転の原則
  13. 13. ちゃんとレイヤー化する 薄く保つ GUI等 ユーザーインターフェース層 (又はプレゼンテーション層) ドメイン層 (又はモデル層) インフラストラクチャ層 デバイスへのI/F等 DB HDD
  14. 14. TDDをやると本当は何がいいの? http://www.flickr.com/photos/mikedefiant/429402831
  15. 15. 今までのプログラミング (Debug Later Programming) Td Tfind Tfix 時間 バグ混入 バグ顕在化 バグ発見 バグFix バグが混入してから顕在化するまでに時間が掛かると、 バグがある場所を探すまでに時間が掛かる
  16. 16. テスト駆動開発 (Test Driven Development) Td Tfind Tfix 時間 バグ混入 バグ顕在化 バグ発見 バグFix バグ混入から顕在化するまでの時間を短くし、 バグがある場所を探すまでの時間を短縮する
  17. 17. ユニットテストフレームワークを使う JUnit, Google Test, CppUTest・・・ Mockフレームワークを使う jMock, GMock・・・ http://www.flickr.com/photos/kazk/198640938
  18. 18. と言うことで・・・
  19. 19. Google Testをインストールしてみよう • 以下のサイトからGoogle Mockのソースを ダウンロードする(Google Testのソースも 含まれている) http://code.google.com/p/googlemock/ • zipファイルを適当な場所に解凍する • READMEに従って、ビルド&インストール
  20. 20. で、実際に始める前に・・・
  21. 21. テストメソッドの構造  準備  オブジェクトの生成や状態のセット等を 行う  実行  テスト対象メソッドを実行する  検証  実行結果を検証し、Red/Greenを判定 する  後始末  オブジェクトを破棄する
  22. 22. テストコードのポイント  良いユニットテストはRepeatable (繰り返し可能、再現可能)  テストダブルを使いこなす  外部環境との界面にインターフェイスを作成し、テストダブルで置き換える  良いユニットテストは独立 (Independent) していなければならない  後始末を忘れずに行い、テストを独立させる  static を避け、テストメソッド間の依存関係を断つ  Assertion Roulette に注意する  目指すのは「テストメソッド毎にアサーションひとつ」(しかし、やりすぎは禁物)  カスタムアサーションを使う  パラメタライズドテスト(Parameterized Test)を使いこなす  Fragile Test (脆いテスト) に注意する  テストだけに使う部分の可視性を下げる  private メソッドを扱いたくなったら要注意  テストを設計ツールとして使う  テストコードのノイズを減らす  日本語テストメソッドを試してみる  シンプルなコードとテスト失敗時の情報のバランスを考える これらのポイントを考えながらテストコードを書くことで、テスト容易性を考慮した設計が見えてく るはずです。 出展: CodeIQ MAGAZINE https://codeiq.jp/magazine/2013/11/1475/
  23. 23. 準備 (1) 先ずは、テスト対象となるクラス/モジュール を準備する。 (2) テストクラスを準備する。 (3) ビルドするためのMakefileを書く ツールを準備しました!! ./prepairFiles [--lang=c | -lang=cpp] [--target=targetName] [--without_Makefile] className ・・・
  24. 24. 準備 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
  25. 25. 準備 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"
  26. 26. 準備 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
  27. 27. 準備 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
  28. 28. 準備 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. */ }
  29. 29. 準備 ファイルが生成されたら、srcディレクトリに移動 (cd src)して、 make test --targetに指定した名前+Testの実行ファイルが ビルドされる。(デフォルトは、「a.outTest」) main関数が無いからmakeはできないので注意
  30. 30. よく使うアサーション 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 文字列 の内容が等しくない
  31. 31. Google Testの使い方 戻り値がある関数をテストする テストクラス 関数 f() 関数の戻り値を期待値と比較する 例 EXPECT_EQ(1, Factorial(0)); EXPECT_EQ(1, sut->Factorial(0)); (c) (c++) 注 • c++のstd::stringは、このアサーションでテストする • cの文字列は、「ASSERT_STREQ」や「EXPECT_STREQ」等を使用する
  32. 32. Google Testの使い方 変数を更新する関数をテストする テストクラス 関数 f() 変数の値を期待値と比較する 例 Factorial(0); EXPECT_EQ(1, result); (c) sut->Factorial(0); EXPECT_EQ(1, sut->getResult()); (c++) 変数 v
  33. 33. よく使うアサーション 明示的な失敗 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 が例外を投げない
  34. 34. Google Testの使い方 例外がある関数をテストする テストクラス 関数 f() 例外発生の有無や例外の型を期待値と比較する 例 ASSERT_THROW( sut->parseArguments(argc, argv), std::invalid_argument ); 注 このアサーションは例外をキャッチする 依って、try/catchで囲んでも期待する動作をしない
  35. 35. Tips 関数の呼び出しをテストする (c) テストクラス 関数 f() 関数 g() 関数 g() 関数 f()が、関数 g()を正しく呼び出していることを確認する場合、 • 関数 g()をモックモジュールの関数 g()に置き換える • モックモジュールの関数 g()で引数の値の保存等、必要な処理 を実施 • アサーションにより保存した値等をチェック 注 • 同じ関数を持つオブジェクトファイルをリンクするとエラー(duplicate symbol)が出るので プロダクションコードはライブラリにしてモックモジュールより後にリンクする • テスト対象のモジュールが変わったら、再ビルドが必要かも
  36. 36. Tips privateメソッドをテストする (c++) テストクラス テスト対象クラス + 関数 f() # 関数 g() △ Spyクラス + 関数 g() privateメソッドをどうしてもテストしたい場合、 • privateメソッドをprotectedメソッドに変更する • テスト対象クラスを継承するSpyクラスを定義し、その中で publicに変更した同名のメソッドを定義する • Spyクラスのメソッドは、元のメソッドを呼び出すだけ 注 • privateメソッドをテストする必要性を検討する必要あり • privateメソッドは、publicメソッドから呼び出されているので何らかの形でテスト済みなハズ
  37. 37. Tips 標準出力への出力内容をテストする (c/c++) テストクラス 関数 f() stdout/stderrへの出力をテストしたい場合、 • Google Testに付属の関数を使用する • stdoutへの出力を変数に保存する • stdoutへの出力をファイルに保存する 標準出力
  38. 38. 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()を使用する。
  39. 39. 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を復帰
  40. 40. 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を復帰
  41. 41. Google Testによるパラメータ化テスト •パラメータ化テストとは、1つのテストメソッド内でテスト条件(引 数や期待値)を変えながらテストするもの。 •限界値分析や同値分割をやる際に有効 •テストクラスを継承すると共にWithParamInterface<T>を実装 •Tには、テスト対象メソッドの引数の型を指定 ::testing::WithParamInterface<T> △ ::testing::Test △ テストクラス △ パラメータ化テストクラス
  42. 42. 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) ); パラメータを準備
  43. 43. 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)) ); パラメータを準備
  44. 44. Red ー Green ー リファクタリング サイクル テストメソッドを書く テストをPassする最低限 のコードを実装する リファクタリング
  45. 45. 例:三角形のチェッククラスを考える TriangleクラスのcheckTriangleメソッド • 3辺が等しい三角形、”EqualateralTriangle”を返す • 2辺が等しい三角形、”IsoscelesTriangle”を返す • 三角形にならない時、”NotTriangle”を返す
  46. 46. 例:三角形のチェッククラスを考える 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"; }
  47. 47. 例:三角形のチェッククラスを考える 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"; }
  48. 48. 例:三角形のチェッククラスを考える 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"; }
  49. 49. 例:三角形のチェッククラスを考える 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"; }
  50. 50. 継続的にTDDをやるための材料 • 単体テストは設計(実装)の一部だと理解する • 単体テストフレームワークを使用する • テストコードも構成管理する • テストカバレッジを「Doneの定義」に含める • CI (Continuous Integration)サーバを導入する http://www.flickr.com/photos/roboppy/312248677
  51. 51. 最後に • TDD は簡単なプラクティスではありません。 • しかし、コード品質は確実に向上します。 • 一度や二度の失敗で諦めずに取り組みましょう。 継続は、力なり http://www.flickr.com/photos/picsbyperi/7457301384
  52. 52. ワークショップのルール • 先ずは、ペアで自己紹介して下さい。 • ナビゲータとドライバを決めて下さい。 • ナビゲータがテストコードを、 ドライバがプロダクションコードを書いて下さい。 • ナビゲータとドライバは適当に交代して下さい。 • 時間は30分です。
  53. 53. ワークショップ1 FizzBuzzクラス(又は関数)を作る • 引数が3の倍数の時、”Fizz”を返す • 引数が5の倍数の時、”Buzz”を返す • 引数が3と5の公倍数の時、”FizzBuzz”を返す • それ以外の時、引数の整数を文字列にして返す
  54. 54. ワークショップ2 FizzBuzzを作る さっき作ったクラス(関数)を使ってFizzBuzzを 作ってください • 引数は、1~100で • ディスプレイに表示するように
  55. 55. ワークショップ3 FizzBuzzを作る さっき作ったFizzBuzzを改修してください • 出力先をファイルに変更してください。 • ファイル名は任意でOKです。
  56. 56. ワークショップ4 FizzBuzzを作る さっき改修したFizzBuzzを更に改修してください • 出力先をファイルだけでなく、ディスプレイにも 表示するように変更してください。 • ファイル名は任意でOKです。
  57. 57. ワークショップ5 FizzBuzzを作る さっき改修したFizzBuzzを更に改修してください • 数字はディスプレイに、Fizz、Buzz、及びFizzBuzz はファイルに出力するようにしてください • ファイル名は任意でOKです。

×