Reach

Grow

Manage

Automate

Reach

Grow

Manage

Automate

Iniziare con CppUTest

Uccello

14 mag 2017

Email

1 min read

Iniziare con CppUTest

Uccello

14 mag 2017

Email

1 min read

Iniziare con CppUTest

In SparkPost, investiamo molto tempo ed energie nel testare il nostro codice. La nostra piattaforma è scritta in C, e recentemente ho fatto ricerche per integrare un framework di test unitari chiamato “CppUTest”, che offre test in stile xUnit per C/C++. Questo framework è robusto, ricco di funzionalità e in fase di sviluppo attivo, il che lo rende una scelta eccellente. Fornisce anche uno strato di integrazione C che ha reso facile l'uso con il nostro codice C della piattaforma, anche se la maggior parte del framework è in C++. Questo tutorial copre come iniziare a utilizzare CppUTest nei tuoi progetti.

At SparkPost, dedichiamo molto tempo ed energie al testing del nostro codice. La nostra piattaforma è scritta in C e recentemente ho studiato l'integrazione con un framework di unit test chiamato “CppUTest”, che fornisce test in stile xUnit per C/C++. Questo framework è robusto, ricco di funzionalità e in fase di sviluppo attivo, il che lo rende una scelta eccellente. Fornisce anche un layer di integrazione C che ha reso facile l'uso con il codice C della nostra piattaforma anche se la maggior parte del framework è in C++. Questo tutorial copre come iniziare a usare CppUTest nei tuoi progetti.

Scaricando CppUTest

La pagina del progetto CppUTest è disponibile qui, e il repository è su github. È anche incluso nei repository di gestione dei pacchetti per molte distro Linux, così come in homebrew su Mac OS. Gli esempi che seguono sono stati eseguiti su Mac OS X, ma sono derivati da codice scritto per Red Hat, il sistema operativo su cui gira la nostra piattaforma.

Le basi sono ben documentate sulla home page di CppUTest. Passeremo rapidamente attraverso di esse per arrivare a caratteristiche più interessanti.

Gettare le fondamenta

Prima di tutto, scriviamo un po' di codice!

Il nostro progetto di test avrà un file 'main' e includerà una libreria di utilità chiamata 'code'. La libreria fornirà una semplice funzione che restituisce 1 (per ora). I file saranno organizzati in questo modo:

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

Iniziamo a scrivere i file 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

Adesso, facciamo i test, che si troveranno nella directory t/. La prima cosa da fare è impostare un test runner che eseguirà i nostri file di test. Questa è anche la funzione 'main' che verrà eseguita una volta compilato tutto:

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

Ora possiamo scrivere il nostro primo modulo di test:

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

Successivamente, dobbiamo scrivere i makefile. Ne avremo bisogno di due: uno per i file del progetto sotto src/, e uno per i test.

Prima di tutto, scriviamo un po' di codice!

Il nostro progetto di test avrà un file 'main' e includerà una libreria di utilità chiamata 'code'. La libreria fornirà una semplice funzione che restituisce 1 (per ora). I file saranno organizzati in questo modo:

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

Iniziamo a scrivere i file 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

Adesso, facciamo i test, che si troveranno nella directory t/. La prima cosa da fare è impostare un test runner che eseguirà i nostri file di test. Questa è anche la funzione 'main' che verrà eseguita una volta compilato tutto:

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

Ora possiamo scrivere il nostro primo modulo di test:

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

Successivamente, dobbiamo scrivere i makefile. Ne avremo bisogno di due: uno per i file del progetto sotto src/, e uno per i test.

Prima di tutto, scriviamo un po' di codice!

Il nostro progetto di test avrà un file 'main' e includerà una libreria di utilità chiamata 'code'. La libreria fornirà una semplice funzione che restituisce 1 (per ora). I file saranno organizzati in questo modo:

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

Iniziamo a scrivere i file 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

Adesso, facciamo i test, che si troveranno nella directory t/. La prima cosa da fare è impostare un test runner che eseguirà i nostri file di test. Questa è anche la funzione 'main' che verrà eseguita una volta compilato tutto:

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

Ora possiamo scrivere il nostro primo modulo di test:

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

Successivamente, dobbiamo scrivere i makefile. Ne avremo bisogno di due: uno per i file del progetto sotto src/, e uno per i test.

Project Makefile

Il file make del progetto sarà allo stesso livello delle directory ‘src’ e ‘t’ alla radice del progetto. Dovrebbe apparire così:

# 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)

Nota che questo utilizza ‘make -C’  per i target di test – il che significa che chiamerà nuovamente ‘make’  utilizzando il file make nella directory di test.

A questo punto possiamo compilare il codice ‘src’ con il file make e vedere che funziona:

[]$ 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!

Tests Makefile

Per i test, le cose sono un po' più complesse poiché dobbiamo caricare correttamente e integrare con la libreria CppUTest.

Il repository CppUTest fornisce un file chiamato "MakefileWorker.mk". Fornisce molte funzionalità che rendono semplice la costruzione con CppUTest. Il file si trova nella directory "build" nel repository git. Per questo tutorial assumeremo che sia stato copiato nella directory 't/'. Può essere usato come segue:

# non vogliamo usare percorsi relativi, quindi impostiamo queste variabili PROJECT_DIR=/path/to/project SRC_DIR=$(PROJECT_DIR)/src TEST_DIR=$(PROJECT_DIR)/t # specifica dove si trovano il codice sorgente e gli include INCLUDE_DIRS=$(SRC_DIR)/code SRC_DIRS=$(SRC_DIR)/code # specifica dove si trova il codice di test TEST_SRC_DIRS = $(TEST_DIR) # come chiamare il test binario TEST_TARGET=example # dove si trova la libreria cpputest CPPUTEST_HOME=/usr/local # eseguire MakefileWorker.mk con le variabili definite qui include MakefileWorker.mk

Nota che CPPUTEST_HOME deve essere impostato ovunque CppUTest sia stato installato. Se hai installato un pacchetto distro, questo sarà tipicamente sotto /usr/local su un sistema linux/mac. Se hai clonato il repo da solo, è ovunque si trovi quel checkout.

Queste opzioni sono tutte documentate in MakefileWorker.mk.

MakefileWorker.mk aggiunge anche alcuni obiettivi makefile, inclusi i seguenti:

  1. all – costruisce i test indicati dal makefile

  2. clean – rimuove tutti i file oggetto e gcov generati per i test

  3. realclean – rimuove qualsiasi file oggetto o gcov in tutto l'albero delle directory

  4. flags – elenca tutte le flag configurate usate per compilare i test

  5. debug – elenca tutti i file sorgente, oggetti, dipendenze e 'cose da pulire'

Copertura del Codice

Unit testing would not be complete without a coverage report. The go-to tool for this for projects using gcc is gcov, available as part of the standard suite of gcc utilities. Cpputest integrates easily with gcov, all you need to do is add this line to the makefile:

CPPUTEST_USE_GCOV=Y

Next, we need to make sure that the filterGcov.sh script from this repo is in ‘/scripts/filterGcov.sh’ relative to wherever you set ‘CPPUTEST_HOME’ to be. It also needs to have execute perms.

In the example Makefile, it would be deployed to ‘/usr/local/scripts/filterGcov.sh’. If you’re running CppUTest from a repo checkout, everything should work without modification.




With that in place, you can simply run ‘make gcov’ and the analysis will be generated for you. In our case we’ll need to ‘make -B’ to rebuild the object files with gcov enabled:

[]$ make -B gcov < compilation output > for d in /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 for f in ; 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 See gcov directory for details

This will output a number of files to a new ‘gcov’ directory. These are:

  1. code.cpp.gcov – the actual ‘gcov’ file for the code being tested

  2. gcov_error.txt – an error report (in our case, it should be empty)

  3. gcov_output.txt – the actual output of the gcov command that was run

  4. gcov_report.txt – a summary of the coverage for each file under test

  5. gcov_report.txt.html – an html version of gcov_report

Cpputest Memory Leak Detection

Cpputest consente di rilevare automaticamente la memoria dispersa ridefinendo la famiglia di funzioni standard "malloc/free" per utilizzare invece i suoi propri wrapper. Ciò consente di individuare rapidamente le perdite e segnalarle per ogni esecuzione del test. Questo è abilitato per impostazione predefinita in MakefileWorker.mk, quindi è già attivo con i passaggi delineati finora.

Per illustrare, facciamo perdere un po' di memoria in test_func() !

Tornando a code.c, aggiungiamo un malloc()  alla funzione, in questo modo:

int test_func() { malloc(1); return 1; }

Ora, dopo la ricompilazione, viene prodotto il seguente errore:

test.cpp:9: errore: Fallimento in TEST(AwesomeExamples, FirstExample) Trovata(e) perdita(e) di memoria. Numero di allocazioni (4) Dimensione della perdita: 1 Allocato a: ./code.c e linea: 6. Tipo: "malloc" Memoria: <0x7fc9e94056d0> Contenuto: 0000: 00 |.| Numero totale di perdite: 1

Questo mostra quale test ha causato la perdita, dove è avvenuta la perdita nel codice sorgente e cosa era presente nella memoria perduta. Molto utile!

Ci sono un paio di avvertenze su questa funzionalità:

  1. Cpputest utilizza macro precompilate per ridefinire dinamicamente tutte le chiamate alle funzioni di gestione della memoria standard. Ciò significa che funzionerà solo per le chiamate nel codice sorgente in prova, poiché è quello compilato con i sovrascrittori di CppUTest. Le perdite nelle librerie collegate non verranno rilevate.

  2. A volte la memoria allocata per l'intero ciclo di vita del processo non deve essere liberata. Questo può generare molti errori fastidiosi se stai testando un modulo con questo comportamento. Per disabilitare il rilevamento delle perdite, puoi fare questo:

CPPUTEST_USE_MEM_LEAK_DETECTION=N

Interessato a More?

Questo è solo la punta dell'iceberg quando si tratta di tutte le funzionalità contenute in questo strumento. Oltre alle basi discusse qui, ha anche un framework di simulazione, un layer di integrazione diretta C, e un framework di plugin, per citarne alcuni significativi. Il repository contiene anche un'intera directory di script di supporto che possono aiutare ad automatizzare alcune delle parti di routine del lavoro con il framework.

Spero che le informazioni qui ti aiutino a migliorare la qualità del codice C/C++ con questo ottimo strumento!

Connettiamoci con un esperto di Bird.
Scopri tutta la potenza del Bird in 30 minuti.

Inviando, accetti che Bird possa contattarti riguardo ai nostri prodotti e servizi.

Puoi annullare l'iscrizione in qualsiasi momento. Consulta la Informativa sulla Privacy di Bird per i dettagli sul trattamento dei dati.

Azienda

Newsletter

Rimani aggiornato con Bird attraverso aggiornamenti settimanali nella tua inbox.

Connettiamoci con un esperto di Bird.
Scopri tutta la potenza del Bird in 30 minuti.

Inviando, accetti che Bird possa contattarti riguardo ai nostri prodotti e servizi.

Puoi annullare l'iscrizione in qualsiasi momento. Consulta la Informativa sulla Privacy di Bird per i dettagli sul trattamento dei dati.

Azienda

Newsletter

Rimani aggiornato con Bird attraverso aggiornamenti settimanali nella tua inbox.

Connettiamoci con un esperto di Bird.
Scopri tutta la potenza del Bird in 30 minuti.

Inviando, accetti che Bird possa contattarti riguardo ai nostri prodotti e servizi.

Puoi annullare l'iscrizione in qualsiasi momento. Consulta la Informativa sulla Privacy di Bird per i dettagli sul trattamento dei dati.

R

Raggiungi

G

Grow

M

Manage

A

Automate

Azienda

Newsletter

Rimani aggiornato con Bird attraverso aggiornamenti settimanali nella tua inbox.