Zasięg

Grow

Manage

Automate

Zasięg

Grow

Manage

Automate

Rozpoczęcie pracy z CppUTest

Ptak

14 maj 2017

Email

1 min read

Rozpoczęcie pracy z CppUTest

Ptak

14 maj 2017

Email

1 min read

Rozpoczęcie pracy z CppUTest

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

Na początek napiszmy trochę kodu!

Nasz projekt testowy będzie miał plik „main” i będzie zawierał bibliotekę narzędziową o nazwie „code”. Biblioteka zapewni prostą funkcję, która zwróci 1 (na razie). Pliki będą rozmieszczone w ten sposób:

├── src │ ├── code │ │ ├── code.cpp │ │ └── code.h │ └── main.cpp └── t ├── main.cpp └── test.cpp

Zacznijmy od napisania plików w katalogu src/

// src/main.cpp #include <stdlib.h> #include <stdio.h> #include "code.h" int main(void) { test_func(); printf("hello world!\n"); exit(0); }

// src/code/code.cpp #include <stdlib.h> #include "code.h" int test_func () { return 1; }

// src/code/code.h #ifndef __code_h__ #define __code_h__ int test_func (); #endif

Teraz przeprowadzimy testy, które będą umieszczone w katalogu t/. Pierwszą rzeczą, którą musimy zrobić, jest ustawienie uruchamiacza testów, który będzie uruchamiać nasze pliki testowe. To jest również funkcja „main”, która zostanie wykonana po skompilowaniu wszystkiego:

// t/main.cpp #include "CppUTest/CommandLineTestRunner.h" int main(int ac, char** av) { return CommandLineTestRunner::RunAllTests(ac, av); }

Teraz możemy napisać nasz pierwszy moduł testowy:

// t/test.cpp #include "CppUTest/TestHarness.h" #include "code.h" TEST_GROUP(AwesomeExamples) { }; TEST(AwesomeExamples, FirstExample) { int x = test_func(); CHECK_EQUAL(1, x); }

Następnie musimy napisać pliki makefile. Potrzebujemy dwóch: jednego dla plików projektu w katalogu src/, a drugiego dla testów.

Na początek napiszmy trochę kodu!

Nasz projekt testowy będzie miał plik „main” i będzie zawierał bibliotekę narzędziową o nazwie „code”. Biblioteka zapewni prostą funkcję, która zwróci 1 (na razie). Pliki będą rozmieszczone w ten sposób:

├── src │ ├── code │ │ ├── code.cpp │ │ └── code.h │ └── main.cpp └── t ├── main.cpp └── test.cpp

Zacznijmy od napisania plików w katalogu src/

// src/main.cpp #include <stdlib.h> #include <stdio.h> #include "code.h" int main(void) { test_func(); printf("hello world!\n"); exit(0); }

// src/code/code.cpp #include <stdlib.h> #include "code.h" int test_func () { return 1; }

// src/code/code.h #ifndef __code_h__ #define __code_h__ int test_func (); #endif

Teraz przeprowadzimy testy, które będą umieszczone w katalogu t/. Pierwszą rzeczą, którą musimy zrobić, jest ustawienie uruchamiacza testów, który będzie uruchamiać nasze pliki testowe. To jest również funkcja „main”, która zostanie wykonana po skompilowaniu wszystkiego:

// t/main.cpp #include "CppUTest/CommandLineTestRunner.h" int main(int ac, char** av) { return CommandLineTestRunner::RunAllTests(ac, av); }

Teraz możemy napisać nasz pierwszy moduł testowy:

// t/test.cpp #include "CppUTest/TestHarness.h" #include "code.h" TEST_GROUP(AwesomeExamples) { }; TEST(AwesomeExamples, FirstExample) { int x = test_func(); CHECK_EQUAL(1, x); }

Następnie musimy napisać pliki makefile. Potrzebujemy dwóch: jednego dla plików projektu w katalogu src/, a drugiego dla testów.

Na początek napiszmy trochę kodu!

Nasz projekt testowy będzie miał plik „main” i będzie zawierał bibliotekę narzędziową o nazwie „code”. Biblioteka zapewni prostą funkcję, która zwróci 1 (na razie). Pliki będą rozmieszczone w ten sposób:

├── src │ ├── code │ │ ├── code.cpp │ │ └── code.h │ └── main.cpp └── t ├── main.cpp └── test.cpp

Zacznijmy od napisania plików w katalogu src/

// src/main.cpp #include <stdlib.h> #include <stdio.h> #include "code.h" int main(void) { test_func(); printf("hello world!\n"); exit(0); }

// src/code/code.cpp #include <stdlib.h> #include "code.h" int test_func () { return 1; }

// src/code/code.h #ifndef __code_h__ #define __code_h__ int test_func (); #endif

Teraz przeprowadzimy testy, które będą umieszczone w katalogu t/. Pierwszą rzeczą, którą musimy zrobić, jest ustawienie uruchamiacza testów, który będzie uruchamiać nasze pliki testowe. To jest również funkcja „main”, która zostanie wykonana po skompilowaniu wszystkiego:

// t/main.cpp #include "CppUTest/CommandLineTestRunner.h" int main(int ac, char** av) { return CommandLineTestRunner::RunAllTests(ac, av); }

Teraz możemy napisać nasz pierwszy moduł testowy:

// t/test.cpp #include "CppUTest/TestHarness.h" #include "code.h" TEST_GROUP(AwesomeExamples) { }; TEST(AwesomeExamples, FirstExample) { int x = test_func(); CHECK_EQUAL(1, x); }

Następnie musimy napisać pliki makefile. Potrzebujemy dwóch: jednego dla plików projektu w katalogu src/, a drugiego dla testów.

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:

  1. all – buduje testy wskazane przez makefile

  2. clean – usuwa wszystkie obiekty i pliki gcov wygenerowane dla testów

  3. realclean – usuwa wszelkie obiekty lub pliki gcov w całym drzewie katalogów

  4. flags – wypisuje wszystkie skonfigurowane flagi używane do kompilacji testów

  5. 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:

  1. code.cpp.gcov – rzeczywisty plik ‘gcov’ dla testowanego kodu

  2. gcov_error.txt – raport o błędach (w naszym przypadku powinien być pusty)

  3. gcov_output.txt – rzeczywisty wynik uruchomionego polecenia gcov

  4. gcov_report.txt – podsumowanie pokrycia dla każdego testowanego pliku

  5. 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:

  1. 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.

  2. 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!

Połączmy Cię z ekspertem Bird.
Zobacz pełną moc Bird w 30 minut.

Przesyłając, zgadzasz się, że Bird może kontaktować się z Tobą w sprawie naszych produktów i usług.

Możesz zrezygnować z subskrypcji w dowolnym momencie. Zobacz Privacy Statement firmy Bird, aby uzyskać szczegóły dotyczące przetwarzania danych.

Company

Biuletyn

Bądź na bieżąco z Bird dzięki cotygodniowym aktualizacjom do Twojej skrzynki odbiorczej.

Połączmy Cię z ekspertem Bird.
Zobacz pełną moc Bird w 30 minut.

Przesyłając, zgadzasz się, że Bird może kontaktować się z Tobą w sprawie naszych produktów i usług.

Możesz zrezygnować z subskrypcji w dowolnym momencie. Zobacz Privacy Statement firmy Bird, aby uzyskać szczegóły dotyczące przetwarzania danych.

Company

Biuletyn

Bądź na bieżąco z Bird dzięki cotygodniowym aktualizacjom do Twojej skrzynki odbiorczej.

Połączmy Cię z ekspertem Bird.
Zobacz pełną moc Bird w 30 minut.

Przesyłając, zgadzasz się, że Bird może kontaktować się z Tobą w sprawie naszych produktów i usług.

Możesz zrezygnować z subskrypcji w dowolnym momencie. Zobacz Privacy Statement firmy Bird, aby uzyskać szczegóły dotyczące przetwarzania danych.

R

Reach

G

Grow

M

Manage

A

Automate

Company

Biuletyn

Bądź na bieżąco z Bird dzięki cotygodniowym aktualizacjom do Twojej skrzynki odbiorczej.