Simple project that shows how to use Unity and FFF (Fake Function Framework) for unit testing applications developed with ESP-IDF.
This example makes use of the Unity component already present on ESP-IDF and illustrates a way to integrate test doubles into your application's unit tests.
- Requirements
- Example layout
- Unit tests for components
- Shared test doubles
- How to add this to your project
- How to run the example
For running this example you will need the following:
- ESP32 toolchain
- ESP-IDF v3.3
- Target hardware with at least one GPIO that can be used as digital input. Have on hand this GPIO number, you will need it to build the application project.
The example follows the common ESP-IDF based project layout, with custom components
in the components/
directory, the main
component and the correspondig makefiles.
This is the application project.
esp32-unity-fff-demo - Application project directory
- components - Application project components
+ button
+ mockable
+ testable
+ main - Application project main component
+ test - Test project directory
Makefile - Application project makefile
README.md - This README
sdkconfig.defaults - Application project default settings
UNLICENSE
In addition to this, there is a similar structure inside the test/
folder,
this is the test project and will run the application components tests.
esp32-unity-fff-demo - Application project directory
+ components - Application project components
+ main - Application project main component
- test - Test project directory
- components - Test project components
+ fff - Fake Function Framework component directory
+ mocks - Shared test doubles component
+ main - Test project main component
Makefile - Test project makefile
sdkconfig.defaults - Test project default settings
test_component.mk - Custom makefile for tests components using test doubles.
Makefile - Application project makefile
README.md - This README
sdkconfig.defaults - Application project default settings
UNLICENSE
Each component to be tested has a test/
folder inside containing tests source files
and component makefile.
Components that do not require test doubles are the simplest ones and follow this structure:
esp32-unity-fff-demo - Application project directory
- components - Application project components
+ button
- mockable
+ include - Component header files directory
- test - Component tests directory
component.mk - Component tests makefile
mockable_test.c - Component tests source file
component.mk - Component makefile
mockable.c - Component source file
+ testable
+ main - Application project main component
+ test - Test project directory
Makefile - Application project makefile
README.md - This README
sdkconfig.defaults - Application project default settings
UNLICENSE
Its component.mk
file (the one inside test/
folder) just contains:
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive
Components that require test doubles, but those doubles are not require for other components tests, follow this structure:
esp32-unity-fff-demo - Application project directory
- components - Application project components
+ button
+ mockable
+ testable
+ include - Component header files directory
- test - Component tests directory
- mocks - Require test doubles directory
+ include - Test doubles header files directory
mockable_mocks.c - Test doubles source file
component.mk - Component tests makefile
testable_test.c - Component tests source file
component.mk - Component makefile
testable.c - Component source file
+ main - Application project main component
+ test - Test project directory
Makefile - Application project makefile
README.md - This README
sdkconfig.defaults - Application project default settings
UNLICENSE
The component.mk
file inside test/
folder should contain:
COMPONENT_OWNBUILDTARGET := 1
COMPONENT_OWNCLEANTARGET := 1
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive
# Source directories with mocks for this test component.
export MOCK_SRCDIRS := mocks
# Pass this component source dirs to sub-make
export COMPONENT_SRCDIRS
.PHONY: build
build:
$(MAKE) -C $(COMPONENT_BUILD_DIR) -f $(PROJECT_PATH)/test_component.mk build
.PHONY: clean
clean:
$(MAKE) -C $(COMPONENT_BUILD_DIR) -f $(PROJECT_PATH)/test_component.mk clean
The above tells the build system to use custom build and clean targets for component tests to allow linking to test doubles instead of real dependencies.
Components that require test doubles that are common to other components tests, follow the same structure as those that do not require test doubles at all:
esp32-unity-fff-demo - Application project directory
- components - Application project components
+ button
+ include - Component header files directory
- test - Component tests directory
component.mk - Component tests makefile
button_test.c - Component tests source file
component.mk - Component makefile
button.c - Component source file
+ mockable
+ testable
+ main - Application project main component
+ test - Test project directory
Makefile - Application project makefile
README.md - This README
sdkconfig.defaults - Application project default settings
UNLICENSE
The component.mk
file inside test/
folder is very similar to that of tests
that has own test doubles, but the variable MOCK_SRCDIRS
should be left empty
or can be omitted.
COMPONENT_OWNBUILDTARGET := 1
COMPONENT_OWNCLEANTARGET := 1
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive
# Source directories with mocks for this test component.
export MOCK_SRCDIRS :=
# Pass this component source dirs to sub-make
export COMPONENT_SRCDIRS
.PHONY: build
build:
$(MAKE) -C $(COMPONENT_BUILD_DIR) -f $(PROJECT_PATH)/test_component.mk build
.PHONY: clean
clean:
$(MAKE) -C $(COMPONENT_BUILD_DIR) -f $(PROJECT_PATH)/test_component.mk clean
These are part of a component named mocks
of the test project:
esp32-unity-fff-demo - Application project directory
+ components - Application project components
+ main - Application project main component
- test - Test project directory
- components - Test project components
+ fff - Fake Function Framework component directory
- mocks - Shared test doubles component
+ include - Test doubles header files directory
component.mk - Test doubles component makefile
freertos_mocks.c - Test doubles source file (FreeRTOS doubles)
gpio_mocks.c - Test doubles source file (GPIO doubles)
+ main - Test project main component
Makefile - Test project makefile
sdkconfig.defaults - Test project default settings
test_component.mk - Custom makefile for tests components using test doubles.
Makefile - Application project makefile
README.md - This README
sdkconfig.defaults - Application project default settings
UNLICENSE
The component.mk
file of the shared test doubles component should contain:
# Make mocked symbols hidden.
CFLAGS += -fvisibility=hidden
CXXFLAGS += -fvisibility=hidden
# Do not link with mocks lib
COMPONENT_ADD_LDFLAGS :=
You will need to copy the test_component.mk
file inside this test project
folder to your test project folder and add test doubles source files for
your components tests according to the cases described above. To write your
test doubles and unit tests, you may want to take a look into the source files
of this example.
Open a terminal, go to the directory where you want to put this example and run:
git clone --recursive https://gitlab.com/deltalejo/esp32-unity-fff-demo.git
This will download the project into folder esp32-unity-fff-demo
.
Go to esp32-unity-fff-demo
folder, e.g.: cd esp32-unity-fff-demo
Run make menuconfig
and set the serial port under Serial Flasher Options.
Under Example Configuration, enter the GPIO number that will be used as input.
Make sure logging output level is at least info, this is the default.
Connect target hardware and run:
make -j(number of CPUs + 1) flash monitor
Ignoring boot logs, main application will show:
I (47) main: Hello from app_main()!
I (47) gpio: GPIO[34]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:2
After pressing a button attached to the configured GPIO, in this case GPIO 34, will log:
I (47) main: Hello from app_main()!
I (47) gpio: GPIO[34]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:2
I (1597) testable: testable_calls_mockable() - Lets see if I call the real function...
I (1597) mockable: I'm the real mockable_function()!
I (1597) testable: testable_calls_mockable() - It seems real.
Subsequent presses will append the last three logs:
I (47) main: Hello from app_main()!
I (47) gpio: GPIO[34]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:2
I (1597) testable: testable_calls_mockable() - Lets see if I call the real function...
I (1597) mockable: I'm the real mockable_function()!
I (1597) testable: testable_calls_mockable() - It seems real.
I (4667) testable: testable_calls_mockable() - Lets see if I call the real function...
I (4667) mockable: I'm the real mockable_function()!
I (4667) testable: testable_calls_mockable() - It seems real.
Go to esp32-unity-fff-demo/test
folder, e.g.: cd test
Run make menuconfig
and set the serial port under Serial Flasher Options.
Make sure logging output level is at least info, this is the default.
Connect target hardware and run:
make -j(number of CPUs + 1) flash monitor
Ignoring boot logs, test application will show:
I (60) main: app_main() - First, call some real functions...
I (66) mockable: I'm the real mockable_function()!
I (72) testable: testable_calls_mockable() - Lets see if I call the real function...
I (80) mockable: I'm the real mockable_function()!
I (86) testable: testable_calls_mockable() - It seems real.
I (92) main: app_main() - Then, go to test menu...
Press ENTER to see the list of tests.
And the tests list:
Here's the test menu, pick your combo:
(1) "mockable_function(): Should return 0" [mockable]
(2) "testable_calls_mockable(): Should return true if mockable_function() returns zero" [testable]
(3) "testable_calls_mockable(): Should return false if mockable_function() returns non-zero" [testable]
(4) "button_init(): Should config given GPIO as input" [button]
(5) "button_init(): Should enable falling edge interrupts on given GPIO" [button]
(6) "button_init(): Should install GPIO ISR service" [button]
(7) "button_init(): Should add ISR handler with given GPIO as argument" [button]
(8) "button_init(): Should create queue for at least one gpio_num_t elements" [button]
(9) "button_init(): Should create queue before adding ISR handler" [button]
(10) "button_init(): Should create queue before creating task" [button]
(11) "button_init(): Should create task with name 'button_task'" [button]
(12) "button_isr(): Should send given argument to queue" [button]
(13) "button_task(): Should should wait on queue for button event" [button]
(14) "button_task(): Should call registered callback when receive a button event" [button]
(15) "button_task(): Should not call registered callback when there is any button event" [button]
Enter test for running.
- Running tests with tag [mockable]:
Running tests matching '[mockable]'...
Running mockable_function(): Should return 0...
I (5039) mockable: I'm the real mockable_function()!
/mnt/Alejo/Workspaces/esp/projects/esp32-unity-fff-demo/components/mockable/test/mockable_test.c:16:mockable_function(): Should return 0:PASS
-----------------------
1 Tests 0 Failures 0 Ignored
OK
- Running tests with tag [testable]:
Running tests matching '[testable]'...
Running testable_calls_mockable(): Should return true if mockable_function() returns zero...
I (10004) testable: testable_calls_mockable() - Lets see if I call the real function...
I (10004) testable: testable_calls_mockable() - It seems real.
/mnt/Alejo/Workspaces/esp/projects/esp32-unity-fff-demo/components/testable/test/testable_test.c:17:testable_calls_mockable(): Should return true if mockable_function() returns zero:PASS
Running testable_calls_mockable(): Should return false if mockable_function() returns non-zero...
I (10049) testable: testable_calls_mockable() - Lets see if I call the real function...
W (10049) testable: testable_calls_mockable() - It seems it has been mocked!
/mnt/Alejo/Workspaces/esp/projects/esp32-unity-fff-demo/components/testable/test/testable_test.c:27:testable_calls_mockable(): Should return false if mockable_function() returns non-zero:PASS
-----------------------
2 Tests 0 Failures 0 Ignored
OK
- Running tests with tag [button]:
Running tests matching '[button]'...
Running button_init(): Should config given GPIO as input...
/mnt/Alejo/Workspaces/esp/projects/esp32-unity-fff-demo/components/button/test/button_test.c:66:button_init(): Should config given GPIO as input:PASS
Running button_init(): Should enable falling edge interrupts on given GPIO...
/mnt/Alejo/Workspaces/esp/projects/esp32-unity-fff-demo/components/button/test/button_test.c:87:button_init(): Should enable falling edge interrupts on given GPIO:PASS
Running button_init(): Should install GPIO ISR service...
/mnt/Alejo/Workspaces/esp/projects/esp32-unity-fff-demo/components/button/test/button_test.c:108:button_init(): Should install GPIO ISR service:PASS
Running button_init(): Should add ISR handler with given GPIO as argument...
/mnt/Alejo/Workspaces/esp/projects/esp32-unity-fff-demo/components/button/test/button_test.c:125:button_init(): Should add ISR handler with given GPIO as argument:PASS
Running button_init(): Should create queue for at least one gpio_num_t elements...
/mnt/Alejo/Workspaces/esp/projects/esp32-unity-fff-demo/components/button/test/button_test.c:145:button_init(): Should create queue for at least one gpio_num_t elements:PASS
Running button_init(): Should create queue before adding ISR handler...
/mnt/Alejo/Workspaces/esp/projects/esp32-unity-fff-demo/components/button/test/button_test.c:164:button_init(): Should create queue before adding ISR handler:PASS
Running button_init(): Should create queue before creating task...
/mnt/Alejo/Workspaces/esp/projects/esp32-unity-fff-demo/components/button/test/button_test.c:186:button_init(): Should create queue before creating task:PASS
Running button_init(): Should create task with name 'button_task'...
/mnt/Alejo/Workspaces/esp/projects/esp32-unity-fff-demo/components/button/test/button_test.c:207:button_init(): Should create task with name 'button_task':PASS
Running button_isr(): Should send given argument to queue...
/mnt/Alejo/Workspaces/esp/projects/esp32-unity-fff-demo/components/button/test/button_test.c:228:button_isr(): Should send given argument to queue:PASS
Running button_task(): Should should wait on queue for button event...
/mnt/Alejo/Workspaces/esp/projects/esp32-unity-fff-demo/components/button/test/button_test.c:270:button_task(): Should should wait on queue for button event:PASS
Running button_task(): Should call registered callback when receive a button event...
/mnt/Alejo/Workspaces/esp/projects/esp32-unity-fff-demo/components/button/test/button_test.c:312:button_task(): Should call registered callback when receive a button event:PASS
Running button_task(): Should not call registered callback when there is any button event...
/mnt/Alejo/Workspaces/esp/projects/esp32-unity-fff-demo/components/button/test/button_test.c:376:button_task(): Should not call registered callback when there is any button event:PASS
-----------------------
12 Tests 0 Failures 0 Ignored
OK