Zaawansowane CPP/Ćwiczenia 4: Testowanie

From Studia Informatyczne

Ćwiczenie 1

Do przykładu testów max napisanych w CppUnit dodaj testy sprawdzające wersję szukającą maksimum w tablicy. Wykorzystaj w tym celu dodatkową klasę testującą. Sprawdź swój test na implementacji max_error.h. Znajdź znajdujące się tam błędy.

Rozwiązanie

Ćwiczenie 2

Zaproponuj i napisz, używając CppUnit, testy klasy Stack. Implementacja tej klasy znajduje się w pliku stack.h. Nie zapomnij o testach kopiowania i przypisywania oraz destruktora.

Sprawdź czy stos zaimplementowany w pliku stack_dyn.h, przechodzi twoje testy. Jeśli tak, to znajdź błędy w kodzie i tak popraw testy, aby wyłapywały te błędy. Popraw kod tak, aby przeszedł uzupełnione testy.

Rozwiązanie

Test stosu podzielę na cztery części: pierwsza zawierać będzie test podstawowych funkcji, druga - destruktora, a trzecia i czwarta - konstruktora kopiującego i operatora przypisania.

Test podstawowy zawarty jest w składowej test_base() klasy stack_test w pliku stack_test_cppunit.cpp. Polega on na naprzemiennym wkładaniu i zdejmowaniu ze stosu liczb całkowitych i sprawdzaniu poprawności zdejmowanych elementów. Na końcu trzykrotnie wypełniamy i opróżniamy cały stos.

Pozostałe testy wymagają więcej komentarza. Ich zadaniem jest testowanie funkcji generowanych automatycznie. W tym przypadku działają one prawidłowo, co można dość łatwo stwierdzić bez przeprowadzania testów. W ogólności jednak nie musi tak być. Przykładem może być implementacja stack_dyn.h.

Zacznijmy od destruktora. Zadaniem destruktora jest usunięcie pamięci zajętej przez stos, a objawem błędu w destruktorze jest wyciek pamięci. Nie znam uniwersalnego sposobu testowania wycieku pamięci. Zwykle używam prostej, ale ograniczonej sztuczki. Polega ona na tworzeniu i niszczeniu dużego stosu w dużej pętli.

   try {
     for(int i =   0;i<1000;i++) {
         Stack<double,1000000> s;
     }
   } catch(std::bad_alloc &) {
     CPPUNIT_ASSERT(0);
   };

Jeśli destruktor nie zwolni pamięci, to w każdej iteracji następował będzie wyciek i pamięci w końcu zabraknie. Wtedy kolejne wywołanie operator new rzuci wyjątek. Oczywiście jeśli wyciek będzie mały np. parę bajtów, to ta metoda go nie wychwyci.

Testy kopiowania i przypisania wyglądają podobnie. W obu przypadkach wynikiem powinny być dwa stosy o takiej samej zawartości. Proszę jednak zauważyć, że nie posiadamy możliwości bezpośredniego porównania zawartości stosów bez opróżniania ich. Nawet zresztą gdybyśmy mieli operator porównia, to i tak wymagałby on testowania. W tym celu napisałem funkcję equal, która porównuje dwa stosy, opróżniając je, a następnie uzupełniając z powrotem. Kod znajduje się w pliku stack_test_cppunit.cpp.

Częstym błędem podaczas kopiowania/przypisywania jest doprowadzenie do współdzielenia reprezentacji. Musimy więc sprawdzić, czy dwie kopie nie tylko są równe, ale i niezależne. W tym celu napisałem funkcję inc, która zwiększa każdy element stosu liczb całkowitych o jeden. W przypadku współdzielenia reprezentacji nastąpi zwiększenie obu stosów i oba stosy dalej będą równe.

Dodatkowo w trakcie przypisywania może dojść do wycieku pamięci w stosie, do którego przypisujemy nową wartość. Testujemy to podobnie jak w przypadku destruktora.

Całość kodu znajduje się w pliku stack_test_cppunit.cpp.