Home > On-Demand Archives > Workshops >
Test-Driven Development
James Grenning - Watch Now - EOC 2023 - Duration: 01:13:15
Test-Driven Development is a technical practice that supports Agile's iterative and incremental development cycle. TDD helps you quickly discover mistakes, preventing defects. You weave a test safety net as you grow your product's behavior one test at a time. This safety net supports you now and in the future to help you keep code working as you change it. Oh yeah, don't let me forget to tell you it's fun and once you learn it, you save time and money.
Maybe you have heard of Test-Driven Development but don't quite get it. A good way to understand TDD is to pair program with an experienced practitioner. We will start with a brief overview and demo of Test-Driven Development. In this interactive workshop, you can practice TDD in C. You don't need to install any tools for this workshop. You'll run the exercise on my exercise server. You will know what TDD is after this session. We'll conclude the workshop with a debrief on your experience.
Before attending this workshop, it is highly recommended that you watch this talk from the 2020 Embedded Online Conference.
It sounds like the thing left of an equal sign is not assignable, it is not a lvaue
(L-value)
You will need to provide a snip of the code if you need more help.
For ex: ADC1_RB = (uint16_t)(10); Is accepted by one compiler but gives an error on a different compiler (different microcontroller). Could a compiler block assignment of a register in a simulated environment in your experience? I tried searching for this specifically on the web but couldnt find anything. even asked ChatGPT ;)
It looks like you are using a silicon vendor-specific compiler. ADC1_RB is a device, right?
The silicon provider may be trying to be helpful, but as they extend the C compiler, your code is now handcuffed to their silicon. That's not a good thing for product software to be so firmly connected to the silicon. You should strive for minimal hardware dependencies and portable code.
The solution is pretty easy, you can either wrap all HW interactions in simple functions or macros.
Using functions and linker substitution
During test, you can provide a link-time test-double for void adc1_rb_write(uint16_t value)
for test, and link with this function for production.
void adc1_rb_write(uint16_t value)
{
ADC1_RB = value;
}
Using Macros and pre-processor substitution
You could use a header file that defines the hardware interactions. This is the one used in production. During test, you could make a test double structured similarly.
#define ADC1_RB_WRITE(value) ADC1_RB = (uint16_t)(value)
You would use two header files with the same name, and configure your build for production or test.
Perfect! Thats what I was looking for...I was thinking so much on it that I forgot that I can do what you just suggested!
Just finished all of the unit tests in the exercise.
The recommended API change to accommodate getting from an empty queue felt unsatisfying to me. I understand that it was done to avoid having to refactor almost all of the unit-tests. I think we were all aware (at the start of the exercise) that a "get" could fail. Should that API have been design differently from the start? i.e. bool CircularBuffer_Get(struct CircularBuffer *, int *);
Does this highlight a weakness in the TDD methodology?
Hi Barrie
Good question. There were two API changes, and both were painful.
CircularBuffer_Create()
changes to
CircularBuffer_Create(size_t capacity)
changes to
CircularBuffer_Create(2)
In my live training course, we spend time on this that we could not in the workshop. For starters, in my examples, I set up problems that we can later discuss. I want people doing the exercise to feel the pain. With that, they can better appreciate the solution. API changes are painful, and somewhat unavoidable over time. So, it is important to have a way to deal with them.
BTW: I have enabled more of the surrounding material. You can watch the "requirements" discussion where the API is identified. I have also enabled full demos of me doing the solution. You may want to check them out.
There are two takeaways from the live course about API evolution...
1) Try to get an API right, from the beginning. -- BTW: this is not always possible, as things change in unanticipated ways. In this example, we could have gotten CircularBuffer_Create(size_t capacity, int default_value)
right. We anticipated this interface in the video discussion.
2) How should we make an API change and not break all the clients at once? There is a mechanical refactoring approach that helps keep the code working while we change it. I would have said this in a live training class numerous times: "It's easier to keep a system working than to fix it after you break it.". Here are the steps to add the capacity
parameter to CircularBuffer_Create(void)
and not break existing code and tests.
- Test drive the new API function to have the needed behavior while using a temporary name like
CircularBuffer_Create2
. - One at a time, replace calls to
CircularBuffer_Create
withCircularBuffer_Create2
adding the neededcapacity
parameter. - Once all the
CircularBuffer_Create
calls are gone, deleteCircularBuffer_Create
- Rename
CircularBuffer_Create2
toCircularBuffer_Create
All existing tests passed through that change.
Regarding your proposed CircularBuffer_Get()
API, I prefer the programmable default return value over a modifiable parameter. I know modifiable parameters are very common in C programs, but I try to avoid them.
Thanks you so much for your detailed reply. I will look into seeing if our organization would want to benefit from your more in-depth training course.
11:21:49 From James Grenning to Everyone: #include11:23:20 From BarrieC to Everyone: and zero-initialize it, right? 11:26:26 From Sri Krishna Chaitanya Narumanchi to Everyone: But this was a mistake in the tests.. not the code.. How do we ensure that we can depend on the tests themselves? 11:41:21 From Charles Miller to Everyone: I'm gonna write me a minivan (Wally in Dilbert some time ago...) 11:50:09 From Stephane to James Grenning(Direct Message): One hour left... 11:53:20 From James Grenning to Everyone: https://wingman-sw.com/eoc23 11:53:22 From Conor Carr to Everyone: Unfortunately I have to leave. Can this cyber dojo be done later (on demand)? 11:53:39 From Nick Moore to Everyone: ^+1 (will this remain open for the week)? 11:53:52 From kendrick.hamilton@sasktel.net to Everyone: Or do you have a docker container we can pull and run? 11:54:29 From Nick Moore to Everyone: Awesome, thanks! 11:54:38 From Conor Carr to Everyone: thanks 11:55:15 From Carson to Everyone: how do we get to the exercise after registering? 11:55:52 From Carson to Everyone: Thank you! 11:56:12 From James Grenning to Everyone: cpputest starter project and docker container info https://github.com/jwgrenning/cpputest-starter-project 11:58:36 From Charles Miller to Everyone: Need to drop for a few minutes (team meeting called last minute...) 11:58:58 From Alice to Everyone: I am running into a problem where the Module 0 button is inactive. The HTML for the button shows "module access expired". Any ideas how to fix? 11:59:32 From Gonzalo to Everyone: I got lost after signing up. What was the Access to the excercise? 12:00:08 From Alice to Everyone: In the menu bar of the website there is a blue link with the words "Attendee (EOC-xx-x)" <- click that to access exercises 12:00:29 From Gonzalo to Everyone: Thanks 12:01:57 From Gonzalo to Everyone: I found the path, thank you 12:13:12 From James Grenning to Everyone: you can check later. I'll turn on the self-paced version fo the exercise 12:13:29 From James Grenning to Everyone: recorded intro, demos etc 12:14:17 From Carson to Everyone: how do you check if the circular buffer is empty? are you juste checking for the value of the output index to be 0? 12:14:58 From James Grenning to Everyone: initially yes. input_index == 0 means empty 12:15:21 From James Grenning to Everyone: later... input_index == output_index 12:15:44 From James Grenning to Everyone: it may change again as you dig into the problem/solution 12:18:00 From BarrieC to Everyone: thanks for that... I hate coding under pressure. :-) 12:28:12 From Carson to Everyone: should I push on past the "YOUR CODE GOT AHEAD OF YOUR TESTS!" if it is only complaining about the unexecuted "else return false" line? 12:31:20 From Carson to Everyone: Thanks James. When I delete the offending line, the code doesn't compile 12:32:21 From James Grenning to Stephane(Direct Message): could you hear my announcement? 12:32:28 From Johannes to Everyone: where do you run these test in a working legacy code base? 12:32:52 From Johannes to Everyone: I.e. I have KEIL where my C-code is compiled for an embedded target, where do I test that code? 12:35:00 From James Grenning to Everyone: https://linoit.com/users/wingman-sw/canvases/eoc-2023-parking-lot 12:38:15 From Thomas Wagner to Everyone: I realised I took a wrong path on the initial tasks, in other words I hacked my way through the tests and now I have to refactor everything for it to make any sense. I feel like I am hardly doing TDD in the process though... 12:38:49 From Charles Miller to Everyone: Are you saying "mock first", then add in HW calls? 12:45:41 From Stephane to James Grenning(Direct Message): 5 minutes 12:46:00 From James Grenning to Everyone: https://wingman-sw.com/articles/deep-stack-tracer-bullets-from-adc-to-browser 12:49:25 From moredatalesscenter to Everyone: Could you send a link to your website where you can see prices to book your course? I couldn't find it when I looked before. Thanks James. 12:49:58 From Jacob Beningo to Everyone: https://wingman-sw.com/ 12:50:54 From John Schofield to Everyone: Thank you. Even having (sort of) used TDD on a couple of projects, I felt that I learned a lot. 12:51:45 From Stephane to James Grenning(Direct Message): Thank you James! 12:51:58 From BarrieC to Everyone: very helpful. thanks 12:52:04 From Alice to Everyone: Thank you! 12:52:08 From Chris Coyne to Everyone: Excellent - thanks, James! 12:52:10 From Thomas Wagner to Everyone: Thank you very much! 12:52:12 From Aminul Haq to Everyone: Thank you 12:52:15 From Vishwa Perera to Everyone: Thank you James 12:52:17 From Dhanashree.Vaidya to Everyone: Thank you 12:52:27 From Agustin to Everyone: Thank you James 12:52:28 From Jeroen to Everyone: great session and fantastic book James. Thanks! 12:52:28 From James Grenning to Everyone: you are all welcome 12:52:35 From moredatalesscenter to Everyone: Reacted to "you are all welcome" with 👍 12:52:37 From Aaron Olowin to Everyone: Thanks James! 12:52:45 From Charles Miller to Everyone: Thanks James! 12:52:50 From Carson to Everyone: Thank you! 12:52:50 From Andrey Vlasov to Everyone: Thank you! 12:52:51 From Igor Semenov to Everyone: Thanks James'
Any way to avoid this error during register assignments? I use Unity framework.
Error[Pe137]: expression must be a modifiable lvalue