sobota, 1 września 2018

[1] CppUnit - Asercje

Ten post chciałbym poświęcić na opisanie dostępnych Asercji w CPPUnit wraz z sposobami ich testowania.
[Źródło: http://academy.trendonix.com]

Poniższe przykłady opiszę na jakichś przykładowych funkcjach.

CPPUNIT_ASSERT


Podstawowy sposób testowania. Sprawdza tyczy dany warunek jest prawdziwy.

Funkcja testowa wygląda następująco:

  1. bool checkCondition(uint8_t val1, uint8_t val2)
  2. {
  3.     if(val1 < val2 || val1 == val2)
  4.     {
  5.         return true;
  6.     }
  7.     return false;
  8. }

I przykładowy test z użyciem wspomnianej asercji:

  1. void UnitTest::checkCondition_ValueOneSmallerThenValueTwo_ReturnTrue()
  2. {
  3.     uint8_t value1ToPass = 23;
  4.     uint8_t value2ToPass = 45;
  5.     CPPUNIT_ASSERT(checkCondition(value1ToPass, value2ToPass));
  6. }

Ten warunek będzie działał także dla porównywania dwóch wartości w warunku. Funkcja testowa:

  1. int add(int a, int b)
  2. {
  3.     return a + b;
  4. }

Instrukcja zadziała poprawnie tylko gdy dwie podane wartości będą sobie równe:

  1. void UnitTest::add_CheckIfSumIsOK()
  2. {
  3.     int a = 5;
  4.     int b = 4;
  5.    
  6.     int expectedValue = (+ b);
  7.     CPPUNIT_ASSERT(add(a, b) == expectedValue);
  8. }

Do instrukcji możemy wprowadzić wszystkie warunki jaki by można było wstawić np. do instrukcji warunkowej If.

CPPUNIT_ASSERT_MESSAGE


To samo co instrukcja CPPUNIT_Assert, z tą różnicą, że w przypadku błędu zostanie zwrócony ciąg znaków na ekranie.

Poniższy test zgłosi błąd:

  1. void UnitTest::checkCondition_ValueOneSmallerThenValueTwo_ReturnTrue()
  2. {
  3.     uint8_t value1ToPass = 50;
  4.     uint8_t value2ToPass = 45;
  5.     CPPUNIT_ASSERT_MESSAGE("Value 1 is bigger then value 2", checkCondition(value1ToPass, value2ToPass));
  6. }

W związku z tym, że została podana wiadomość to oprócz standardowych komunikatów zostanie wyświetlona zdefiniowana informacja:


CPPUNIT_FAIL


Wywołuje błąd z zdefiniowaną wiadomością:

  1. void UnitTest::checkCondition_ValueOneSmallerThenValueTwo_ReturnTrue()
  2. {
  3.     uint8_t value1ToPass = 50;
  4.     uint8_t value2ToPass = 45;
  5.     uint8_t operationStatus = checkCondition(value1ToPass, value2ToPass);
  6.     CPPUNIT_FAIL("Error MSG");
  7. }

Wygenerowany błąd wygląda następująco:


CPPUNIT_ASSERT_EQUAL


Następny warunek sprawdza czy wartości są sobie równe:

  1. void UnitTest::checkCondition_ValueOneSmallerThenValueTwo_ReturnTrue()
  2. {
  3.     uint32_t value1ToPass = 50;
  4.     uint32_t value2ToPass = 45;
  5.     uint32_t expectedValue = value2ToPass * value1ToPass;
  6.     uint32_t retValue = multipleValue(value1ToPass, value2ToPass);
  7.     CPPUNIT_ASSERT_EQUAL(expectedValue, retValue);
  8. }

W przypadku błędu zostaną wyświetlone dokładne informacje wraz z otrzymanymi danymi o wartościach:



CPPUNIT_ASSERT_EQUAL


Działanie instrukcji podobne do poprzedniego, tym razem natomiast zostanie wyświetlona wiadomość w przypadku nieudanego testu. Przykładowy test:

  1. void UnitTest::checkCondition_ValueOneSmallerThenValueTwo_ReturnTrue()
  2. {
  3.     uint32_t value1ToPass = 50;
  4.     uint32_t value2ToPass = 45;
  5.     uint32_t expectedValue = value2ToPass * value1ToPass + 5;
  6.     uint32_t retValue = multipleValue(value1ToPass, value2ToPass);
  7.     CPPUNIT_ASSERT_EQUAL_MESSAGE("Error value diff then expected", expectedValue, retValue);
  8. }

Wartość otrzymana w przypadku błędu:


CPPUNIT_ASSERT_DOUBLES_EQUAL

Głównym zadaniem tej funkcji jest sprawdzanie liczb zmiennoprzecinkowych z określoną dokładnością. Przykład asercji poniżej:

  1. void UnitTest::doublesEqual()
  2. {
  3.     double const deltaExpected = 3.14;
  4.     double const deltaCalculated = 22.0 / 7.0;
  5.     double const maximumTolerance = 0.005;
  6.  
  7.     CPPUNIT_ASSERT_DOUBLES_EQUAL(deltaExpected, deltaCalculated, maximumTolerance);
  8. }

Gdy ustawimy mniejszą tolerancję i wynik będzie za bardzo rozbieżny to uzyskamy następującą informację o błędzie:


Drugi przypadek pozwala na sprawdzenie czy znaleźliśmy ilość licz z jakiegoś zakresu. Funkcja którą będę testował zlicza ilość odpowiednich wartości w tablicy do momentu otrzymania wartości 0xA2.

  1. static uint8_t countValuesInTable(uint8_t *table, uint8_t tableSize)
  2. {
  3.     uint8_t countValue = 0;
  4.    
  5.     for(uint8_t i=0; i<tableSize; i++)
  6.     {
  7.         if(*(table + i) >= 0xA5)
  8.         {
  9.             countValue++;
  10.         }
  11.         else if(*(table + i) >= 0xA2)
  12.         {
  13.             return 0;
  14.         }
  15.     }
  16.    
  17.     return countValue;
  18. }

Projekt testowy wykorzystujący wspomnianą instrukcję może sprawdzać czy otrzymaliśmy ilość danych w zdefiniowanym zakresie:

  1. void UnitTest::countValuesInTable_CheckIfValuesAreFromSelectedRange()
  2. {
  3.     uint8_t table[200];
  4.  
  5.     prepareTableForFunc_countValuesInTable(&table[0], sizeof(table));
  6.  
  7.     uint8_t countedValues = countValuesInTable(&table[0], sizeof(table));
  8.  
  9.     CPPUNIT_ASSERT_DOUBLES_EQUAL(0xA0, countedValues, 0x0F);
  10. }

Tutaj najpierw tworzymy nową tablicę następnie w funkcji zewnętrznej wprowadzamy wartości do tablicy z zakresu od 0 do 0xC8. Następnie wywołujemy funkcję zliczającą elementy w tablicy. Końcowy element czyli asercja sprawdza czy udało się odczytać ilość danych z podanego zakresu.



CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE


Ta sama funkcja co wcześniej tylko z wyświetlaniem wiadomości. Zmieniony kod z wcześniejszego przykładu:

CPPUNIT_ASSERT_THROW


W tym przypadku sprawdzane jest czy w danej funkcji wywoływany jest wyjątek. Pod spodem testowana funkcja w języku C++:

  1. double checkValue(double checkValue)
  2. {
  3.     if (checkValue < 0.0) {
  4.         throw std::invalid_argument("Value must be bigger then 0");
  5.     }
  6.     else if(checkValue > 5){
  7.         throw std::invalid_argument("Value must be lower then 5");
  8.     }
  9.  
  10.     return checkValue * 3.14;
  11. }

Przeprowadzenie testów dla instrukcji warunkowych mogło by wyglądać następująco:

  1. void UnitTest::checkValue_LowerThenZero()
  2. {
  3.     CPPUNIT_ASSERT_THROW(checkValue(-1), std::invalid_argument);
  4. }
  5.  
  6. void UnitTest::checkValue_BiggerThenFive()
  7. {
  8.     CPPUNIT_ASSERT_THROW(checkValue(6), std::invalid_argument);
  9. }

CPPUNIT_ASSERT_THROW_MESSAGE


To samo co powyżej tylko z dodatkową wiadomością w przypadku błędu. Zmodyfikowane testy z przykładu powyżej:

  1. void UnitTest::checkValue_LowerThenZero()
  2. {
  3.     CPPUNIT_ASSERT_THROW_MESSAGE("No exception invalid_argument", checkValue(-1), std::invalid_argument);
  4. }
  5.  
  6. void UnitTest::checkValue_BiggerThenFive()
  7. {
  8.     CPPUNIT_ASSERT_THROW_MESSAGE("No exception invalid_argument", checkValue(6), std::invalid_argument);
  9. }

CPPUNIT_ASSERT_NO_THROW


Ta asercja nie wyrzuca błędów dla niepoprawnych parametrów.

CPPUNIT_ASSERT_NO_THROW_MESSAGE


To samo co powyżej, tylko w przypadku błędu ma być wyświetlana informacja dla użytkownika.

CPPUNIT_ASSERT_ASSERTION_FAIL


Wywołanie błędu bez względu na wartość wprowadzonego parametru, warunku.

  1. void UnitTest::assertionFail()
  2. {
  3.     uint8_t value = 5;
  4.     CPPUNIT_ASSERT_ASSERTION_FAIL(value = 15);
  5. }

CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE

To samo co powyżej tylko z wiadomością dla użytkownika.

  1. void UnitTest::assertionFail()
  2. {
  3.     uint8_t value = 5;
  4.     CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("Change parameter", value = 15);
  5. }

CPPUNIT_ASSERT_ASSERTION_PASS

Poprawne wykonanie asercji, bez względu na jej wynik. Chyba, że zostanie wyrzucony wyjątek.

CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE

Asercja wykonuje się poprawnie, chyba że zostanie wyrzucony wyjątek. Wtedy wyświetli się informacja o nim oraz dodatkowa wiadomość przygotowana przez użytkownika.