More Related Content Similar to 2014/02: 嵌入式測試驅動開發 Similar to 2014/02: 嵌入式測試驅動開發 (20) More from AgileCommunity (11) 2014/02: 嵌入式測試驅動開發13. 自動化單元測試框架
測試案例 #1 測試案例 #2 ...
測試案例 #1 測試案例 #2
report
測試結果 #1
測試結果 #1
測試框架
測試框架
測試結果 #2
測試結果 #2
產品代碼 ((受測代碼 ))
產品代碼 受測代碼
...
proj/
fun.c
objs/
projTest/
src/
func.o
src/
funTest.c
objs/
func.o
funcTest.o
目標環境
測試環境
19. 嵌入式驅動開發工具
• Unity
– C 語言自動化測試框架
– 用 Ruby Script 安裝測
試
• CppUTest
– C/C++ 自動化測試框架
– 用 Ruby Script 將測試
轉換成 Unity 測試
Unity http://throwtheswitch.org/, CppUTest http://cpputest.github.io/
22. 這麼簡單還需要測試嗎 ?
MyLedDriver.c
MyLedDriver.c
#define LED_REGISTER 0x80001234
#define LED_REGISTER 0x80001234
void LedDriver_Set (uint16_t value)
void LedDriver_Set (uint16_t value)
{
{
*((uint16_t *)LED_REGISTER) = value;
*((uint16_t *)LED_REGISTER) = value;
}
}
uint16_t LedDriver_Get (void)
uint16_t LedDriver_Get (void)
{
{
return *((uint16_t *)LED_REGISTER);
return *((uint16_t *)LED_REGISTER);
}
}
LedUser.c
LedUser.c
void TurnOnLed8 (void)
void TurnOnLed8 (void)
{
{
LedDriver_Set (1 << 8);
LedDriver_Set (1 << 8);
}
}
26. 先寫出測試失敗的測試
LedDriverTest.c
LedDriverTest.c
TEST (LedDriver, LedsOffAfterCreate)
TEST (LedDriver, LedsOffAfterCreate)
{
{
uint16_t virtualLeds = 0xffff;
uint16_t virtualLeds = 0xffff;
LedDriver_Create (&virtualLeds);
LedDriver_Create (&virtualLeds);
TEST_ASSERT_EQUAL (0, virtualLeds);
TEST_ASSERT_EQUAL (0, virtualLeds);
}
}
LedDriver.c
LedDriver.c
void LedDriver_Create (uint16_t* address)
void LedDriver_Create (uint16_t* address)
{
{
}
}
Dependence Injection ( 依賴注入 )
28. 再增加一個測試
LedDriverTest.c
LedDriverTest.c
TEST (LedDriver, TurnOnLedOne)
TEST (LedDriver, TurnOnLedOne)
{
{
uint16_t virtualLeds;
uint16_t virtualLeds;
LedDriver_Create (&virtualLeds);
LedDriver_Create (&virtualLeds);
LedDriver_TurnOn (1);
LedDriver_TurnOn (1);
TEST_ASSERT_EQUAL (1, virtualLeds);
TEST_ASSERT_EQUAL (1, virtualLeds);
}
}
LedDriver.c
LedDriver.c
void LedDriver_TurnOn (int ledNumber)
void LedDriver_TurnOn (int ledNumber)
{
{
}
}
29. 寫 hardcode 讓測試通過
LedDriver.c
LedDriver.c
static uint16_t* ledsAddress;
static uint16_t* ledsAddress;
void LedDriver_Create (uint16_t* address)
void LedDriver_Create (uint16_t* address)
{
{
ledsAddress = address;
ledsAddress = address;
*ledsAddress = 0;
*ledsAddress = 0;
}
}
void LedDriver_TurnOn (int ledNumber)
void LedDriver_TurnOn (int ledNumber)
{
{
*ledDriver = 1;
*ledDriver = 1;
}
}
Data Encapsulation ( 資料封裝 )
43. FakeTimeService.c
FakeTimeService.c
static int theMinute;
static int theMinute;
void FakeTimeService_SetMinute (int minute)
void FakeTimeService_SetMinute (int minute)
{
{
theMinute = minute;
theMinute = minute;
}
}
void TimeService_GetTime (Time* time)
void TimeService_GetTime (Time* time)
{
{
time->minuteOfDay = theMinute;
time->minuteOfDay = theMinute;
}
}
FakeTimeServiceTest.c
FakeTimeServiceTest.c
TEST (FakeTimeService, Set)
TEST (FakeTimeService, Set)
{
{
Time time;
Time time;
}
}
FakeTimeService_SetMinute (42);
FakeTimeService_SetMinute (42);
TimeService_GetTime (&time);
TimeService_GetTime (&time);
LONGS_EQUAL (42, time.minuteOfDay);
LONGS_EQUAL (42, time.minuteOfDay);
45. FakeTimeService.c
FakeTimeService.c
static int theMinute;
static int theMinute;
int FakeTimeService_GetMinute (void)
int FakeTimeService_GetMinute (void)
{
{
return theMinute;
return theMinute;
}
}
void TimeService_SetDay (Time* time)
void TimeService_SetDay (Time* time)
{
{
theMinute = time->minuteOfDay;
theMinute = time->minuteOfDay;
}
}
FakeTimeServiceTest.c
FakeTimeServiceTest.c
TEST (FakeTimeService, Get)
TEST (FakeTimeService, Get)
{
{
Time time;
Time time;
}
}
time->minuteOfDay = 42;
time->minuteOfDay = 42;
TimeService_SetTime (&time);
TimeService_SetTime (&time);
LONGS_EQUAL (42, FakeTimeService_GetMinute());
LONGS_EQUAL (42, FakeTimeService_GetMinute());
48. FlashTest.c
FlashTest.c
TEST (Flash, WriteSucceeds)
TEST (Flash, WriteSucceeds)
{
{
int result = 0;
int result = 0;
MockIO_Expect_Write (0, 0x40);
MockIO_Expect_Write (0, 0x40);
MockIO_Expect_Write (0x1000, 0xBEEF);
MockIO_Expect_Write (0x1000, 0xBEEF);
MockIO_Expect_ReadThenReturn (0, 1<<7);
MockIO_Expect_ReadThenReturn (0, 1<<7);
MockIo_Expect_ReadThenReturn (0, 1<<7);
MockIo_Expect_ReadThenReturn (0, 1<<7);
MockIO_Expect_ReadThenReturn (0x1000, 0xBEEF);
MockIO_Expect_ReadThenReturn (0x1000, 0xBEEF);
Result = Flash_Write (0x1000, 0xBEEF);
Result = Flash_Write (0x1000, 0xBEEF);
}
}
LONG_EQUAL (0, result);
LONG_EQUAL (0, result);
MockIO_Verify_Complete();
MockIO_Verify_Complete();
CMock http://throwtheswitch.org/, CppUMock http://cpputest.github.io/
52. 用 C 語言實現類別繼承
Watch.h
Watch.h
typedef struct WatchStruct {
typedef struct WatchStruct {
void (*SetTime) (Watch*, Time);
void (*SetTime) (Watch*, Time);
Time (*GetTime) (Watch*);
Time (*GetTime) (Watch*);
} Watch;
} Watch;
DigitalWatch.c
DigitalWatch.c
typedef struct DigitalWatchStruct {
typedef struct DigitalWatchStruct {
Watch* base;
Watch* base;
Time time;
Time time;
} DigitalWatch;
} DigitalWatch;
Watch* DigitalWatch_Create (void) {
Watch* DigitalWatch_Create (void) {
DigitalWatch* self = malloc(sizeof(DigitalWatch));
DigitalWatch* self = malloc(sizeof(DigitalWatch));
self->base->SetTime = mySetTime;
self->base->SetTime = mySetTime;
self->base->GetTime = myGetTime;
self->base->GetTime = myGetTime;
return (Watch*)self;
return (Watch*)self;
}
}
《 interface 》
《 interface 》
Watch
Watch
Digital
Digital
Watch
Watch
53. 用 C 語言實現類別多型
User.c
User.c
void doSetTime (Watch* watch, Time time) {
void doSetTime (Watch* watch, Time time) {
watch->SetTime (time);
watch->SetTime (time);
}
}
DigitalWatch.c
DigitalWatch.c
static void mySetTime (Watch* watch, Time time)
static void mySetTime (Watch* watch, Time time)
{
{
DigitalWatch* self = (DigitalWatch*)watch;
DigitalWatch* self = (DigitalWatch*)watch;
self->time = time;
self->time = time;
}
}
MechanicWatch.c
MechanicWatch.c
static void mySetTime (Watch* watch, Time time)
static void mySetTime (Watch* watch, Time time)
{
{
MechanicWatch* self = (MechanicWatch*)watch;
MechanicWatch* self = (MechanicWatch*)watch;
self->time = time;
self->time = time;
}
}
User
User
《 interface 》
《 interface 》
Watch
Watch
Digital
Digital
Watch
Watch
Mechanic
Mechanic
Watch
Watch
54. SOLID 設計原則
User
User
《 interface 》
《 interface 》
Watch
Watch
Digital
Digital
Watch
Watch
Mechanic
Mechanic
Watch
Watch
Pocket
Pocket
Watch
Watch
SRP 單一職責
OCP 開放封閉
LSP 替換原則
ISP 介面分離
DIP 依賴倒轉
《 Agile Software Development, Principle, Patterns, and Practices 》
57. CodeSmells.c
CodeSmells.c
void foobar (Time* time, Work* work)
void foobar (Time* time, Work* work)
{
{
if (work->item != NULL) {
if (work->item != NULL) {
Day day = time->dayOfWeek;
Day day = time->dayOfWeek;
int min = time->minuteOfDay;
int min = time->minuteOfDay;
if ((day >= MONDAY && day <= FRIDAY) &&
if ((day >= MONDAY && day <= FRIDAY) &&
((min >= 9*60 && min <= 12*60) ||
((min >= 9*60 && min <= 12*60) ||
(min >= 13*60 && min <= 18*60)) {
(min >= 13*60 && min <= 18*60)) {
if (work->type == CODING)
if (work->type == CODING)
doCode (work->item);
doCode (work->item);
else
else
doDebug (work->item);
doDebug (work->item);
// doSomethingImportant ();
// doSomethingImportant ();
}
}
}
}
}
}
66. 測試點
• 接縫 ( 函式呼叫 )
• 全域變數 / 感知變
量
• 除錯輸出
• 嵌入監控
71. 參考資料
《
《
《
《
《
《
《
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
Software Engineering Toolkit 》
Editor's Notes Bug 是什麼? bug 是應注意,而未注意的錯誤。
許願式則是測試 -> 寫碼 -> 重構,就前兩步驟順序互換。如果你先許願,然後去實現它。你會發現結果只會實現你許過的願望,不會發生實現你沒許過的願望。以軟體開發術語來說,就是花最少力氣完成目標程式的開發,剛剛好符合單元測試的"規範"。絕對不會過度設計(over design),而且寫出來的目標程式碼 100% 可以被測試,測試涵蓋率百分百。
增量式軟體開發技術、先有測試再寫程式
測試要小、能自動
測試是對需求的理解
測試是功能許下的承諾
測試驗證新功能可運作
測試保證舊功能沒被破壞
易於運行的測試: 獨立、可重複、自我檢驗、全自動
測試四階段
建立: 創建測試的前置條件
運行: 對系統進行操作
驗證: 檢查預期的輸出
拆卸: 把被測系統恢復到測試前的初始狀態
產生較少的 bug
除錯時間變短
產生可執行的規格
永遠有最新的文件
改善設計
監督進度
硬體遲遲沒準備好,壓縮軟體開發時程
硬體昂貴、資源稀少,開發人員空等待
硬體也會有 bug,除錯更加困難
建構目標系統時間漫長
上傳(燒錄)韌體時間漫長
Cross-compiler 昂貴,授權數量有限
TDD 對嵌入式開發的幫助:
硬體可用前或硬體很貴很稀少,通過獨立於硬體的方式校驗產品代碼
開發環境除錯,減少編譯、上傳的次數與時間
減少在目標硬體的除錯時間
隔離軟硬體交互影響
軟硬體解耦合有助於改善設計
TDD 微循環 - 運行頻繁,幾分鐘一次。代碼與測試都在這開發。可做靜態分析。
編譯器相容性測試 - 移植性問題 (header file、library、語言工具)。採用新語言特性、函式庫才驗證。
評估版上運行單元測試 - 驗證編譯後的代碼在本地開發系統和目標處理器上行為一致。CI 的一部分,每天運行。
目標硬體運行單元測試 - 挑戰目標硬體有限資源 (記憶體)。可能需要批次測試不同測試套件。
目標硬體運行驗收測試 - 手動驗證不能完全自動化的測試
Unity
用 C 實現的自動化測試框架
用 Ruby Script 解決測試安裝問題
CppUTest
用 C/C++ 實現的自動化測試框架
不需理解 C++
附帶 Ruby Script 可將 CppUTest 測試轉換成 Unity 測試
使用者點亮第八顆燈,卻清除了其他燈號的設定
測試列表由需求衍生而來
測試列表定義你對需要完成功能的最好理解
* 依賴注入
先測試介面,在測試內部實現
但是 測試是對的
Bob Martin TDD三條原則
除非是讓一個失敗的單元測試通過,否則不要寫產品代碼
不要寫比足以失敗更多的單元測試,建構失敗也可以
不要寫比足以讓單元測試通過更多的產品代碼
剛接觸TDD的人會感到困惑
增加不是現在測試所需要的代碼,會降低寫讓代碼不產生bug所需的所有測試
有測試之前增加代碼會增加複雜度
增量式前進
先仿冒在建造 - 一旦去仿冒比正確實做更麻煩時,就造出真正的東西
保持小而專注的測試
綠燈之後要重構
Test automation is about verifying the behavior of the SUT.
The actual behavior of the SUT can be verified in two ways.
Verify the states of various objects affected by the SUT
Verify the interactions between SUT and its DOCs
檢驗狀態
In State Verification, we assert that the SUT and any objects it returns are in the expected state after we have exercised the SUT. We “pay no attention to the man behind the curtain.”
檢驗行為
In Behavior Verification, we focus our assertions on the indirect outputs (outgoing interfaces) of the SUT. This typically involves replacing the DOC with something that facilitates observing and verifying the outgoing calls.
1. We can verify the states of various objects affected by the SUT by extracting each state using an observation point and using assertions to compare it to the expected state.
2. We can verify the behavior of the SUT directly by using observation points inserted between the SUT and its depended-on component (DOC) to monitor its interactions (in the form of the method calls it makes) and comparing those method calls with what we expected.
注入難以產生的輸入 >> 觸發不太可能執行到的路徑
加速緩慢的合作者 >> 資料庫、網路、慢速儲存裝置
依賴不穩定的事情 >> 時鐘
對開發事物的依賴 >> 有機會探索CUT對於現在未實現的服務的需求
對於難以配置的事物的依賴 >> 資料庫
Using a Test Stub as a control point for indirect inputs. One way to use a control point to inject indirect inputs into the SUT is to install a Test Stub in place of the DOC. Before exercising the SUT, we tell the Test Stub what it should return to the SUT when it is called. This strategy allows us to force the SUT through all its code paths.
Using a Test Spy as an observation point for indirect outputs of the SUT. One way to implement Behavior Verification is to install a Test Spy in place of the target of the indirect outputs. After exercising the SUT, the test asks the Test Spy for information about how it was used and compares that information to the expected behavior using assertions.
Using a Mock Object as an observation point for indirect outputs of the SUT. Another way to implement Behavior Verification is to install a Mock Object in place of the target of the indirect outputs. As the SUT makes calls to the DOC, the Mock Object uses assertions to compare the actual calls and arguments with the expected calls and arguments.
可測性 (Testable)是什麼?
什麼樣的代碼才有可測性
模組的概念 - 系統中一個完備的部份,有明確定義的介面
抽象資料類型(ADT)中,模組的數據被當成私有成員對待 (封裝)
不要把選擇邏輯分佈在系統各處,而是只在一處做選擇,並且將系統配置成支持這種情形 (工廠模式)
壞名字、眼花撩亂的布林運算、邪惡的嵌套、依戀情結
解決軟體開發的技術債
壞名字、眼花撩亂的布林運算、邪惡的嵌套、依戀情結
沒有測試的代碼是壞代碼。無論寫得多好,多美,多麼物件導向或封裝良好。有了測試,才能快速和可驗證地改變代碼。沒有測試,實際上無法知道代碼倒底變好還是變壞。
測試點 - 發現改動點後,考慮如何測試它。在哪一點上面能更自然地感知代碼發生了什麼,代碼又是從哪裡得到輸入的。
斷開依賴 - 把遺留代碼放入測試框架中,或是得到某些測試點的訪問,必須斷開依賴。
寫測試 - 有了測試點,就能寫一些測試來識別及保護遺留代碼的行為。
接縫 - 函式呼叫在代碼不同部分形成了接縫。接縫就是你可以改變程序的行為,卻不用修改那個位置。
可以使用測試替身來”監視”傳給合作者數據的地方,讓測試可以確保被測試代碼給合作者傳去正確的指令。
讓它能編譯 - 為測試函式提供數據結構和參數 (隨意使用空指標和簡單數值) ,增加 #include 讓測試文件可以編譯
讓它能連結 - 處理未解決的外部符號 (unresolved external symbol) ,連結部份產品代碼或使用測試替身
運行會崩潰 - 因為之前傳入未初始化或不恰當初始化的數據
找到運行時依賴 - 使用 debugger 找到崩潰點 (非法訪問記憶體)
修正運行時依賴 - 初始化數據、函式指標