poniedziałek, 15 kwietnia 2019

Embedded Unit Test - Biblioteka dla testów jednostkowych na LPC1769

W tym poście chciałbym przedstawić prostą bibliotekę oferującą możliwość przeprowadzania testów prostych testów jednostkowych na mikrokontrolerze LPC1769.

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


Dane głównie będę wyprowadzał za pomocą danych z UART. Informację o poprawnym uruchomieniu UART'u będę otrzymywał z badania statusu diody.

Biblioteka Assert:


Pod tym linkiem przykładowy sposób użycia makra assert z domyślną wiadomością.

Biblioteka 


Poniżej przedstawię prostą bibliotekę do wykorzystania właściwie na każdym procesorze, do wyświetlania wyników operacji użyję interfejsu UART.

Na samym początku opiszę funkcję do sprawdzania czy dane są sobie równe. Poniżej funkcja odpowiedzialna za porównywanie dwóch wartości ośmio bitowych.

  1. static bool Static_AreEqualU8_CheckValue(uint8_t val1, uint8_t val2)
  2. {
  3.     return (val1 == val2) ? true : false;
  4. }

Główna funkcja sprawdzająca dla liczb 8 bitowych wygląda następująco. Na samym początku sprawdzamy zdeklarowany warunek a następnie wysyłamy odpowiednią ramkę danych przez UART:

  1. static bool Static_AreEqualU8_CheckAndWriteResponse(uint8_t val1, uint8_t val2, uint8_t* file, uint32_t line, uint8_t *functionName)
  2. {
  3.     char displayString[100];
  4.     bool operationResoult = Static_AreEqualU8_CheckValue(val1, val2);
  5.     if(operationResoult == false)
  6.     {
  7.         sprintf(displayString, "ERROR => AreEqualU8 => %s => operation %u == %u => false, line:%u, file:%s \r\n",
  8.                 functionName, val1, val2, line, file);
  9.     }
  10.     else
  11.     {
  12.         sprintf(displayString, "OK => AreEqualU8 => %s => operation %u == %u => true,  line:%u, file:%s \r\n",
  13.                 functionName, val1, val2, line, file);
  14.     }
  15.     DisplayMsg(&displayString[0]);
  16. }

Funkcje obsługujące dany test wywoływane są przez wskaźniki umieszczone w strukturze:

  1. typedef struct AssertFuntion_TypedefStruct
  2. {
  3.     ...
  4.     ...
  5.     bool (*AreEqualU8)(uint8_t, uint8_t, uint8_t*, uint32_t, uint8_t*);
  6.     bool (*AreEqualU16)(uint16_t, uint16_t, uint8_t*, uint32_t, uint8_t*);
  7.     bool (*AreEqualU32)(uint32_t, uint32_t, uint8_t*, uint32_t, uint8_t*);
  8.     bool (*AreEqualU64)(uint64_t, uint64_t, uint8_t*, uint32_t, uint8_t*);
  9.     bool (*AreEqualU)(uint64_t, uint64_t, uint8_t*, uint32_t, uint8_t*);
  10.     ...
  11.     ...
  12. }AssertFun_TypedefStruct;

Podobnie wyglądają funkcje dla pozostałych zmiennych int, char:

  1. typedef struct AssertFuntion_TypedefStruct
  2. {
  3.     ...
  4.     ...
  5.     void (*AreEqualI8)(int8_t, int8_t, uint8_t*, uint32_t, uint8_t*);
  6.     void (*AreEqualI16)(int16_t, int16_t, uint8_t*, uint32_t, uint8_t*);
  7.     void (*AreEqualI32)(int32_t, int32_t, uint8_t*, uint32_t, uint8_t*);
  8.     void (*AreEqualI64)(int64_t, int64_t, uint8_t*, uint32_t, uint8_t*);
  9.     void (*AreEqualI)(int, int, uint8_t*, uint32_t, uint8_t*);
  10.     void (*AreEqualC) (char, char, uint8_t*, uint32_t, uint8_t*);
  11.     void (*AreEqualUC) (unsigned char, unsigned char, uint8_t*, uint32_t, uint8_t*);
  12.     ...
  13.     ...
  14. }AssertFun_TypedefStruct;

Dla zmiennych float oraz double podawana jest wartość tolerancji z jaką dokładność ma być zachowana.

  1. static bool Static_AreEqualF_CheckValue(float val1, float val2, float tolerance)
  2. {
  3.     if(((val1 - tolerance) < val2) && ((val1 + tolerance) > val2))
  4.     {
  5.         return true;
  6.     }
  7.     return false;
  8. }

Możliwe funkcje testowe dla double oraz float służące do porównywania wartości to:

  1. typedef struct AssertFuntion_TypedefStruct
  2. {
  3.     ...
  4.     ...
  5.     void (*AreEqualF)(float, float, float, uint8_t*, uint32_t, uint8_t*);
  6.     void (*AreEqualD)(double, double, double, uint8_t*, uint32_t, uint8_t*);
  7.     ...
  8.     ...
  9. }AssertFun_TypedefStruct;

Kolejna grupa funkcji testowych służy do sprawdzania zawartości tablicy. Poniżej przykład dla tablicy z elementami uint8_t:

  1. static uint16_t Static_AreEqualU8_Array_CheckArray(uint8_t *testArray, uint8_t *referenceArray, uint8_t *wrongParamArray, uint16_t numberOfElementsToCheck)
  2. {
  3.     for(uint16_t i=0;i<numberOfElementsToCheck;i++)
  4.     {
  5.         if(*(testArray + i) != *(referenceArray + i))
  6.         {
  7.             *(wrongParamArray + 0) = *(testArray + i);
  8.             *(wrongParamArray + 1) = *(referenceArray + i);
  9.             return i;
  10.         }
  11.     }
  12.     return numberOfElementsToCheck + 1;
  13. }

Każdy element tablicy testowej porównywany jest z tablicą referencyjną. Jako wskaźnik podawana jest tablica przechowująca błędną wartość z podawanych tablic. Dodatkowo funkcja zwraca pozycję z miejscem błędu. W przypadku nie znalezienia błędu w tablicy zwracana jest ilość testowanych elementów zwiększona o 1.

Funkcja przesyłająca wynik testu wygląda następująco:

  1. static void Static_AreEqualU8_Array_CheckAndWriteResponse(uint8_t *testArray, uint8_t *referenceArray, uint16_t numberOfElementsToCheck,uint8_t* file, uint32_t line, uint8_t *functionName)
  2. {
  3.     char displayString[MESSAGE_LENGTH];
  4.     uint8_t wrongElementArray[2] = {0x00};
  5.     uint16_t operationResoult = Static_AreEqualU8_Array_CheckArray(&testArray[0], &referenceArray[0], &wrongElementArray[0],
  6.             numberOfElementsToCheck);
  7.  
  8.     if(operationResoult != (numberOfElementsToCheck + 1))
  9.     {
  10.         sprintf(displayString, "ERROR => AreEqualU8_Array => %s => element %d, operation %d == %d => false, line:%u, file:%s \r\n",
  11.                 functionName,
  12.                 operationResoult,
  13.                 wrongElementArray[0],
  14.                 wrongElementArray[1],
  15.                 line,
  16.                 file);
  17.     }
  18.     else
  19.     {
  20.         sprintf(displayString, "OK => AreEqualU8_Array => %s, line:%u, file:%s \r\n",
  21.                 functionName, line, file);
  22.     }
  23.  
  24.     DisplayMsg(&displayString[0]);
  25. }

Funkcje testujące tablice to:

  1. typedef struct AssertFuntion_TypedefStruct
  2. {
  3.     ...
  4.     ...
  5.     //Compare Array
  6.     void (*AreEqualU8_Array)(uint8_t*, uint8_t*, uint16_t, uint8_t*, uint32_t, uint8_t*);
  7.     void (*AreEqualU16_Array)(uint16_t*, uint16_t*, uint16_t, uint8_t*, uint32_t, uint8_t*);
  8.     void (*AreEqualU32_Array)(uint32_t*, uint32_t*, uint16_t, uint8_t*, uint32_t, uint8_t*);
  9.     ...
  10.     ...
  11. }AssertFun_TypedefStruct;

Kolejną możliwością testową jest sprawdzenie czy dana wartość jest większa od drugiej:

  1. typedef struct AssertFuntion_TypedefStruct
  2. {
  3.     ...
  4.     ...
  5.     void (*GraterU8)(uint8_t, uint8_t, uint8_t*, uint32_t, uint8_t*);
  6.     void (*GraterU16)(uint16_t, uint16_t, uint8_t*, uint32_t, uint8_t*);
  7.     void (*GraterU32)(uint32_t, uint32_t, uint8_t*, uint32_t, uint8_t*);
  8.     void (*GraterU64)(uint64_t, uint64_t, uint8_t*, uint32_t, uint8_t*);
  9.     void (*GraterU)(uint64_t, uint64_t, uint8_t*, uint32_t, uint8_t*);
  10.  
  11.     void (*GraterI8)(int8_t, int8_t, uint8_t*, uint32_t, uint8_t*);
  12.     void (*GraterI16)(int16_t, int16_t, uint8_t*, uint32_t, uint8_t*);
  13.     void (*GraterI32)(int32_t, int32_t, uint8_t*, uint32_t, uint8_t*);
  14.     void (*GraterI64)(int64_t, int64_t, uint8_t*, uint32_t, uint8_t*);
  15.     void (*GraterI)(int, int, uint8_t*, uint32_t, uint8_t*);
  16.  
  17.     void (*GraterC) (char, char, uint8_t*, uint32_t, uint8_t*);
  18.     void (*GraterUC) (unsigned char, unsigned char, uint8_t*, uint32_t, uint8_t*);
  19.  
  20.     void (*GraterF)(float, float, float, uint8_t*, uint32_t, uint8_t*);
  21.     void (*GraterD)(double, double, double, uint8_t*, uint32_t, uint8_t*);
  22.     ...
  23.     ...
  24. }AssertFun_TypedefStruct;

Teraz scenariusz odwrotny od wcześniejszego czyli gdy wartość ma być mniejsza od podanej:

  1. typedef struct AssertFuntion_TypedefStruct
  2. {
  3.     ...
  4.     ...
  5.     void (*LessU8)(uint8_t, uint8_t, uint8_t*, uint32_t, uint8_t*);
  6.     void (*LessU16)(uint16_t, uint16_t, uint8_t*, uint32_t, uint8_t*);
  7.     void (*LessU32)(uint32_t, uint32_t, uint8_t*, uint32_t, uint8_t*);
  8.     void (*LessU64)(uint64_t, uint64_t, uint8_t*, uint32_t, uint8_t*);
  9.     void (*LessU)(uint64_t, uint64_t, uint8_t*, uint32_t, uint8_t*);
  10.  
  11.     void (*LessI8)(int8_t, int8_t, uint8_t*, uint32_t, uint8_t*);
  12.     void (*LessI16)(int16_t, int16_t, uint8_t*, uint32_t, uint8_t*);
  13.     void (*LessI32)(int32_t, int32_t, uint8_t*, uint32_t, uint8_t*);
  14.     void (*LessI64)(int64_t, int64_t, uint8_t*, uint32_t, uint8_t*);
  15.     void (*LessI)(int, int, uint8_t*, uint32_t, uint8_t*);
  16.  
  17.     void (*LessC) (char, char, uint8_t*, uint32_t, uint8_t*);
  18.     void (*LessUC) (unsigned char, unsigned char, uint8_t*, uint32_t, uint8_t*);
  19.  
  20.     void (*LessF)(float, float, float, uint8_t*, uint32_t, uint8_t*);
  21.     void (*LessD)(double, double, double, uint8_t*, uint32_t, uint8_t*);
  22.     ...
  23.     ...
  24. }AssertFun_TypedefStruct;

Poniżej funkcje odpowiedzialne za sprawdzanie czy podany warunek jest prawdziwy bądź fałszywy:

  1. typedef struct AssertFuntion_TypedefStruct
  2. {
  3.     ...
  4.     ...
  5.     void (*IsTrue)(bool, uint8_t*, uint32_t, uint8_t *);
  6.     void (*IsFalse)(bool, uint8_t*, uint32_t, uint8_t *);
  7.     ...
  8.     ...
  9. }AssertFun_TypedefStruct;

Sprawdzanie czy wskaźnik wynosi NULL lub zero:

  1. typedef struct AssertFuntion_TypedefStruct
  2. {
  3.     ...
  4.     ...
  5.     void (*IsNull)(void*, uint8_t*, uint32_t, uint8_t*);
  6.     void (*IsNotNull)(void*, uint8_t*, uint32_t, uint8_t*);
  7.  
  8.     void (*IsZero)(void*, uint8_t*, uint32_t, uint8_t*);
  9.     void (*IsNotZero)(void*, uint8_t*, uint32_t, uint8_t*);
  10.     ...
  11.     ...
  12. }AssertFun_TypedefStruct;

Sprawdzenie danych odbywa się w następujących makrach:

  1. #define checkIfNull(x)      (x == NULL) ? true : false
  2. #define checkIfNotNull(x)   (x != NULL) ? true : false
  3. #define checkIfZero(x)      (x == 0) ? true : false
  4. #define checkIfNotZero(x)   (x != 0) ? true : false

Wywołanie funkcji podobnie jak poprzednio odbywa się na zasadzie sprawdzenia przesłanych parametrów po czym następuje przesłanie odpowiedniej ramki danych.

Inicjalizacja struktury może odbywać się w następujący sposób:

  1. AssertFun_TypedefStruct AssertFun = {
  2.         .AreEqualU8 = &Static_AreEqualU8_CheckAndWriteResponse,
  3.         .AreEqualU16 = &Static_AreEqualU16_CheckAndWriteResponse,
  4.         .AreEqualU32 = &Static_AreEqualU32_CheckAndWriteResponse,
  5.         .AreEqualU64 = &Static_AreEqualU64_CheckAndWriteResponse,
  6.         .AreEqualU = &Static_AreEqualU64_CheckAndWriteResponse,
  7.  
  8.         .AreEqualI8 = &Static_AreEqualI8_CheckAndWriteResponse,
  9.         .AreEqualI16 = &Static_AreEqualI16_CheckAndWriteResponse,
  10.         .AreEqualI32 = &Static_AreEqualI32_CheckAndWriteResponse,
  11.         .AreEqualI64 = &Static_AreEqualI64_CheckAndWriteResponse,
  12.         .AreEqualI = &Static_AreEqualI_CheckAndWriteResponse,
  13.  
  14.         .AreEqualF = &Static_AreEqualF_CheckAndWriteResponse,
  15.         .AreEqualD = &Static_AreEqualD_CheckAndWriteResponse,
  16.  
  17.         .GraterU8 = &Static_Grater_U8_CheckAndWriteResponse,
  18.         .GraterU16 = &Static_Grater_U16_CheckAndWriteResponse,
  19.         .GraterU32 = &Static_Grater_U32_CheckAndWriteResponse,
  20.         .GraterU64 = &Static_Grater_U64_CheckAndWriteResponse,
  21.         .GraterU = &Static_Grater_U64_CheckAndWriteResponse,
  22.  
  23.         .GraterI8 = &Static_Grater_I8_CheckAndWriteResponse,
  24.         .GraterI16 = &Static_Grater_I16_CheckAndWriteResponse,
  25.         .GraterI32 = &Static_Grater_I32_CheckAndWriteResponse,
  26.         .GraterI64 = &Static_Grater_I64_CheckAndWriteResponse,
  27.         .GraterI = &Static_Grater_I_CheckAndWriteResponse,
  28.  
  29.         .GraterC = &Static_Grater_C_CheckAndWriteResponse,
  30.         .GraterUC = &Static_Grater_UC_CheckAndWriteResponse,
  31.  
  32.         .GraterF = &Static_Grater_F_CheckAndWriteResponse,
  33.         .GraterD = &Static_Grater_D_CheckAndWriteResponse,
  34.  
  35.         .LessU8 = &Static_Less_U8_CheckAndWriteResponse,
  36.         .LessU16 = &Static_Less_U16_CheckAndWriteResponse,
  37.         .LessU32 = &Static_Less_U32_CheckAndWriteResponse,
  38.         .LessU64 = &Static_Less_U64_CheckAndWriteResponse,
  39.         .LessU = &Static_Less_U64_CheckAndWriteResponse,
  40.  
  41.         .LessI8 = &Static_Less_I8_CheckAndWriteResponse,
  42.         .LessI16 = &Static_Less_I16_CheckAndWriteResponse,
  43.         .LessI32 = &Static_Less_I32_CheckAndWriteResponse,
  44.         .LessI64 = &Static_Less_I64_CheckAndWriteResponse,
  45.         .LessI = &Static_Less_I_CheckAndWriteResponse,
  46.  
  47.         .LessC = &Static_Less_C_CheckAndWriteResponse,
  48.         .LessUC = &Static_Less_UC_CheckAndWriteResponse,
  49.  
  50.         .LessF = &Static_Less_F_CheckAndWriteResponse,
  51.         .LessD = &Static_Less_D_CheckAndWriteResponse,
  52.  
  53.         .IsTrue = &Static_IsTrue_CheckAndWriteResponse,
  54.         .IsFalse = &Static_IsFalse_CheckAndWriteResponse,
  55.  
  56.         .AreEqualU8_Array = &Static_AreEqualU8_Array_CheckAndWriteResponse,
  57.         .AreEqualU16_Array = &Static_AreEqualU16_Array_CheckAndWriteResponse,
  58.         .AreEqualU32_Array = &Static_AreEqualU32_Array_CheckAndWriteResponse,
  59.  
  60.         .IsNull = &Static_IsNull_CheckAndWriteResponse,
  61.         .IsNotNull = &Static_IsNotNull_CheckAndWriteResponse,
  62.  
  63.         .IsZero = &Static_IsZero_CheckAndWriteResponse,
  64.         .IsNotZero = &Static_IsNotZero_CheckAndWriteResponse
  65. };

Lub przez wpisanie danych w funkcji i wywołanie jej przed rozpoczęciem testów:

  1. void InitAssertFunction()
  2. {
  3.     AssertFun.AreEqualU8 = &Static_AreEqualU8_CheckAndWriteResponse;
  4.     AssertFun.AreEqualU16 = &Static_AreEqualU16_CheckAndWriteResponse;
  5.     AssertFun.AreEqualU32 = &Static_AreEqualU32_CheckAndWriteResponse;
  6.     AssertFun.AreEqualU64 = &Static_AreEqualU64_CheckAndWriteResponse;
  7.     AssertFun.AreEqualU = &Static_AreEqualU64_CheckAndWriteResponse;
  8.     ...
  9.     ...
  10. }

Oczywiście aby testy działały poprawnie należy wcześniej uruchomić UART. Poniżej uruchomienie interfejsu na testowanym przeze mnie mikrokontrolerze czyli LPC1769:

  1. void UART_MB_Initialize(void)
  2. {
  3.     PINSEL_CFG_Type PinCfg;
  4.     UART_CFG_Type UARTConfigStruct;
  5.  
  6.     PinCfg.Funcnum = 1;
  7.     PinCfg.OpenDrain = 0;
  8.     PinCfg.Pinmode = 0;
  9.     PinCfg.Pinnum = UARTX_MB_PIN_TX;
  10.     PinCfg.Portnum = UARTX_MB_PORT_NUM_TX;
  11.     PINSEL_ConfigPin(&PinCfg);
  12.     PinCfg.Pinnum = UARTX_MB_PIN_RX;
  13.     PinCfg.Portnum = UARTX_MB_PORT_NUM_RX;
  14.     PINSEL_ConfigPin(&PinCfg);
  15.  
  16.     UARTConfigStruct.Baud_rate = UARTX_MB_BAUDRATE;
  17.     UARTConfigStruct.Databits = UARTX_MB_DATABITS;
  18.     UARTConfigStruct.Parity = UARTX_MB_PARITY;
  19.     UARTConfigStruct.Stopbits = UARTX_MB_STOPBITS;
  20.     UART_Init((LPC_UART_TypeDef *)UARTX_MB_PORT, &UARTConfigStruct);
  21.  
  22.     //Rozpocznij transmisję
  23.     UART_TxCmd((LPC_UART_TypeDef *)UARTX_MB_PORT, ENABLE);
  24.  
  25.     //Uruchom przerwania od UARTA
  26.     UART_IntConfig((LPC_UART_TypeDef *)UARTX_MB_PORT, UART_INTCFG_RBR, ENABLE);
  27.     NVIC_EnableIRQ(UARTX_MB_INTERRUPT);
  28. }

Przesłanie danych wygląda następująco:

  1. static void USART_SendChar(unsigned char c)
  2. {
  3.     while( !(LPC_UART0->LSR & UART_LSR_THRE) );
  4.     LPC_UART0->THR = c;
  5. }
  6.  
  7. size_t USART_MB_SendString(const char * str) {
  8.     uint16_t charCount = 0;
  9.  
  10.     while(*str) {
  11.         USART_SendChar(*str++);
  12.         charCount++;
  13.     }
  14.     return charCount;
  15. }

Przykładowe podstawowe testy mogłyby wyglądać następująco:

  1. void AddTest()
  2. {
  3.     uint16_t a = 456;
  4.     uint16_t b = 206;
  5.     uint16_t responseValue = a + b;
  6.     uint16_t responseFunctionValue = AddFunction(a, b);
  7.  
  8.     AssertFun.AreEqualU16(responseValue, responseFunctionValue, FILE_NAME, __LINE__, __func__);
  9. }
  10.  
  11. void SubstractionTest()
  12. {
  13.     uint16_t a = 456;
  14.     uint16_t b = 206;
  15.     uint16_t responseValue = a - b;
  16.     uint16_t responseFunctionValue = SubstractionFunction(a, b);
  17.  
  18.     AssertFun.AreEqualU16(responseValue, responseFunctionValue, FILE_NAME, __LINE__, __func__);
  19. }
  20.  
  21. void CompareArraysTest()
  22. {
  23.     uint8_t array1[5] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x09, 0x08};
  24.     uint8_t array2[5] = {0x01, 0x02, 0x04, 0x04, 0x05, 0x06, 0x09, 0x08};
  25.  
  26.     AssertFun.AreEqualU8_Array(&array1[0], &array2[0], (sizeof(array1)/sizeof(array1[0])), FILE_NAME, __LINE__, __func__);
  27. }

Wynik testów przedstawionych powyżej zaobserwowany w programie terminal:


Bibliotekę z funkcjami opisanymi w tym poście można pobrać z dysku Google pod tym linkiem.