The Physics of Debug Later Programming (DLP)
Test Driven Development for Embedded C
Presented by James Grenning at Agile China 2010 twitter: jwgrenning
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Td
Tnd
T x Time
Mistake made (bug injection)
Bug discovery Bug found Bug xed
As Td increases, Tfind increases dramatically Tfix is usually short, but can increase with Td
http://pragprog.com/titles/jgade/
www.renaissancesoftware.net james@renaissancesoftware.net
Presented at Agile China October 14, 2010 Test Driven Development
1
1
Copyright 2008-2010 James W. Grenning All Rights Reserved.
www.renaissancesoftware.net james@renaissancesoftware.net
2
2
Monday, October 18, 2010
Monday, October 18, 2010
The Physics of Test Driven Development
T d Tnd T x Time Mistake Mistake xed made Mistake Root cause found discovery
TDD Micro Cycle
Write a test Watch it fail Make it pass Refactor (clean up any mess) Repeat until done
When Td approaches zero, Tfind approaches zero In many cases, bugs are not around long enough to be considered bugs. See: http://www.renaissancesoftware.net/blog/archives/16
Copyright 2008-2010 James W. Grenning All Rights Reserved. www.renaissancesoftware.net james@renaissancesoftware.net
3
3
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
4
4
Monday, October 18, 2010
Monday, October 18, 2010
TDD State Machine in C/C++
Start
Test Driving a Circular Buffer (FIFO)
Choose a test No more tests All tests pass
Write the test
Compilation error
23
66
12
99
16
90
99
Compiles Clean
Refactor (Make it right) DONE! All tests pass Link error Make the test compile Compilation error
Out-Index
In-Index
Programming error
Make the test pass
New test fails
Make the test link
Link error
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
5
5
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
6
6
Monday, October 18, 2010
Monday, October 18, 2010
Create a Test-List (avoid analysis paralysis)
Circular Buffer Tests Initially Empty Transition to empty Transition to Full Transition from Full Put-Get FIFO Put to full Get from empty Filled but not wrapped around Wrap around
Set up a Test Fixture
TEST_GROUP(CircularBuffer) { CircularBuffer* buffer; void setup() { buffer = CircularBuffer_Create(); } void teardown() { CircularBuffer_Destroy(buffer); } }; TEST(CircularBuffer, TestName) { }
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
7
7
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
8
8
Monday, October 18, 2010
Monday, October 18, 2010
Make a bare Skeleton of the Production Code
#ifndef D_CircularBuffer_H #define D_CircularBuffer_H typedef struct CircularBuffer CircularBuffer; CircularBuffer * CircularBuffer_Create(int capacity); void CircularBuffer_Destroy(CircularBuffer *); #endif // D_CircularBuffer_H
Make a bare Skeleton of the Production Code
#include "CircularBuffer.h" #include <stdlib.h> struct CircularBuffer { int dummy; } ; CircularBuffer* CircularBuffer_Create() { CircularBuffer* self = calloc(capacity, sizeof(CircularBuffer)); return self; } void CircularBuffer_Destroy(CircularBuffer* self) { free(self); }
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
9
9
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
10
10
Monday, October 18, 2010
Monday, October 18, 2010
Choose a Test
Start
Which Test?
Choose a test No more tests All tests pass
Write the test
Compilation error
Compiles Clean
Refactor (Make it right) DONE! All tests pass Link error Make the test compile Compilation error
Out-Index
In-Index
41
New test fails Make the test link Link error
59
14
33
31
Programming error
Make the test pass
In-Index
Out-Index
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
11
11
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
12
12
Monday, October 18, 2010
Monday, October 18, 2010
Production Code is Grown One Test at a Time
Write a Test
Start
Choose a test No more tests
Write the test
Code without new feature
Code with new tested feature
DONE!
All tests pass
Compilation error
Compiles Clean
Refactor (Make it right) Make the test compile Link error Compilation error
All tests pass
Programming error
Make the test pass
New test fails
Make the test link
Link error
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
13
13
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
14
14
Monday, October 18, 2010
Monday, October 18, 2010
Start with a Test That is Easy to Get to Pass -- Designing the Interface
TEST_GROUP(CircularBuffer) { CircularBuffer* buffer; void setup() { buffer = CircularBuffer_Create(); } void teardown() { CircularBuffer_Destroy(buffer); } }; TEST(CircularBuffer, ShouldBeEmptyAfterCreate) {
CHECK(CircularBuffer_IsEmpty(buffer));
Programming error
Make the Test Compile
Start
Choose a test No more tests All tests pass
Write the test
Compilation error
Compiles Clean
Refactor (Make it right) DONE! All tests pass Link error Make the test compile Compilation error
Make the test pass
New test fails
Make the test link
Link error
}
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
15
15
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
16
16
Monday, October 18, 2010
Monday, October 18, 2010
Make the Test Compile
#ifndef D_CircularBuffer_H #define D_CircularBuffer_H typedef struct CircularBuffer CircularBuffer;
Make the Test Link
Start
Choose a test
Write the test
CircularBuffer * CircularBuffer_Create(int capacity); void CircularBuffer_Destroy(CircularBuffer *); int CircularBuffer_IsEmpty(CircularBuffer *); #endif // D_CircularBuffer_H
No more tests All tests pass Compilation error
Compiles Clean
Refactor (Make it right) DONE! All tests pass Link error Make the test compile Compilation error
Programming error
Make the test pass
New test fails
Make the test link
Link error
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
17
17
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
18
18
Monday, October 18, 2010
Monday, October 18, 2010
Make the Test Link and Intentionally Fail
#include "CircularBuffer.h" #include <stdlib.h> struct CircularBuffer { int dummy; } ; ... not all code shown ... int CircularBuffer_IsEmpty(CircularBuffer* self) { return 0; }
Watch it Fail
CircularBufferTest.cpp:48: error: Failure in TEST(CircularBuffer, EmptyAfterCreation) CHECK(CircularBuffer_IsEmpty(buffer)) failed
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
19
19
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
20
20
Monday, October 18, 2010
Monday, October 18, 2010
Make the Test Pass
Start
Write no More Code than is Necessary to Pass the Current Test
Hard code behavior or partial implementations are OK and often preferred.
Choose a test No more tests All tests pass
Write the test
Compilation error
Compiles Clean
Refactor (Make it right) DONE! All tests pass Link error Make the test compile Compilation error
Programming error
Make the test pass
New test fails
Make the test link
Link error
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
21
21
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
22
22
Monday, October 18, 2010
Monday, October 18, 2010
Make the Test Pass
#include "CircularBuffer.h" #include <stdlib.h> struct CircularBuffer { int dummy; } ; ... not all code shown ... int CircularBuffer_IsEmpty(CircularBuffer* self) { return 1; }
Repeat until You Run Out of Tests
The first tests drive the interface definition Later tests complete the behavior You will think of more tests as you go
Write the new test or Add it to the test list
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
23
23
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
24
24
Monday, October 18, 2010
Monday, October 18, 2010
Tests are FIRST
Thank you: Tim Ottinger
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Fast Independent Repeatable Self-validating Timely
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
Adaptation for Embedded Software
What is scarce during embedded development?
25
25
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
26
26
Monday, October 18, 2010
Monday, October 18, 2010
Hardware is Scarce!
It does not exist. It is being used by someone else.
Hardware is Scarce!
It does not exist. It is being used by someone else.
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
27
27
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
28
28
Monday, October 18, 2010
Monday, October 18, 2010
Hardware Has Bugs of its Own
bool BlindsScheduler::DoesLightRespondToday (TimeService::Day reactionDay) { int today = timeService->GetDay(); if (reactionDay == TimeService::EVERYDAY) return true; if (reactionDay == today) return true; if (reactionDay == TimeService::WEEKEND && (TimeService::SATURDAY == today || TimeService::SUNDAY == today)) return true; if (reactionDay == TimeService::WEEKDAY && today >= TimeService::MONDAY && today <= TimeService::FRIDAY) return true; return false; } void BlindsScheduler::CheckEvent(ScheduledBlindsEvent* blindsEvent) { if (blindsEvent->extraRandomEventPreventer > 0) { blindsEvent->extraRandomEventPreventer--; return; } if (!DoesLightRespondToday(blindsEvent->day)) return; if (timeService->GetMinute() != blindsEvent->minute + blindsEvent->randomMinutes) return; switch (blindsEvent->blindsType) { HorizontalBlinds* theBlinds; case Horizontal: switch (blindsEvent->operation) { case Raise:
Tests Must be Fast and We Can Run Them NOW
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
29
29
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
30
30
Monday, October 18, 2010
Monday, October 18, 2010
Use Your Development System for a Test Bed
Multi-targeted code. Must beware of hardware and OS dependencies. Object Oriented approach to Dependency Management. Avoid inefficiencies
Waiting for hardware. Waiting for restart. Waiting for downloads. Waiting for long compiles. Debugging on the target.
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
But There are Risks with Development System Tests
Architecture differences
Word size Big-endian Little-endian Alignment
Compiler differences Library differences Execution differences
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
31
31
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
32
32
Monday, October 18, 2010
Monday, October 18, 2010
TDD Adaptation for Embedded Development
Stage 1 Stage 2 Stage 3 Stage 4 Stage 5
TDD Adaptation for Embedded Development
Stage 1 Stage 2 Stage 3 Run Tests in the Eval Hardware or Simulator Stage 4 Stage 5
Write a Test Make it Pass Refactor
Compile for Target Processor
Run Tests in the Eval Hardware
Run Tests in Target Hardware
Acceptance Tests
Write a Test Make it Pass Refactor
Compile for Target Processor
Run Tests in Target Hardware
Run Manual Tests in Target
More Frequent
More Frequent
Less Frequent
Less Frequent
See : http://renaissancesoftware.net/files/articles/ProgressBeforeHardware.pdf
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
33
33
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
34
34
Monday, October 18, 2010
Monday, October 18, 2010
Automate Stages 2-5
Source Code Repository
Continuous Integration Server
Test Management System
TDD and the Code in the Middle
(most of the code!)
Eval/Reference System Developer Workstation Target System Simulator
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
35
35
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
36
36
Monday, October 18, 2010
Monday, October 18, 2010
What about Code in the Middle
Testing a Module in the Middle
The test case takes the role of the client
The Module under test is tested independent of other modules
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
The servers might need to be stubbed out
37
37
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
38
38
Monday, October 18, 2010
Monday, October 18, 2010
A Module with Collaborators
Every minute, the RTOS wakes up the Light Scheduler. If it is time for one of the lights to be controlled, the LightController is told to turn on/off the light.
Admin Console Light Scheduler +addSchedule() +removeSchedule() +wakeUp()
<<anonymous callback>>
Program to Interfaces
Separate interface and implementation as separate entities. This design has good separation of responsibilities
Admin Console Light Scheduler +addSchedule() +removeSchedule() +wakeUp()
<<anonymous callback>>
Light Controller + on(id) + off(id)
Time Service + getDay() + getTimeOfDay() + setPeriodicAlarm() RTOS
Light Controller + on(id) + off(id)
<<interface>>
Time Service + getDay() + getTimeOfDay() + setPeriodicAlarm()
<<implements>>
<<interface>>
Hardware
<<implements>>
Real Light Controller
Real Time Service
Hardware
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
RTOS
www.renaissancesoftware.net james@renaissancesoftware.net
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
39
39
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
40
40
Monday, October 18, 2010
Monday, October 18, 2010
Testing a Module with Collaborators
Use the real collaborators if you can. Use fakes when you must.
Light Scheduler Test Light Scheduler +addSchedule() +removeSchedule() +wakeUp()
Testing the Scheduler
TEST(LightScheduler, ScheduleOnTodayNotTimeYet) { LightScheduler_ScheduleTurnOn(3, EVERYDAY, 1000); FakeTimeSource_SetMinute(999); LightScheduler_Wakeup(); LONGS_EQUAL(LIGHT_NA, FakeLightController_getState(3));
Light Controller + on(id) + off(id)
<<interface>>
Time Service + getDay() + getTimeOfDay() + setPeriodicAlarm()
<<implements>>
<<interface>>
} TEST(LightScheduler, ScheduleOnTodayItsTime) { LightScheduler_ScheduleTurnOn(3, EVERYDAY, 1000); FakeTimeSource_SetMinute(1000); LightScheduler_Wakeup(); LONGS_EQUAL(LIGHT_ON, FakeLightController_getState(3)); }
<<implements>>
Light Controller Spy
Fake Time Service
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
41
41
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
42
42
Monday, October 18, 2010
Monday, October 18, 2010
Light Controller Interface
#ifndef D_LightController_H #define D_LightController_H enum { MAX_LIGHTS = 32 }; void void void void LightController_Create(void); LightController_Destroy(); LightController_On(int id); LightController_Off(int id); // D_LightController_H
Fake Light Controller Interface
#ifndef D_FakeLightController_H #define D_FakeLightController_H #include "LightController.h" typedef enum { NO_ID = -1, UNKNOWN_STATE = -1, LIGHT_OFF = 0, LIGHT_ON = 1} LightState; int FakeLightController_getLastId(void); int FakeLightController_getLastState(void); int FakeLightController_getState(int id); #endif // D_FakeLightController_H
#endif
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
43
43
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
44
44
Monday, October 18, 2010
Monday, October 18, 2010
Fake Light Controller Implementation
#include "FakeLightController.h" #include "memory.h" static int lastId; static int lastLevel; void LightController_Create(void) { lastId = NO_ID; lastLevel = UNKNOWN_STATE; } void LightController_Destroy() { }
Fake Light Controller Implementation
int FakeLightController_getLastId(void) { return lastId; } int FakeLightController_getLastState(void) { return lastLevel; } void LightController_On(int id) { lastId = id; lastLevel = LIGHT_ON; } void LightController_Off(int id) { lastId = id; lastLevel = LIGHT_OFF; }
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
45
45
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
46
46
Monday, October 18, 2010
Monday, October 18, 2010
Testing the Scheduler
TEST(LightScheduler, NoScheduleNothingHappens) { LightScheduler_Wakeup(); LONGS_EQUAL(NO_ID, FakeLightController_getLastId()); LONGS_EQUAL(UNKNOWN_STATE, FakeLightController_getLastState()); } TEST(LightScheduler, ScheduleOnTodayNotTimeYet) { LightScheduler_ScheduleTurnOn(3, EVERYDAY, 1200); FakeTimeService_SetMinute(1199); LightScheduler_Wakeup(); LONGS_EQUAL(NO_ID, FakeLightController_getLastId()); LONGS_EQUAL(UNKNOWN_STATE, FakeLightController_getLastState()); }
Testing the Scheduler
TEST(LightScheduler, ScheduleWeekdayItsSunday) { LightScheduler_ScheduleTurnOn(3, EVERYDAY, 1200); FakeTimeService_SetDay(SUNDAY); FakeTimeService_SetMinute(1200); LightScheduler_Wakeup(); LONGS_EQUAL(3, FakeLightController_getLastId()); LONGS_EQUAL(LIGHT_ON, FakeLightController_getLastState()); }
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
47
47
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
48
48
Monday, October 18, 2010
Monday, October 18, 2010
When must you use fakes?
When the Code under test cannot be fully tested with the real collaborators. Examples:
When manual verification is needed (Printed output, LEDs) When the results change (Time, random events) When failures need to be simulated (Network down) When hardware is involved (LEDs, USB, Sensors, Motors, IO pins, Flash...) Operating system calls (RTOS)
Breaking Dependencies with Function Pointers
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
49
49
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
50
50
Monday, October 18, 2010
Monday, October 18, 2010
Loosening Compile-Time and Run-Time Dependencies
Problem: an existing C function needs to be stubbed out for some tests...
//From the .h file ... int RandomMinuteGenerator_Get(); ...
Convert Direct Dependency to Function Pointer
Modify the function declaration in the header file
Add extern Add (*)
//From the .c file ... int RandomMinuteGenerator_Get() { ...the implementation... } ...
//From the .h file ... extern void (*RandomMinuteGenerator_Get)(); ...
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
51
51
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
52
52
Monday, October 18, 2010
Monday, October 18, 2010
Convert Direct Dependency to Function Pointer
Modify the function definition in the .c file
Change function name by appending _impl Hide the _impl by making it static
No Changes are Needed to Callers of the Function Pointer
Calling code does not change. No pointer dereferencing is needed
//from some production code... ... RandomMinuteGenerator_Get(); ...
Add defining instance of the function pointer and initialize the pointer with the the _impl
//From the .c file ... static int RandomMinuteGenerator_Get_impl() { ...the implementation... } int (*RandomMinuteGenerator_Get)() = RandomMinuteGenerator_Get_impl;
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
53
53
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
54
54
Monday, October 18, 2010
Monday, October 18, 2010
Runtime Substitution Test setup and teardown
Override the function by assigning the test version of the function during setup Dont forget to restore the original
void setup() { RandomMinuteGenerator_Get = RandomMinuteGenerator_Get_fake; } void teardown() { RandomMinuteGenerator_Get = RandomMinuteGenerator_Get_impl; }
Use CppUTests UT_PTR_SET
void setup() { UT_PTR_SET(RandomMinuteGenerator_Get, RandomMinuteGenerator_Get_fake); } void teardown() { }
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
55
55
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
56
56
Monday, October 18, 2010
Monday, October 18, 2010
Message Flow for Flash Memory Block Erase with Error
FlashDriver FlashDevice
Self Verifying Mock Objects
Flash_Program(offset, data)
IOWrite(CommandRegister, 0x40)
IOWrite(offset, data)
IORead(StatusRegister) b7 == 0 IORead(StatusRegister) b7 == 1, other bits == 0 IORead(offset) FlashSuccess data
Repeats while b7 == 0
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
57
57
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
58
58
Monday, October 18, 2010
Monday, October 18, 2010
Flash Program Flow Chart How Many Tests are Needed?
Start b3 == 0 Program Command Write 0x40 to 0x0
Mock Object
Problem - Complex collaborator interactions cannot be captured with a simple spy. Solution - Mock Object
A Mock Object is a Test Double that verifies that the code being tested interacts with its collaborator properly. The test tells the mock
The expected calls In the expected order What to return.
NO
Vpp Error
YES
Write data to address
b4 == 0
NO
Program Error
How do you test these errors in the target?
YES
Read status register Protected Block Error
b1 == 0
NO
NO YES
b7 == 1 Start/Stop Clear status Write 0xFF to 0x0
YES
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
59
59
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
60
60
Monday, October 18, 2010
Monday, October 18, 2010
Flash Driver Test
TEST(Flash, ProgramSucceedsReadyImmediately1) { int result = 0; MockIO_Expect_IOWrite(0, 0x40); MockIO_Expect_IOWrite(0x1000, 0xBEEF); MockIO_Expect_IORead(0, 1<<7); MockIO_Expect_IORead(0x1000, 0xBEEF); result = Flash_Program(0x1000, 0xBEEF); LONGS_EQUAL(0, result); MockIO_Assert_AllExpectationsMet(); }
void void void void void
Mock Flash Read/Write
//MockIO.h #include "IOReadWrite.h" MockIO_Create(int maxExpectations); MockIO_Destroy(); MockIO_Expect_IOWrite(ioAddress_t offset, uint16_t data); MockIO_Expect_IORead(ioAddress_t offset, uint16_t data); MockIO_Assert_AllExpectationsMet();
//IOReadWrite.h #include <stdint.h> typedef uint32_t ioAddress_t; typedef uint16_t ioData_t; ioData_t IORead(ioAddress_t offset ); void IOWrite(ioAddress_t offset, ioData_t data);
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
61
61
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
62
62
Monday, October 18, 2010
Monday, October 18, 2010
Mock Flash Read/Write
//MockIO.c typedef struct Expectation { int kind; ioAddress_t addr; ioData_t value; } Expectation; static static static static static Expectation* expectations = 0; int setExpectationCount; int getExpectationCount; int maxExpectationCount; int failureAlreadyReported = 0;
Capturing Flash Read/Write Expectations
void MockIO_Expect_IOWrite(ioAddress_t addr, ioAddress_t value) { failWhenNoMoreRoomForExpectations(report_too_many_write_expectations); recordExpectation(FLASH_WRITE, addr, value); } void MockIO_Expect_IORead(ioAddress_t addr, ioAddress_t value) { failWhenNoMoreRoomForExpectations(report_too_many_read_expectations); recordExpectation(FLASH_READ, addr, value); }
//Snip void MockIO_Create(int maxExpectations) { expectations = malloc(sizeof(Expectation)*maxExpectations); memset(expectations, 0, sizeof(expectations)); setExpectationCount = 0; getExpectationCount = 0; maxExpectationCount = maxExpectations; failureAlreadyReported = 0; }
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
63
63
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
64
64
Monday, October 18, 2010
Monday, October 18, 2010
Mock Flash Read
ioData_t IORead(ioAddress_t addr) { setExpectedAndActual(addr, -1); failWhenNotInitialized(); failWhenNoUnusedExpectations(report_read_but_out_of_expectations); failWhen(expectationIsNot(FLASH_READ), report_expect_write_was_read); failWhen(expectedAddressIsNot(addr), report_read_wrong_address); return expectations[getExpectationCount++].value; }
Mock Flash Write
void IOWrite(ioAddress_t addr, ioData_t value) { setExpectedAndActual(addr, value); failWhenNotInitialized(); failWhenNoUnusedExpectations(report_write_but_out_of_expectations); failWhen(expectationIsNot(FLASH_WRITE), report_expect_read_was_write); failWhen(expectedAddressIsNot(addr), report_write_does_not_match); failWhen(expectedDataIsNot(value), report_write_does_not_match); getExpectationCount++; }
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
65
65
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
66
66
Monday, October 18, 2010
Monday, October 18, 2010
All Expectations Must Be Met
void MockIO_Assert_AllExpectationsMet() { if (failureAlreadyReported) return; failWhenNotAllExpectationsUsed(); }
Minimize DOH!
Debug On Hardware
www.renaissancesoftware.net james@renaissancesoftware.net
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
67
67
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
68
68
Monday, October 18, 2010
Monday, October 18, 2010
On-line
Test harnesses
[CPPTEST] www.sourceforge.org, project CppUTest [FITNESSE] www.fitnesse.org
The End
Groups
http://groups.yahoo.com/group/testdrivendevelopment http://groups.yahoo.com/group/AgileEmbedded
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
69
69
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
70
70
Monday, October 18, 2010
Monday, October 18, 2010
See Embedded TDD and Related Blogs and Papers
http://www.renaissanceSoftware.net http://www.renaissancesoftware.net/blog/
Embedded TDD Zune Bug: Test Driven Bug Fix Learning Tests are Free! TDD as a Design Rot Prevention System Crashing Your Way to Great Legacy C Tests TDD and the Big Framework Part Bug Fixes and TDD Physics of Test Driven Development Tests vs. Short Term Cache Between Your Ears Embedded Systems Conference FAQ I miss constructors Who says you cant test drive a device driver? Why are You Still Using C? Planing Poker Agile Embedded Software Development (ESC) Launching Extreme Programming at a Process Intensive Company (IEEE) Test Driven Development for Embedded Software Progress Before Hardware Agile Times - Containing Progress Before Hardware Test-Driven Development for Embedded C++ Programmers
References and Sources
[RCM] Robert C. Martin, Clean Code, 2008 [SLAD] Craig Larman and Bas Voode, Scaling Lean & Agile Development [WELC] Michael Feathers, Working Effectively with Legacy Code [REF] Martin Fowler, Refactoring [TDD] Kent Beck, Test-Driven Development, 2003 [xUNIT] Gerard Meszaros - xUnit Testing Patterns, 2008 [MOCK] Tim Mackinnon and Steve Freeman and Philip Craig, EndoTesting: Unit Testing with Mock Objects, 2001 [TD] Lasse Koskela, Test Driven, 2007 [LESSONS] Bret Pettichord Cem Kaner, James Bach. Lessons Learned in Software Testing: A Context-Driven Approach. John Wiley & Sons, New York, NY, 2002.
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
71
71
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
72
72
Monday, October 18, 2010
Monday, October 18, 2010
Available now in Beta
http://pragprog.com/titles/jgade/
In print early 2011
Copyright 2008-2010 James W. Grenning All Rights Reserved. For use by training attendees.
Presented at Agile China October 14, 2010 Test Driven Development
www.renaissancesoftware.net james@renaissancesoftware.net
73
73
Monday, October 18, 2010