For additional information:
- Martin Fowler, article on Continuous Integration
- Lasse Koskela, Driving on CruiseControl Part1 and Part2
- CruiseControl Configuration Reference
The following situation is not too uncommon. An embedded system project is getting closer to a process gate. All of a sudden it is time to write documentation because documents are "demanded by the process". This is ridiculous! Process does not demand or want anything, even less likely it is going to NEED anything. Dominating parts in a project system are the people, not the process. What do they NEED in order to progress effectively? That's correct from the back row - communication. Everyone agrees with that, but why is communication so strongly associated with paper document in engineering, or project management? Are we still paying the price of the stereo type created decades ago, that engineers can not communicate? If so, what makes project managers think that engineers could write?
So back to the project. Project has built working prototypes with incremental development and short time-boxing in cross-disciplined engineering teams. Electronic schematics, part lists, price estimates, firmware, mechanical drawings, thermal management results, power handling results, EMC data etc. are available as natural outcome of experimenting. What is missing if we need to COMMUNICATE the project, its status, the risk etc.?
I heard that! "Documentation" you whispered. This is what the process demands, and for some werd reason this is interpreted as Word documentation. So, the team gets into this unproductive mode and writes Word ducumentation at velocity of 2pg/month. Gate passed, everyone happy?
Dead wrong. The first downside is that now the documentation is done. We proceed with prototypes and create more new knowledge on detailed level. This is the spearhead of new knowledge created in this project. Unfortunately since time is wasted at early stages we will be in a hurry towards the end of the project. This means documentation at this crucial stage, at the end, has less focus. Not to mention the frustrated engineers that have already documented a lot without any feedback (the dinosaur process just swallows the papers). It is harder to convince them about the importance of documentation at this later stage. This documentation, which James Shore called Enable Future Work -documentation, remains missing in many so called traditional projects.
The second downside comes from the fact that we actually now have the Get Work Done documentation, but not the high quality Enable Future Work -documentation. This documentation is passed to the next project team as a starting point for their work. This documentation contains obvious facts, which are worthless. More importantly it has flaws and speculations which were found wrong during further experimentation. Unfortunately this new information typically never gets updated into these documents. So there is lots of information that is not true and does not go one-to-one with the actual final design. The new team now spends more time figuring out whether to believe the document or the design. That is if they are not experienced enough to always go with the actual design.
My Three Day Faceoffs -post describes the power of face-to-face communication over paper document in practice.
Get Work Done -(Word) documentation is concidered harmfull.
1. I have been mostly writing about running the tests on host (PC). We are using Cygwin environment. We compile our unit tests with gcc and tests are written on top of embUnit framework. Reporting is done both on screen and as XML.
This article at Object Mentor by James Grenning defines an embedded TDD cycle. This demonstrates the fact that embedded programming is the extreme end of programming. We not only have to worry about all the normal problems in software engineering, but we need to fight these challenges with limited processor resources, poor tools, hard real-time deadlines, stakeholders from other engineering disciplines, usually without formal computer science education etc. This means that having unit test suite alone is not enough for testing the embedded system. The other arrows in embedded TDD cycle illustrate this.
As can be seen in Grenning's article unit testing on host is an important corner piece in the puzzle. Doing TDD has so many other benefits that come as a side products that it is worth the hassle. Unit testing the modules will help you to write clear consistent interfaces and enforce good programming practices.
2. Keeping the modules small and less complex it should not be too much work to port the tests to be run at target environment as well. Nancy has done some work on running the tests in target. In microcontrollers today it is easy to find some kind of serial port to report the results back to PC to be further formatted. Even if you do not have HW pheripheral on board, it's not a big deal to write serial communication driver with bit-banging. This however always needs some arrangements (getting the code to target, resetting the target etc.) In my (limited) experience these little arrangements may just be too much of a barrier to cause the tests not to be run by developers. At least not regularly enough for TDD. Fully automating this at high level is a bit difficult. It needs to be remembered that this would still only partly resolve the problem since the unit test code seldom fits the target memory with production code.
This said, when I get the unit testing on host really going, I will focus on running the same tests on target - and automated. There just is no rush, you cannot get to nirvana over night.
Nancy Van Schooenderwoert will give a presentation at ESC Boston 2006 about their CATS C unittest framework. That should be interesting.
***** worker.h *****
typedef struct worker
{
void (*theJob)( void );
} _worker;
extern void WORKER_construct( BYTE meIndex,
void (*function)( void ) ) ;
extern void WORKER_doTheJob( BYTE meIndex );
extern void setPORTA( void );
extern void clearPORTA( void );
***** worker.c *****
#include "uc_defs.h"
#include "worker.h"
worker theWorker[2];
#define me theWorker[meIndex]
void WORKER_construct( BYTE meIndex,
void (*function)(void) )
{
me.theJob = function;
}
void WORKER_doTheJob( BYTE meIndex )
{
me.theJob();
}
void setPORTA( void )
{
PORTA = 0xFF;
}
void clearPORTA( void )
{
PORTA = 0x00;
}
***** main.c *****
#include "uc_defs.h"
#include "worker.h"
void main( void )
{
WORKER_construct(0, &clearPORTA );
WORKER_construct(1, &setPORTA );
while( 1 )
{
if( PORTB & 0x01 )
{
WORKER_doTheJob(0); // executes clearPORTA
}
else
{
WORKER_doTheJob(1); // executes setPORTA
}
}
}