Skip to content

unit test frameworks Integration

Mustafa Bahaa edited this page May 11, 2024 · 1 revision

Table of Contents

Prerequisites

Introduction

This wiki page outlines the integration process of CMock, Unity, and gcovr.

How CMock and Unity Work

First, we need to know that CMock and Unity are just some source and header files that should be compiled and linked with your test files.

flowchart TD;
    A[componant object files *.o];
    B[unity.o];
    C[cmock.o];
    D[Executable file];
    E[_______ LINKER _______]; 
    F[mock_*.o]; 
    A --> E ; 
    B --> E ; 
    C --> E ; 
    E --> D ; 
    F --> E ;
Loading

But to generate the component mock files mock_* , we need a built-in Ruby script located at CMock/lib/cmock.rb (it is a ruby script).

flowchart TD;
    E[a.h] --> C ;
    F[b.h] --> C ;
    H[*.h] --> C ;
    A[CMock config file] --> B[_______CMock/lib/cmock.rb_______] ; 
    C[COMPONANT_NAME_mock_header_files] --> B ;
    B --> I[mock_a.h & mock_a.c];
    B --> J[mock_b.h & mock_b.c];
    B --> K[mock_*.h & mock_*.c];
Loading

If CMock config file is not provided, the script will use the default configuration.

How gcovr work

Gcovr is available as a Python package that can be installed via pip.
Either --coverage or -fprofile-arcs -ftest-coverage are needed so that the compiler produces the information necessary to gather coverage data.
The gcovr tool searches recursively from the root directory, looking for *.gcno files to generate coverage reports.

Integrate CMock/Unity with project files

Let us take the software/bsw/mcal/port_swc as example The working tree should be

software/bsw/mcal/port_swc
│
├── CMakeLists.txt
├── core
│   ├── Port.c
│   ├── Port.h
│   └── Port_MemMap.h
├── tests
│   └── unit
│       ├── test_port_swc.c
│       └── test_runners
│           ├── all_tests.c
│           └── port_Runner.c
└── tools
    └── cmake
        ├── port_swc_compile.cmake
        └── port_swc_tests.cmake

Start with tools/cmake/port_swc_tests.cmake

It will contain this variables:

  1. port_swc_tests_sources_list: This variable contains:

    • Main test source files: test_port_swc, test_runners/all_tests.c, and test_runners/port_Runner.c.
    • All mock files located in ${MOCK_FILE_PATH}/mocks/mock_Det.c must be included.
  2. port_swc_ut_sources_list: This variable contains:

    • The source code of the main component: ${port_swc}/core/Port.c.
    • All dependencies of Port.c, such as ${gendata}/Port_Lcfg.c and ${gendata}/Port_PBcfg.c.
  3. port_swc_mock_header_files: This variable contains all files that need to be mocked, such as Det.h and any dependencies.

  4. port_swc_tests_includes: This variable includes all header files related to the port_swc component.

  5. port_swc_tests_compile_options: This variable contains any extra compiler options needed.

  6. port_swc_tests_defines: This variable contains all macros that need to be defined during compilation for all source files.

and for software/bsw/mcal/port_swc/CMakeLists.txt
Snippet from CMake file:

extract_module_path(port_swc MOCK_FILE_PATH)

if(MODE STREQUAL "TESTING")    
 # Execute CMock script
 execute_process(
   COMMAND ruby ${CMOCK_SCRIPT} -o${CMOCK_CONFIG_FILE} ${port_swc_mock_header_files}
   OUTPUT_VARIABLE CMOCK_OUTPUT
   WORKING_DIRECTORY ${MOCK_FILE_PATH}
 )
 message("CMock output: ${CMOCK_OUTPUT}")

 # Testing Mode: Build the library and test executable
 include(tools/cmake/port_swc_tests.cmake)
 add_executable(port_swc_test ${port_swc_tests_sources_list} ${unity_common_sources})
 target_compile_definitions(port_swc_test PRIVATE ${port_swc_tests_defines})
 target_compile_options(port_swc_test PRIVATE ${dio_swc_tests_compile_options})
 target_include_directories(port_swc_test PRIVATE ${port_swc_tests_includes} ${unity_common_includes})
 target_link_libraries(port_swc_test PRIVATE port_swc)
endif()

For extract_module_path(port_swc MOCK_FILE_PATH) function
it used to extract the path of port_swc within the Build directory and then assigns it to the variable named MOCK_FILE_PATH so we can use it in Start with tools/cmake/port_swc_tests.cmake

  • For mock file generation we must excute the ruby script first
# Execute CMock script
execute_process(
COMMAND ruby ${CMOCK_SCRIPT} -o${CMOCK_CONFIG_FILE} ${port_swc_mock_header_files}
OUTPUT_VARIABLE CMOCK_OUTPUT
WORKING_DIRECTORY ${MOCK_FILE_PATH}
)

You can overwite the default config file by change the o${CMOCK_CONFIG_FILE} with new configuration path

execute_process(
COMMAND ruby ${CMOCK_SCRIPT} -o./test/unit/myConfig.yml ${port_swc_mock_header_files}
OUTPUT_VARIABLE CMOCK_OUTPUT
WORKING_DIRECTORY ${MOCK_FILE_PATH}
)
  • port_swc_mock_header_files This variable contains the list of header need to be mocked.

  • CMOCK_OUTPUT This variable contains the output of the Ruby script execution

  • unity_common_sources This variable contains all source files of unity/CMock frameworks (Must be included within add_executable)

  • unity_common_includes This variable contains all header files of unity/CMock frameworks (Must be included within target_include_directories)

Now, all components are compiled and linked together, and it will generate test executable file each componant. (Take a look at the main Makefile section)

Gcovr Integration

set(COMMON_FLAGS "-Og -g -Wall --coverage")
GCOVR_INCLUDE_PATHS := '.*/core/.*'
gcovr -r . --filter ${GCOVR_INCLUDE_PATHS} \
	--html-self-contained \
	--html-theme ${HTML_THEME} \
	--html-nested \
	--cobertura \
	--html=$(UNIT_TESTS_INSTALL_DIR)/html/test_report.html \
	--xml=$(UNIT_TESTS_INSTALL_DIR)/test_report.xml \
	-j$(MAXIMUM_CPU_CORES);
  • r . Recursively search from the root directory /workspace in case of the devcontainer
  • --filter ${GCOVR_INCLUDE_PATHS} generates coverage only for paths matching the pattern **/core/.*c (representing the main source files of the SWC).
  • --html=$(UNIT_TESTS_INSTALL_DIR)/html/test_report.html
    --xml=$(UNIT_TESTS_INSTALL_DIR)/test_report.xml \
    Set output dir for html and xml reports.
  • you can set the theme of html reports by pass option HTML_THEME like make coverage HTML_THEME=green

Main Makefile

The unit_test rule consist of 3 subrules:

  1. build_test: This rule generates the executable test files for each component included in CMake.
  2. test: This rule will
    • runs each program in $(TEST_DIRS) that has /*_test -it is indicating test executable files- .
    • generate test report file in $(UNIT_TESTS_INSTALL_DIR)/test_results.txt
    • echo the test output on the shell screen.
  3. coverage: This rule will gnerate html and xml coverage report for each SWC.
  4. Additionally, unit_test rule will echo the test summary using the built-in Ruby script parse_output.rb, using test_results.txt as the input file.
flowchart TD;
 A[test_results.txt]
 B[--------- parse_output.rb ---------]
 C[echo Test summary ]
 A --> B --> C ; 
Loading