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).
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:
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:
Część uruchamiającą hardware można zakryć:
Część ustawiającą pin zdefiniowaną w plikach nagłówkowych można na czas testów zastąpić:
Natomiast funkcje z innych bibliotek np. HAL_SPI_xxx, można w dosyć prosty sposób zastąpić:
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:
Testy następnie dodajemy do pliku cpp:
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:
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:
- extern "C" {
- #include "stm_nrf24l01.h"
- #include "stm_nrf24l01.c"
- }
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:
- #define UNIT_TEST_ENABLED
Część uruchamiającą hardware można zakryć:
- static void nrf24l01_InitSpi(void) { /* NOT WRITE TESTS */
- #ifndef UNIT_TEST_ENABLED
- hspi3.Instance = SPI3;
- hspi3.Init.Mode = SPI_MODE_MASTER;
- hspi3.Init.Direction = SPI_DIRECTION_2LINES;
- hspi3.Init.DataSize = SPI_DATASIZE_8BIT;
- hspi3.Init.CLKPolarity = SPI_POLARITY_LOW;
- hspi3.Init.CLKPhase = SPI_PHASE_1EDGE;
- hspi3.Init.NSS = SPI_NSS_SOFT;
- hspi3.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;
- hspi3.Init.FirstBit = SPI_FIRSTBIT_MSB;
- hspi3.Init.TIMode = SPI_TIMODE_DISABLE;
- hspi3.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
- hspi3.Init.CRCPolynomial = 10;
- if (HAL_SPI_Init(&hspi3) != HAL_OK)
- {
- _Error_Handler(__FILE__, __LINE__);
- }
- #endif
- }
Część ustawiającą pin zdefiniowaną w plikach nagłówkowych można na czas testów zastąpić:
- #ifndef UNIT_TEST_ENABLED
- #define NRF24L01_CS_LOW() HAL_GPIO_WritePin(NRF24L01_CS_PORT, NRF24L01_CS_PIN, GPIO_PIN_RESET)
- #define NRF24L01_CS_HIGH() HAL_GPIO_WritePin(NRF24L01_CS_PORT, NRF24L01_CS_PIN, GPIO_PIN_SET)
- #define NRF24L01_CE_LOW() HAL_GPIO_WritePin(NRF24L01_CE_PORT, NRF24L01_CE_PIN, GPIO_PIN_RESET)
- #define NRF24L01_CE_HIGH() HAL_GPIO_WritePin(NRF24L01_CE_PORT, NRF24L01_CE_PIN, GPIO_PIN_SET)
- #else
- #define NRF24L01_CS_LOW() do{ }while(0);
- #define NRF24L01_CS_HIGH() do{ }while(0);
- #define NRF24L01_CE_LOW() do{ }while(0);
- #define NRF24L01_CE_HIGH() do{ }while(0);
- #endif
Natomiast funkcje z innych bibliotek np. HAL_SPI_xxx, można w dosyć prosty sposób zastąpić:
- #ifdef UNIT_TEST_ENABLED
- HAL_StatusTypeDef HAL_Transmit_UnitTestFun(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)
- {
- return HAL_OK;
- }
- HAL_StatusTypeDef HAL_TransmitReceive_UnitTestFun(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_tTimeout)
- {
- return HAL_OK;
- }
- HAL_StatusTypeDef HAL_Receive_UnitTestFun(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)
- {
- return HAL_OK;
- }
- #define HAL_SPI_Transmit HAL_Transmit_UnitTestFun
- #define HAL_SPI_TransmitReceive HAL_TransmitReceive_UnitTestFun
- #define HAL_SPI_Receive HAL_Receive_UnitTestFun
- void HAL_Delay(uint32_t Delay)
- {
- do{
- }while(0);
- }
- #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:
- class UnitTest : public CPPUNIT_NS::TestFixture
- {
- CPPUNIT_TEST_SUITE( UnitTest );
- CPPUNIT_TEST( testConstructor );
- CPPUNIT_TEST(checkThenSetPayloadSize_WriteBiggerThenMax_Return32); //Dodawanie do testów
- //CPPUNIT_TEST_EXCEPTION( testAddThrow, IncompatibleM7LCDError);
- CPPUNIT_TEST_SUITE_END();
- public:
- void setUp();
- void tearDown();
- void testConstructor();
- void checkThenSetPayloadSize_WriteBiggerThenMax_Return32(); //Dodanie do klasy
- };
Testy następnie dodajemy do pliku cpp:
- #include "../stm_test/UnitTest.h"
- #include "StdAfx.h"
- #include <cppunit/config/SourcePrefix.h>
- #include "../stm_test/StdAfx.h"
- extern "C" {
- #include "stm_nrf24l01.h"
- #include "stm_nrf24l01.c"
- }
- CPPUNIT_TEST_SUITE_REGISTRATION( UnitTest );
- void UnitTest::setUp() { }
- void UnitTest::tearDown() { }
- void UnitTest::testConstructor() { }
- void UnitTest::checkThenSetPayloadSize_WriteBiggerThenMax_Return32()
- {
- uint8_t returnPayloadSize = checkThenSetPayloadSize(38);
- CPPUNIT_ASSERT( returnPayloadSize == 32 );
- }
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: