
W SparkPost poświęcamy dużo czasu i wysiłku na testowanie naszego kodu. Nasza platforma jest napisana w C, a ostatnio badałem integrację z frameworkiem do testów jednostkowych zwanym „CppUTest”, który oferuje testowanie w stylu xUnit dla C/C++. Ten framework jest solidny, bogaty w funkcje i jest aktywnie rozwijany, co czyni go doskonałym wyborem. Oferuje również warstwę integracji C, co ułatwiło jego użycie z naszym kodem C, mimo że większość frameworka jest w C++. Ten samouczek opisuje, jak rozpocząć korzystanie z CppUTest w swoich projektach.
W SparkPost poświęcamy dużo czasu i wysiłku na testowanie naszego kodu. Nasza platforma jest napisana w C, a ostatnio badałem integrację z frameworkiem do testowania jednostkowego o nazwie „CppUTest”, który zapewnia testowanie w stylu xUnit dla C/C++. Ten framework jest solidny, bogaty w funkcje i znajduje się w fazie aktywnego rozwoju, co czyni go doskonałym wyborem. Zapewnia również warstwę integracji C, co ułatwia korzystanie z naszego kodu platformy C, mimo że większość frameworka jest w C++. Ten samouczek obejmuje, jak rozpocząć pracę z CppUTest w swoich własnych projektach.
Pobieranie CppUTest
Strona projektu CppUTest jest dostępna tutaj, a repozytorium znajduje się na githubie. Jest również uwzględniony w repozytoriach zarządzania pakietami dla wielu dystrybucji linuxa, jak również w homebrew na Mac OS. Przykłady, które się pojawią, były wykonane na Mac OS X, ale zostały one zaczerpnięte z kodu napisanego dla Red Hat, systemu, na którym działa nasza platforma.
Podstawy są dobrze udokumentowane na stronie głównej CppUTest. Zamierzamy szybko przez to przejść i dotrzeć do niektórych z bardziej interesujących funkcji.
Laying the Foundation
Projekt Makefile
Plik makefile projektu będzie na tym samym poziomie co katalogi ‘src’ i ‘t’ w katalogu głównym projektu. Powinno to wyglądać w ten sposób:
# Makefile SRC_DIR=./src CODE_DIR=$(SRC_DIR)/code OUT=example TEST_DIR=t test: make -C $(TEST_DIR) test_clean: make -C $(TEST_DIR) clean code.o: gcc -c -I$(CODE_DIR) $(CODE_DIR)/code.cpp -o $(CODE_DIR)/code.o main: code.o gcc -I$(CODE_DIR) $(CODE_DIR)/code.o $(SRC_DIR)/main.cpp -o $(OUT) all: test main clean: test_clean rm $(SRC_DIR)/*.o $(CODE_DIR)/*.o $(OUT)
Zauważ, że używa to ‘make -C’ dla celów testowych – co oznacza, że wywoła ‘make’ ponownie używając pliku makefile w katalogu testowym.
Na tym etapie możemy skompilować kod ‘src’ za pomocą pliku makefile i zobaczyć, że działa:
[]$ make main gcc -c -I./src/code ./src/code/code.cpp -o ./src/code/code.o gcc -I./src/code ./src/code/code.o ./src/main.cpp -o example []$ ./example hello world!
Testy Makefile
Dla testów sprawy są nieco bardziej skomplikowane, ponieważ musimy poprawnie załadować i zintegrować się z biblioteką CppUTest.
Repozytorium CppUTest dostarcza plik o nazwie „MakefileWorker.mk”. Zapewnia on wiele funkcji, które ułatwiają budowanie z CppUTest. Plik znajduje się w katalogu „build” w repozytorium git. W tym samouczku założymy, że został skopiowany do katalogu „t/”. Można go użyć w następujący sposób:
# nie chcemy używać ścieżek względnych, więc ustawiamy te zmienne PROJECT_DIR=/path/to/project SRC_DIR=$(PROJECT_DIR)/src TEST_DIR=$(PROJECT_DIR)/t # określ, gdzie znajduje się kod źródłowy i dołączane INCLUDE_DIRS=$(SRC_DIR)/code SRC_DIRS=$(SRC_DIR)/code # określ, gdzie znajduje się kod testowy TEST_SRC_DIRS = $(TEST_DIR) # jak nazwać testowy plik binarny TEST_TARGET=example # gdzie jest zlokalizowana biblioteka cpputest CPPUTEST_HOME=/usr/local # uruchom MakefileWorker.mk z definicjami tutaj include MakefileWorker.mk
Pamiętaj, że CPPUTEST_HOME musi być ustawiony na miejsce, gdzie zainstalowano CppUTest. Jeśli zainstalowałeś pakiet dystrybucyjny, zazwyczaj będzie to pod /usr/local w systemie linux/mac. Jeśli sam zdejmowałeś repozytorium, to tam, gdzie znajduje się to repozytorium.
Wszystkie te opcje są udokumentowane w MakefileWorker.mk.
MakefileWorker.mk dodaje również kilka celów makefile, w tym następujące:
all – buduje testy wskazane przez makefile
clean – usuwa wszystkie obiekty i pliki gcov wygenerowane dla testów
realclean – usuwa wszelkie obiekty lub pliki gcov w całym drzewie katalogów
flags – wypisuje wszystkie skonfigurowane flagi używane do kompilacji testów
debug – wypisuje wszystkie pliki źródłowe, obiekty, zależności i 'rzeczy do czyszczenia'
Code Coverage
Testowanie jednostkowe nie byłoby pełne bez raportu pokrycia. Narzędzie używane do tego w projektach korzystających z gcc to gcov, dostępne jako część standardowego pakietu narzędzi gcc. Cpputest łatwo integruje się z gcov, wystarczy dodać tę linię do pliku makefile:
CPPUTEST_USE_GCOV=Y
Następnie musimy upewnić się, że skrypt filterGcov.sh z tego repozytorium znajduje się w ‘/scripts/filterGcov.sh’ w odniesieniu do miejsca, gdzie ustawisz ‘CPPUTEST_HOME’. Powinien również mieć uprawnienia do wykonywania.
W przykładowym pliku Makefile będzie on umieszczony w ‘/usr/local/scripts/filterGcov.sh’. Jeśli uruchamiasz CppUTest z repozytorium, wszystko powinno działać bez modyfikacji.
Kiedy to jest na miejscu, możesz po prostu uruchomić ‘make gcov’ i analiza zostanie wygenerowana dla Ciebie. W naszym przypadku będziemy musieli ‘make -B’, aby ponownie zbudować pliki obiektu z włączonym gcov:
[]$ make -B gcov < wynik kompilacji > dla d w /Users/ykuperman/code/blogpost/qa/src/code ; do \ FILES=`ls $d/*.c $d/*.cc $d/*.cpp 2> /dev/null` ; \ gcov --object-directory objs/$d $FILES >> gcov_output.txt 2>>gcov_error.txt ; \ done dla f w ; do \ gcov --object-directory objs/$f $f >> gcov_output.txt 2>>gcov_error.txt ; \ done /usr/local/scripts/filterGcov.sh gcov_output.txt gcov_error.txt gcov_report.txt example.txt cat gcov_report.txt 100.00% /Users/ykuperman/code/blogpost/qa/src/code/code.cpp mkdir -p gcov mv *.gcov gcov mv gcov_* gcov Zobacz katalog gcov, aby uzyskać szczegóły
To wyprowadzi szereg plików do nowego katalogu ‘gcov’. Są to:
code.cpp.gcov – rzeczywisty plik ‘gcov’ dla testowanego kodu
gcov_error.txt – raport o błędach (w naszym przypadku powinien być pusty)
gcov_output.txt – rzeczywisty wynik uruchomionego polecenia gcov
gcov_report.txt – podsumowanie pokrycia dla każdego testowanego pliku
gcov_report.txt.html – wersja html gcov_report
Cpputest Wykrywanie Wycieku Pamięci
Cpputest pozwala automatycznie wykrywać wycieki pamięci poprzez redefiniowanie standardowej rodziny funkcji „malloc/free” do używania własnych opakowań. Dzięki temu można szybko wykrywać i raportować wycieki dla każdego wykonania testu. To jest domyślnie włączone w MakefileWorker.mk, więc jest już włączone zgodnie z opisanymi do tej pory krokami.
Aby zilustrować, wycieknijmy trochę pamięci w test_func() !
Wracając do code.c, dodajemy malloc() do funkcji, w następujący sposób:
int test_func() { malloc(1); return 1; }
Teraz, po ponownym skompilowaniu, pojawia się następujący błąd:
test.cpp:9: błąd: Niepowodzenie w TEST(AwesomeExamples, FirstExample) Znaleziono wyciek(i) pamięci. Numer alokacji (4) Rozmiar wycieku: 1 Alokowany w: ./code.c, linia: 6. Typ: "malloc" Pamięć: <0x7fc9e94056d0> Zawartość: 0000: 00 |.| Całkowita liczba wycieków: 1
To pokazuje, który test spowodował wyciek, gdzie wyciek wystąpił w kodzie źródłowym i co było w wycieku pamięci. Bardzo pomocne!
Istnieje kilka zastrzeżeń dotyczących tej funkcji:
Cpputest używa makr preprocesora do dynamicznego redefiniowania wszystkich wywołań standardowych funkcji zarządzania pamięcią. Oznacza to, że zadziała tylko dla wywołań w testowanym kodzie źródłowym, ponieważ to jest kompilowane z przesłonięciami CppUTest. Wyciek w bibliotekach zlinkowanych nie zostanie wykryty.
Czasami pamięć, która jest alokowana na cały czas trwania procesu, nie jest przeznaczona do zwolnienia. Może to generować mnóstwo błędów, jeśli testujesz moduł o takim zachowaniu. Aby wyłączyć wykrywanie wycieków, możesz to zrobić:
CPPUTEST_USE_MEM_LEAK_DETECTION=N
Zainteresowany czymś więcej?
To tylko wierzchołek góry lodowej, jeśli chodzi o wszystkie funkcje zawarte w tym narzędziu. Oprócz podstaw omówionych tutaj, posiada ono również framework do mockowania, bezpośrednią warstwę integracji z C oraz framework pluginów, żeby wymienić kilka znaczących. Repozytorium zawiera także cały katalog skryptów pomocniczych, które mogą pomóc zautomatyzować niektóre rutynowe czynności związane z pracą z frameworkiem.
Mam nadzieję, że informacje tutaj pomogą Ci poprawić jakość kodu C/C++ przy użyciu tego świetnego narzędzia!