How to deal with dependency issues among modules

In general, firmware cannot be discussed without hardware dependency in embedded systems. For example, the led_controller module needs to use functions from the interface_gpio module to control hardware. When we need to unit test this type of interface, the microcontroller's specific register or interface function has to be mocked. The interface function can be auto-generated as a mock function in Ceedling during compile time.

  • Development Environment Example

image.png

  1. led_controller.h

  2. led_controller.c As you can see highlighted line from led_controller.c, it include interface_gpio.h which is dependency.

  3. interface_gpio.h

Here is a test file for led_controller:

image.png

This test was verbally mentioned in the first post. This is an actual implementation in Ceedling. But this is not enough yet to pass the test. Why?

Let me build and show how it fails:

image.png

It failed because that Ceedling does not know about Write_GPIO function and Read_GPIO function which are coming from interface_gpio module. When we call turn_on_led function, we can expect to call Write_GPIO function. When we call read_led_status function, we can expect to call Read_GPIO function. What we are missing here is a mock function to resolve this dependency issue.

  • How to auto-generate mock functions in Ceedling?

It can be done by simply adding the prefix "mock_" in the test file:

image.png

Let me build again:

image.png

Ceedling can clearly understand those functions now. It is always stopped at the first found error. To make it pass, we need to add the expected mock function call at the exact location.

Finally, a unit test is passed:

image.png

  • Place the expected mock function before the actual call!

while line16: turn_on_led() is executed, Write_GPIO(LED_PIN, LED_ON) will be called in production code. During unit testing, this can be one of the test criteria to make sure the dependency function is called with expected inputs. In this case, both parameters are the mock function's input since this function return type is void. I'd like to test parameter check and mock function call check so that Write_GPIO's mock function is called line15 which is before the actual call.

Likewise, line 17 is another mock function call. while line18 read_led_status() is executed, Read_GPIO(LED_ON) will be called in production code. In this case, one parameter is for input check and another parameter is to manipulate the return value of mock function. If the mock function return type is something, the mock function name's postfix would be ExpectAndReturn or IgnoreAndReturn.

You can find a list of mock functions after build: build\test\mocks. As writing a unit test, we do not need to see generated c file. Here is the auto-generated header file:

image.png

  • Generated mock function name convention

The return type is void↓Parameter check is required→

Yes

No

No

ExpectAndReturn

IgnoreAndReturn

Yes

Expect

Ignore

Here is Ignore keyword version when I do not care about the mock function's parameter check:

image.png

  • Conclusion

This post explains how Ceedling handles dependency between two modules. It is handy that Ceedling can automatically generate mock functions for users. It also provides two options for each function whether parameter value is needed to be tested or not. Next time, I will discuss StubWithCallback from the generated mock file.

Did you find this article valuable?

Support Hyunwoo Choi by becoming a sponsor. Any amount is appreciated!