Home > On-Demand Archives > Workshops >

Test-Driven Development

James Grenning - Watch Now - Duration: 01:13:15

Test-Driven Development
James Grenning

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.

M↓ MARKDOWN HELP
italicssurround text with
*asterisks*
boldsurround text with
**two asterisks**
hyperlink
[hyperlink](https://example.com)
or just a bare URL
code
surround text with
`backticks`
strikethroughsurround text with
~~two tilde characters~~
quote
prefix with
>

harshil22
Score: 0 | 10 months ago | 1 reply

Any way to avoid this error during register assignments? I use Unity framework.
Error[Pe137]: expression must be a modifiable lvalue

jwgrenningSpeaker
Score: 0 | 10 months ago | 1 reply

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.

harshil22
Score: 0 | 10 months ago | 1 reply

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 ;)

jwgrenningSpeaker
Score: 0 | 10 months ago | 1 reply

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.

harshil22
Score: 0 | 10 months ago | no reply

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!

BarrieC
Score: 0 | 10 months ago | 1 reply

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?

jwgrenningSpeaker
Score: 0 | 10 months ago | 1 reply

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 with CircularBuffer_Create2 adding the needed capacity parameter.
  • Once all the CircularBuffer_Create calls are gone, delete CircularBuffer_Create
  • Rename CircularBuffer_Create2 to CircularBuffer_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.

BarrieC
Score: 0 | 10 months ago | no reply

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:
	#include 

11: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'

OUR SPONSORS