poniedziałek, 27 sierpnia 2018

[0] CppUnit - Tworzenie projektu testowego Cpp Unit

W tym poście chciałbym opisać sposób stworzenia projektu Cpp Unit Test do testowania oprogramowania napisanego na mikrokontrolery STM32 w środowisku Eclipse (System Workbench).

[Źródło: http://academy.trendonix.com]


Cały projekt można pobrać z dysku Google pod tym linkiem.

Na samym początku należy rozpakować biblioteki cppunit. Ja umieściłem je w folderze C:/soft/libraries/cppunit-1.12.1.W niej będę przechowywane biblioteki dla całego projektu.

Następnie do projektu głównego dodajemy dwa projekty. Jeden z nich będzie przechowywał biblioteki statyczne do projektu, które znajduję się we wcześniej wymienionej lokalizacji. Drugi natomiast będzie głównym projektem testowym.

Testowana będzie projekt z biblioteką do układu nrf24l01.

Teraz po dołączeniu projektu testowego do programu Eclipse, należy dorzucić informację o plikach z projektu testowanego:



Do napisania oraz uruchomienia testów potrzebne są właściwie dwa pliki UnitTest.cpp oraz UnitTest.h. Można oczywiście dorzucać inne pliki z testami. Pozwoli to na oddzielenie poszczególnych modułów, czy samych plików. Przez co całe testy będą bardziej przejrzyste.

Do projektu dołącza się testowane pliki w następujący sposób:

  1. extern "C" {
  2.     #include "stm_nrf24l01.h"
  3.     #include "stm_nrf24l01.c"
  4. }

Prawdopodobnie nie wszystkie elementy projektu skompilują się poprawnie. Tutaj z pomocą przychodzi instrukcja define.

Aby uruchomić testy jednostkowe, czyli ukryć elementy które mogą mieć problem z kompilacją należy zdefiniować następującą instrukcje:

  1. #define UNIT_TEST_ENABLED

Część uruchamiającą hardware można zakryć:

  1. static void nrf24l01_InitSpi(void) { /* NOT WRITE TESTS */
  2.     #ifndef UNIT_TEST_ENABLED
  3.     hspi3.Instance = SPI3;
  4.     hspi3.Init.Mode = SPI_MODE_MASTER;
  5.     hspi3.Init.Direction = SPI_DIRECTION_2LINES;
  6.     hspi3.Init.DataSize = SPI_DATASIZE_8BIT;
  7.     hspi3.Init.CLKPolarity = SPI_POLARITY_LOW;
  8.     hspi3.Init.CLKPhase = SPI_PHASE_1EDGE;
  9.     hspi3.Init.NSS = SPI_NSS_SOFT;
  10.     hspi3.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;
  11.     hspi3.Init.FirstBit = SPI_FIRSTBIT_MSB;
  12.     hspi3.Init.TIMode = SPI_TIMODE_DISABLE;
  13.     hspi3.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  14.     hspi3.Init.CRCPolynomial = 10;
  15.     if (HAL_SPI_Init(&hspi3) != HAL_OK)
  16.     {
  17.       _Error_Handler(__FILE__, __LINE__);
  18.     }
  19.     #endif
  20. }

Część ustawiającą pin zdefiniowaną w plikach nagłówkowych można na czas testów zastąpić:

  1. #ifndef UNIT_TEST_ENABLED
  2. #define NRF24L01_CS_LOW()           HAL_GPIO_WritePin(NRF24L01_CS_PORT, NRF24L01_CS_PIN, GPIO_PIN_RESET)
  3. #define NRF24L01_CS_HIGH()          HAL_GPIO_WritePin(NRF24L01_CS_PORT, NRF24L01_CS_PIN, GPIO_PIN_SET)
  4. #define NRF24L01_CE_LOW()           HAL_GPIO_WritePin(NRF24L01_CE_PORT, NRF24L01_CE_PIN, GPIO_PIN_RESET)
  5. #define NRF24L01_CE_HIGH()          HAL_GPIO_WritePin(NRF24L01_CE_PORT, NRF24L01_CE_PIN, GPIO_PIN_SET)
  6. #else
  7. #define NRF24L01_CS_LOW()           do{ }while(0);
  8. #define NRF24L01_CS_HIGH()          do{ }while(0);
  9. #define NRF24L01_CE_LOW()           do{ }while(0);
  10. #define NRF24L01_CE_HIGH()          do{ }while(0);
  11. #endif

Natomiast funkcje z innych bibliotek np. HAL_SPI_xxx, można w dosyć prosty sposób zastąpić:

  1. #ifdef UNIT_TEST_ENABLED
  2. HAL_StatusTypeDef HAL_Transmit_UnitTestFun(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)
  3. {
  4.     return HAL_OK;
  5. }
  6. HAL_StatusTypeDef HAL_TransmitReceive_UnitTestFun(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_tTimeout)
  7. {
  8.     return HAL_OK;
  9. }
  10. HAL_StatusTypeDef HAL_Receive_UnitTestFun(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)
  11. {
  12.     return HAL_OK;
  13. }
  14. #define HAL_SPI_Transmit    HAL_Transmit_UnitTestFun
  15. #define HAL_SPI_TransmitReceive HAL_TransmitReceive_UnitTestFun
  16. #define HAL_SPI_Receive HAL_Receive_UnitTestFun
  17. void HAL_Delay(uint32_t Delay)
  18. {
  19.     do{
  20.     }while(0);
  21. }
  22. #endif

Trzy funkcje od SPI zostały zakryte przez funkcje zwracające HAL_OK. Natomiast funkcja HAL_Delay zdefiniowana jest jako _weak. Wobec tego można ją bez problemu zdefiniować w innym miejscu.

Najpierw przygotowywane są dane w pliku nagłówkowym gdzie przypisujemy nową procedurę testową do klasy oraz dodajemy ją do wykonywania:

  1. class UnitTest : public CPPUNIT_NS::TestFixture
  2. {
  3.   CPPUNIT_TEST_SUITE( UnitTest );
  4.   CPPUNIT_TEST( testConstructor );
  5.   CPPUNIT_TEST(checkThenSetPayloadSize_WriteBiggerThenMax_Return32);    //Dodawanie do testów
  6.   //CPPUNIT_TEST_EXCEPTION( testAddThrow, IncompatibleM7LCDError);
  7.   CPPUNIT_TEST_SUITE_END();
  8. public:
  9.   void setUp();
  10.   void tearDown();
  11.   void testConstructor();          
  12.   void checkThenSetPayloadSize_WriteBiggerThenMax_Return32();           //Dodanie do klasy
  13. };

Testy następnie dodajemy do pliku cpp:

  1. #include "../stm_test/UnitTest.h"
  2. #include "StdAfx.h"
  3. #include <cppunit/config/SourcePrefix.h>
  4. #include "../stm_test/StdAfx.h"
  5. extern "C" {
  6.     #include "stm_nrf24l01.h"
  7.     #include "stm_nrf24l01.c"
  8. }
  9. CPPUNIT_TEST_SUITE_REGISTRATION( UnitTest );
  10. void UnitTest::setUp() { }
  11. void UnitTest::tearDown() { }
  12. void UnitTest::testConstructor() { }
  13. void UnitTest::checkThenSetPayloadSize_WriteBiggerThenMax_Return32()
  14. {
  15.     uint8_t returnPayloadSize = checkThenSetPayloadSize(38);
  16.     CPPUNIT_ASSERT( returnPayloadSize == 32 );
  17. }

W przypadku poprawnie przeprowadzonych testów informacja zostanie wyświetlona w Konsoli programu Eclipse:


Gdy wystąpi błąd wyświetli się np. taka informacja:



Wykaz funkcji do stosowania podczas testów jednostkowych można znaleźć pod tym linkiem.