Tests

This page will describe how to contribute with unit tests of the C code base. We strongly suggest using the VSCode development environment running in the docker container.

Example test folder structure creation in module folder

For better understanding of the test folder structure we will create it in the following example in the os module folder for the os unit. The following test folder structure will contain all necessary files for the os unit testing.

  1. First of all we need to create CMakeLists.txt in the module folder as it is shown in the following example:
    os (module)
    ├── src
    ├── inc
    └── CMakeLists.txt
    
    • The CMakeLists.txt collects all unit tests from the module. It has the same content for every module and can be copied over. Here is its code which can be used during the setup of new test environments:
      cmake_minimum_required(VERSION 3.14)
      
      FILE(GLOB_RECURSE UNIT_TEST_DIRECTORIES LIST_DIRECTORIES true ${UNIT_TEST_DIRECTORY_WILDCARD})
      list(FILTER UNIT_TEST_DIRECTORIES INCLUDE REGEX "^.*/ut/*.")
      
      if(BUILD_TESTS)
          foreach(UNIT_TEST_DIRECTORY ${UNIT_TEST_DIRECTORIES})
              if(EXISTS "${UNIT_TEST_DIRECTORY}/CMakeLists.txt")
                  add_subdirectory(${UNIT_TEST_DIRECTORY})
              endif()
          endforeach(UNIT_TEST_DIRECTORY)
      endif()
      
  2. Then we create a test folder in module with two sub-folders - mock and ut as it is shown in the following example:
    os (module)
    ├── src
    ├── inc
    ├── CMakeLists.txt
    └── test
        ├── mock
        └── ut
    
  3. In mock we then create a os folder for the os unit mock and two files, mock.cpp and osMock.h as it is shown in the following example:
    os (module)
    ├── src
    ├── inc
    ├── CMakeLists.txt
    └── test
        ├── ut
        └── mock
            └── os
                ├── mock.cpp
                └── osMock.h
    
  4. In ut we then create a os folder for the os unit test cfg and two files, CMakeLists.txt and ut.cpp as it is shown in the following example:
    os (module)
    ├── src
    ├── inc
    ├── CMakeLists.txt
    └── test
        ├── mock
        │   └── os
        │       ├── mock.cpp
        │       └── osMock.h
        └── ut
            └── os
                ├── ut.cpp
                ├── CMakeLists.txt
                └── cfg
    
    • The CMakeLists.txt creates an executable for the current unit test folder. It has the same content for the every unit test folder and can be copied over. Here is its code which can be used during the setup of new test environments:
      cmake_minimum_required(VERSION 3.14)
      
      get_filename_component(UNIT_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME)
      
      set(CONFIGURATION_PATH "cfg")
      set(UNIT_SOURCE_PATH "../../../src")
      set(EXECUTABLE_NAME "${UNIT_NAME}_ut_executable")
      set(TEST_NAME "${UNIT_NAME}_test")
      
      list(APPEND LOCAL_MOCK_SOURCES ${MOCK_SOURCES})
      list(FILTER LOCAL_MOCK_SOURCES EXCLUDE REGEX ".*test/mock/${UNIT_NAME}")
      
      include_directories(${CONFIGURATION_PATH})
      FILE(GLOB_RECURSE CFG_SOURCES_C  "${CONFIGURATION_PATH}/*.c")
      
      include_directories(${CONFIGURATION_PATH})
      FILE(GLOB_RECURSE CFG_SOURCES_CPP  "${CONFIGURATION_PATH}/*.cpp")
      
      set(SOURCES
          "${UNIT_SOURCE_PATH}/${UNIT_NAME}.c"
          ${UNIT_TEST_SOURCE_WILDCARD}
          ${LOCAL_MOCK_SOURCES}
          ${CFG_SOURCES_C}
          ${CFG_SOURCES_CPP}
          )
      
      add_executable(${EXECUTABLE_NAME} ${SOURCES})
      target_link_libraries(${EXECUTABLE_NAME} gtest_main gmock_main)
      
      include(GoogleTest)
      gtest_add_tests(
          TARGET ${EXECUTABLE_NAME}
          EXTRA_ARGS --gtest_output=xml:${TEST_RESULTS_DIRECTORY}/googletest_${EXECUTABLE_NAME}.xml --gtest_filter=*
          )
      
    • The ut.cpp file uses a specific file structure that can be found in the snippets (keyword: test_source) or copied from the already implemented ut.cpp - in that case doxygen comments must be fixed.

  5. In the cfg folder we create two files, utCfg.cpp and utCfg.h as it is shown in the following example:
    os (module)
    ├── src
    ├── inc
    ├── CMakeLists.txt
    └── test
        ├── mock
        │   └── os
        │       ├── mock.cpp
        │       └── osMock.h
        └── ut
            └── os
                ├── ut.cpp
                ├── CMakeLists.txt
                └── cfg
                    ├── utCfg.cpp
                    └── utCfg.h
    
    • The utCfg.cpp and utCfg.h files use a specific file structure that can be found in the snippets (for utCfg.cpp - keyword: test_source_configuration, for utCfg.h - keyword: test_header_configuration) or copied from the already implemented utCfg.cpp and utCfg.h files - in that case doxygen comments must be fixed.

  6. The final test folder structure should look exactly the same as it is shown in the following diagram:
    ../../../../_images/testFolderStructure.png

Naming conventions

  1. Mocked folder names should be exactly the same as the unit name.

  2. Unit test folder names should be exactly the same as the unit name.

  3. Macros should use SNAKE_CASE as it is shown in the following example:
    #define FOO_BAR
    
  4. Mock class names consist of two parts, the first one is the unit name (PascalCase) followed by an underscore symbol and MOCK as it is shown in the following example for the os unit mock:
    class Os_MOCK
    
  5. Test fixture class names consist of two parts, the first one is the unit name (PascalCase) followed by an underscore symbol and TestFixture as it is shown in the following example for the os unit test fixture:
    class Os_TestFixture : public ::testing::Test
    
  6. Test suite names should start with Test followed by an underscore symbol and then use camelCase as it is shown in the following example:
    Test_unitName
    
  7. Test names should use SNAKE_CASE as it is shown in the following example for the os_start function where the test checks execution flow:
    #define TEST_OS_START_EXECUTIONFLOW()
    
  8. Global variables should use PascalCase as it is shown in the following example:
    CosmOS_CoreVariableType CoresVar[CORE_NUM];
    

Implementation

  1. First of all we would like to say is that the following rules can be easily observed in any unit test implementation in the repository. You can use the already implemented unit tests as an example for your implementation if you find it more efficient.

  2. For C and C++ code we use clang-format. If you use the docker development environment clang-format is preinstalled with the correct version and VSCode is setup in a way to format your code on save.

  3. Example mock implementation for the os unit and os_getOsCfg function:
    1. For the mock class and test fixture class:
      class Os_MOCK
      {
      public:
          Os_MOCK()
          {}
          ~Os_MOCK()
          {}
      
          MOCK_METHOD( CosmOS_OsConfigurationType *, os_getOsCfg, () );
      };
      
      class Os_TestFixture : public ::testing::Test
      {
      public:
          Os_TestFixture()
          {
              _OsMock.reset( new ::testing::NiceMock<Os_MOCK>() );
          }
          ~Os_TestFixture()
          {
              _OsMock.reset();
          }
      
          static std::unique_ptr<Os_MOCK> _OsMock;
      
      protected:
          virtual void
          SetUp()
          {}
          virtual void
          TestBody()
          {}
          virtual void
          TearDown()
          {}
      };
      
    2. For the mocked function definition:
      std::unique_ptr<Os_MOCK> Os_TestFixture::_OsMock;
      
      CosmOS_OsConfigurationType *
      os_getOsCfg()
      {
          Os_TestFixture::_OsMock->os_getOsCfg();
      
          return ( NULL );
      }
      
  4. We have to define the test case with an unique macro name to be able to link them with the functions. The following example contains a test case definition for the os_start function where the test checks the execution flow:
    #define TEST_OS_START_EXECUTIONFLOW() TEST( Test_os, os_start_executionFlow )
    
  5. To ease test description creation we define a macro mapped to the RecordProperty as it is shown in the following example:
    TEST_DESCRIPTION( desc ) RecordProperty( "description", desc )
    
  6. Put the description inside the test case function definition:
    TEST_OS_START_EXECUTIONFLOW()
    {
        TEST_DESCRIPTION(
            "This test validates execution flow of the os_start function" );
    }
    
  7. Use proper doxygen comments for the test case function definition. The following example contains a test case doxygen comment for the os_start function where the test checks the execution flow:
    /********************************************************************************
      * DOXYGEN DOCUMENTATION INFORMATION                                          **
      * ****************************************************************************/
    /**
      * @brief This test validates execution flow of the os_start function.
      *
      * @see os_start
      * @authors https://github.com/author1 https://github.com/author2
    ********************************************************************************/
    TEST_OS_START_EXECUTIONFLOW()
    {
    
    }
    
  8. Put your code into the correct doxygen section, in this specific case inside the Testcases group as it is shown in the following example:
    /********************************************************************************
      * DOXYGEN START GROUP                                                        **
      * *************************************************************************//**
      * @defgroup testcases_os_ut_c Testcases
      * @ingroup Test_os
      * @{
    ********************************************************************************/
    /********************************************************************************
      * DOXYGEN DOCUMENTATION INFORMATION                                          **
      * ****************************************************************************/
    /**
      * @brief This test validates execution flow of the os_start function.
      *
      * @see os_start
      * @authors https://github.com/author1 https://github.com/author2
    ********************************************************************************/
    TEST_OS_START_EXECUTIONFLOW()
    {
    
    }
    /********************************************************************************
      * DOXYGEN STOP GROUP                                                         **
      * *************************************************************************//**
      * @} */
    /*  testcases_os_ut_c
    ********************************************************************************/
    

Tips and tricks

  1. If you develop in the VSCode you can use code snippets. Just start typing the keyword of the code snippet and VSCode will automatically offer you the snippet (then press TAB).