SMARTBOARD 驅動程式安裝說明 - 清華大學圖書館 · SMARTBOARD 驅動程式安裝說明 本驅動程式用於互動及觸控使用 若為一般投影簡報使用,不需安裝此驅動程式。
2014/02: 嵌入式測試驅動開發
-
Upload
agilecommunity -
Category
Documents
-
view
415 -
download
1
description
Transcript of 2014/02: 嵌入式測試驅動開發
嵌入式測試驅動開發Hugo
2/13/2014
我們熟悉的開發方式
先寫程式,再寫測試找 bug
Test-After Development (TAD)
問題是 ...沒出錯不知道哪裡有 bug
bug 小時候放著不管
長大很恐怖
有什麼方法 ...能盡早把 bug 找出來 ?
先寫測試找 bug ,再寫程式
Test-Driven Development (TDD)
TestDrivenDevelopment測試驅動開發
TDD 循環
• 紅燈:增加一個運行失敗,甚至無法編譯的測試。
• 綠燈:快速修改,只做能讓測試通過的工作。
• 黃燈:重構,移除重複改進代碼可讀性。
《 Test Driven Development: By Example》
測試 是 TDD 的關鍵
建立Setup建立
Setup運行
Exercise運行
Exercise驗證
Verify驗證
Verify拆卸
Teardown拆卸
Teardown
單元測試四階段
自動化單元測試框架
測試框架測試框架
測試案例 #1測試案例 #1 測試案例 #2測試案例 #2 ...
產品代碼 ( 受測代碼 )產品代碼 ( 受測代碼 )
report 測試結果 #1測試結果 #1
測試結果 #2測試結果 #2
...
proj/ src/objs/ func.o
fun.c
projTest/ src/ funTest.cobjs/ func.o
funcTest.o
目標環境
測試環境
TDD 的好處• 產生 bug 更少• 除錯時間更短• 不會說謊的文件• 改善設計• 監督進度• 內心平靜
嵌入式測試驅動開發有什麼特別的地方嗎 ?
依賴硬體,浪費時間
雙目標開發 - 解開硬體的依賴
CodeCode
單元測試單元測試 運行系統運行系統
開發環境開發環境 目標環境目標環境
嵌入式 TDD 循環
《 Test-Driven Development for Embedded C》
階段一 階段二 階段三 階段四 階段五
開發環境TDD
交叉編譯相容測試
評估版單元測試
目標硬體單元測試
目標硬體驗收測試
很頻繁 不頻繁
嵌入式驅動開發工具
• Unity– C 語言自動化測試框架– 用 Ruby Script 安裝測
試• CppUTest
– C/C++ 自動化測試框架– 用 Ruby Script 將測試
轉換成 Unity 測試
Unity http://throwtheswitch.org/, CppUTest http://cpputest.github.io/
範例 - 開發 LED 驅動程式
這玩意我 10 行程式就搞定了
這麼簡單還需要測試嗎 ?MyLedDriver.cMyLedDriver.c
#define LED_REGISTER 0x80001234void LedDriver_Set (uint16_t value){ *((uint16_t *)LED_REGISTER) = value;}uint16_t LedDriver_Get (void){ return *((uint16_t *)LED_REGISTER);}
#define LED_REGISTER 0x80001234void LedDriver_Set (uint16_t value){ *((uint16_t *)LED_REGISTER) = value;}uint16_t LedDriver_Get (void){ return *((uint16_t *)LED_REGISTER);}
LedUser.cLedUser.c
void TurnOnLed8 (void){ LedDriver_Set (1 << 8);}
void TurnOnLed8 (void){ LedDriver_Set (1 << 8);}
沒有測試把關再怎麼簡單都可能出錯
TDD 會怎麼做 ?
測試列表
先寫出測試失敗的測試LedDriverTest.cLedDriverTest.c
TEST (LedDriver, LedsOffAfterCreate){ uint16_t virtualLeds = 0xffff; LedDriver_Create (&virtualLeds); TEST_ASSERT_EQUAL (0, virtualLeds);}
TEST (LedDriver, LedsOffAfterCreate){ uint16_t virtualLeds = 0xffff; LedDriver_Create (&virtualLeds); TEST_ASSERT_EQUAL (0, virtualLeds);}
LedDriver.cLedDriver.c
void LedDriver_Create (uint16_t* address){}
void LedDriver_Create (uint16_t* address){}
Dependence Injection ( 依賴注入 )
用最簡單的方式讓測試通過LedDriver.cLedDriver.c
void LedDriver_Create (uint16_t* address){ *address = 0;}
void LedDriver_Create (uint16_t* address){ *address = 0;}
$ makecompiling LedDriver.cLinking LedDirver_testsRunning LedDriver_tests.OK (1 tests, 1 ran, 1 checks, 0 ignored)
再增加一個測試LedDriverTest.cLedDriverTest.c
TEST (LedDriver, TurnOnLedOne){ uint16_t virtualLeds; LedDriver_Create (&virtualLeds); LedDriver_TurnOn (1); TEST_ASSERT_EQUAL (1, virtualLeds);}
TEST (LedDriver, TurnOnLedOne){ uint16_t virtualLeds; LedDriver_Create (&virtualLeds); LedDriver_TurnOn (1); TEST_ASSERT_EQUAL (1, virtualLeds);}
LedDriver.cLedDriver.c
void LedDriver_TurnOn (int ledNumber){}
void LedDriver_TurnOn (int ledNumber){}
寫 hardcode 讓測試通過LedDriver.cLedDriver.c
static uint16_t* ledsAddress;
void LedDriver_Create (uint16_t* address){ ledsAddress = address; *ledsAddress = 0;}
void LedDriver_TurnOn (int ledNumber){ *ledDriver = 1;}
static uint16_t* ledsAddress;
void LedDriver_Create (uint16_t* address){ ledsAddress = address; *ledsAddress = 0;}
void LedDriver_TurnOn (int ledNumber){ *ledDriver = 1;}
Data Encapsulation ( 資料封裝 )
等等 ! 這不科學啊 ...hardcode 的實作有問題
增加非當下測試所需的代碼會降低捕捉各種 bug 的動力
先仿冒再建造保持小而專注的測試
TDD 像是過河的墊腳石
喔 ... 這就是 TDD 嗎 ?
真正的系統長的像這樣
依賴像一串肉粽
控制輸入 & 監測輸出
直接輸入 直接輸出
間接輸入 間接輸出
斷開魂結、斷開鎖鍊
測試 替身
何時使用測試替身 ?•獨立於硬體• 注入難以產生的輸入• 加速緩慢的合作者• 依賴不穩定的事情•取代未被實現的服務•對於難以配置的事物的依賴
測試替身的替換技術• 編譯時期,透過 Preprocessor 替換•連結時期,透過 Object File 替換•執行時期,透過 Function Pointer 替換
Test Stub
《 xUnit Test Patterns: Refactoring Test Code》
FakeTimeService.cFakeTimeService.c
static int theMinute;
void FakeTimeService_SetMinute (int minute){ theMinute = minute;}void TimeService_GetTime (Time* time){ time->minuteOfDay = theMinute;}
static int theMinute;
void FakeTimeService_SetMinute (int minute){ theMinute = minute;}void TimeService_GetTime (Time* time){ time->minuteOfDay = theMinute;}
FakeTimeServiceTest.cFakeTimeServiceTest.c
TEST (FakeTimeService, Set){ Time time;
FakeTimeService_SetMinute (42); TimeService_GetTime (&time); LONGS_EQUAL (42, time.minuteOfDay);}
TEST (FakeTimeService, Set){ Time time;
FakeTimeService_SetMinute (42); TimeService_GetTime (&time); LONGS_EQUAL (42, time.minuteOfDay);}
Test Spy
FakeTimeService.cFakeTimeService.c
static int theMinute;
int FakeTimeService_GetMinute (void){ return theMinute;}void TimeService_SetDay (Time* time){ theMinute = time->minuteOfDay;}
static int theMinute;
int FakeTimeService_GetMinute (void){ return theMinute;}void TimeService_SetDay (Time* time){ theMinute = time->minuteOfDay;}
FakeTimeServiceTest.cFakeTimeServiceTest.c
TEST (FakeTimeService, Get){ Time time;
time->minuteOfDay = 42; TimeService_SetTime (&time); LONGS_EQUAL (42, FakeTimeService_GetMinute());}
TEST (FakeTimeService, Get){ Time time;
time->minuteOfDay = 42; TimeService_SetTime (&time); LONGS_EQUAL (42, FakeTimeService_GetMinute());}
Mock Object
Flash Program 序列圖 - 成功的情形
FlashTest.cFlashTest.c
TEST (Flash, WriteSucceeds){ int result = 0;
MockIO_Expect_Write (0, 0x40); MockIO_Expect_Write (0x1000, 0xBEEF); MockIO_Expect_ReadThenReturn (0, 1<<7); MockIo_Expect_ReadThenReturn (0, 1<<7); MockIO_Expect_ReadThenReturn (0x1000, 0xBEEF);
Result = Flash_Write (0x1000, 0xBEEF);
LONG_EQUAL (0, result); MockIO_Verify_Complete();}
TEST (Flash, WriteSucceeds){ int result = 0;
MockIO_Expect_Write (0, 0x40); MockIO_Expect_Write (0x1000, 0xBEEF); MockIO_Expect_ReadThenReturn (0, 1<<7); MockIo_Expect_ReadThenReturn (0, 1<<7); MockIO_Expect_ReadThenReturn (0x1000, 0xBEEF);
Result = Flash_Write (0x1000, 0xBEEF);
LONG_EQUAL (0, result); MockIO_Verify_Complete();}
CMock http://throwtheswitch.org/, CppUMock http://cpputest.github.io/
怎樣才算好設計 ?可測性、可讀性、可維護性
借用物件導向的設計原則
用 C 語言實現資料封裝
WallClock.hWallClock.h
void WallClock_SetTime (Time time);Time WallClock_GetTime (void);void WallClock_SetTime (Time time);Time WallClock_GetTime (void);
Watch.hWatch.h
typedef struct WatchStruct Watch;void SetTime (Watch* watch, Time time);Time GetTime (Watch* watch);
typedef struct WatchStruct Watch;void SetTime (Watch* watch, Time time);Time GetTime (Watch* watch);
《 interface》Watch
《 interface》Watch
DigitalWatch
DigitalWatch
Watch.hWatch.h
typedef struct WatchStruct { void (*SetTime) (Watch*, Time); Time (*GetTime) (Watch*);} Watch;
typedef struct WatchStruct { void (*SetTime) (Watch*, Time); Time (*GetTime) (Watch*);} Watch;
DigitalWatch.cDigitalWatch.c
typedef struct DigitalWatchStruct { Watch* base; Time time;} DigitalWatch;
Watch* DigitalWatch_Create (void) { DigitalWatch* self = malloc(sizeof(DigitalWatch)); self->base->SetTime = mySetTime; self->base->GetTime = myGetTime; return (Watch*)self;}
typedef struct DigitalWatchStruct { Watch* base; Time time;} DigitalWatch;
Watch* DigitalWatch_Create (void) { DigitalWatch* self = malloc(sizeof(DigitalWatch)); self->base->SetTime = mySetTime; self->base->GetTime = myGetTime; return (Watch*)self;}
用 C 語言實現類別繼承
《 interface》Watch
《 interface》Watch
DigitalWatch
DigitalWatch
MechanicWatch
MechanicWatch
DigitalWatch.cDigitalWatch.c
static void mySetTime (Watch* watch, Time time){ DigitalWatch* self = (DigitalWatch*)watch; self->time = time;}
static void mySetTime (Watch* watch, Time time){ DigitalWatch* self = (DigitalWatch*)watch; self->time = time;}
MechanicWatch.cMechanicWatch.c
static void mySetTime (Watch* watch, Time time){ MechanicWatch* self = (MechanicWatch*)watch; self->time = time;}
static void mySetTime (Watch* watch, Time time){ MechanicWatch* self = (MechanicWatch*)watch; self->time = time;}
用 C 語言實現類別多型User.cUser.c
void doSetTime (Watch* watch, Time time) { watch->SetTime (time);}
void doSetTime (Watch* watch, Time time) { watch->SetTime (time);}
UserUser
《 interface》Watch
《 interface》Watch
DigitalWatch
DigitalWatch
MechanicWatch
MechanicWatch
UserUser
SOLID 設計原則SRP 單一職責OCP 開放封閉LSP 替換原則ISP 介面分離DIP 依賴倒轉
《 Agile Software Development, Principle, Patterns, and Practices》
PocketWatch
PocketWatch
隨著代碼不斷增加系統架構變得越來越混亂
代碼的壞味道• 重複代碼• 壞名字• 義大利麵式代碼• 長函式• 眼花撩亂的布林運算• 重複的 switch/case• 邪惡的嵌套• 依戀情結• 參數太多• 注釋、注釋掉的代碼• 條件編譯
CodeSmells.cCodeSmells.c
void foobar (Time* time, Work* work){ if (work->item != NULL) { Day day = time->dayOfWeek; int min = time->minuteOfDay; if ((day >= MONDAY && day <= FRIDAY) && ((min >= 9*60 && min <= 12*60) || (min >= 13*60 && min <= 18*60)) { if (work->type == CODING) doCode (work->item); else doDebug (work->item); // doSomethingImportant (); } }}
void foobar (Time* time, Work* work){ if (work->item != NULL) { Day day = time->dayOfWeek; int min = time->minuteOfDay; if ((day >= MONDAY && day <= FRIDAY) && ((min >= 9*60 && min <= 12*60) || (min >= 13*60 && min <= 18*60)) { if (work->type == CODING) doCode (work->item); else doDebug (work->item); // doSomethingImportant (); } }}
重構是“在不改變當前外部行為的條件下,對現有代碼進行修改的過程。”
《 Refactoring: Improving the Design of Existing Code》
RefactoredCode.cRefactoredCode.c
static bool isWorkTime (Time* time){ ...}static void workHard (Work* work){ ...}
void workInOffice (Time* time, Work* work){ if (!isWorkTime(time)) return;
workHard (work);}
static bool isWorkTime (Time* time){ ...}static void workHard (Work* work){ ...}
void workInOffice (Time* time, Work* work){ if (!isWorkTime(time)) return;
workHard (work);}
唉呦 不錯 重構這個屌
但實際動手你肯定會講一句話 ...
砍掉重練比較快
在真實世界裡我們必須要跟遺留代碼戰鬥
遺留代碼 = 沒有測試的代碼
修改遺留代碼• 發現改動點• 找到測試點• 斷開依賴• 寫測試• 改動和重構
《Working Efficiently with Legacy Code》
測試點•接縫 (函式呼叫 )•全域變數 /感知變量
• 除錯輸出• 嵌入監控
把遺留代碼放到測試框架中
TestLegacyCode.cTestLegacyCode.c
void addNewLegacyCTest(){ makeItCompile(); makeItLink(); while (runCrashes()) { findRuntimeDependency(); fixRuntimeDependency(); } addMoreLegacyCTests();}
void addNewLegacyCTest(){ makeItCompile(); makeItLink(); while (runCrashes()) { findRuntimeDependency(); fixRuntimeDependency(); } addMoreLegacyCTests();}
TDD 聽來不錯,但是 ...
不知怎麼開始 ?
先來場 Coding Dojo 吧
參考資料《 Test-Driven Development for Embedded C 》《 Test Driven Development: By Example 》《 xUnit Test Patterns: Refactoring Test Code 》《 Refactoring: Improving the Design of Existing Code 》《 Working Efficiently with Legacy Code 》《 Agile Software Development, Principle, Patterns, and Practices
》《 Design Patterns for Embedded Systems in C: An Embedded Soft
ware Engineering Toolkit 》