Primeros pasos con CppUTest

Primeros pasos con CppUTest

Primeros pasos con CppUTest

May 14, 2017

Publicado por

Publicado por

Bird

Bird

-

Categoría:

Categoría:

Email

Email

Ready to see Bird
in action?

Ready to see Bird
in action?

Getting Started with CppUTest

En SparkPost, dedicamos mucho tiempo y esfuerzo a probar nuestro código. Nuestra plataforma está escrita en C, y recientemente he investigado la integración con un marco de pruebas unitarias llamado "CppUTest", que proporciona pruebas de estilo xUnit para C/C++. Este marco es robusto, rico en funciones y está en desarrollo activo, lo que lo convierte en una gran elección. También proporciona una capa de integración de C que hace que sea fácil de usar con nuestra plataforma de código C a pesar de que la mayor parte del framework es C++. Este tutorial cubre cómo empezar con CppUTest en tus propios proyectos.


Descarga de CppUTest

En CppUTest project page is available here, and the repository is on github. It’s also included in the package management repositories for many linux distros, as well as homebrew on Mac OS. En examples that follow were executed on Mac OS X, but they’re derived from code written for Red Hat, the OS our platform runs on.

The basics are well documented on CppUTest’s página de inicio. We’re going to breeze through that and get to some of the more interesting features.


Sentar las bases

Lo primero es lo primero, ¡vamos a escribir algo de código!

Nuestro proyecto de prueba tendrá un archivo 'main' e incluirá una librería de utilidades llamada 'code'. La biblioteca proporcionará una función simple que devuelve 1 (por ahora). Los archivos se dispondrán así:

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

Empecemos por escribir los archivos 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

Now, let’s do the tests, which will live in the t/ directory.  The first thing to do is to set up a test runner which will run our test files. This is also the ‘main’  function that will execute once this is all compiled:

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

Ahora podemos escribir nuestro primer módulo de prueba:

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

Next, we need to write makefiles.  We’ll need two: one for the project files under src/, and one for the tests.


Makefile del proyecto

El makefile del proyecto estará al mismo nivel que los directorios 'src' y 't' en la raíz del proyecto. Debería tener este aspecto:

# Makefile SRC_DIR=./src CODE_DIR=$(SRC_DIR)/code OUT=ejemplo 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)

Note that this uses ‘make -C’  for the test targets – meaning that it will call ‘make’  again using the makefile in the test directory.

En este punto podemos compilar el código 'src' con el makefile y ver que funciona:

[]$ 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 ejemplo []$ ./ejemplo ¡hola mundo!


Pruebas Makefile

Para las pruebas, las cosas son un poco más complicadas, ya que necesitamos cargar e integrar correctamente la biblioteca CppUTest.

El repositorio de CppUTest proporciona un archivo llamado "MakefileWorker.mk". Proporciona muchas funcionalidades que hacen que construir con CppUTest sea sencillo. El archivo se encuentra en el directorio "build" del repositorio git. Para este tutorial vamos a asumir que ha sido copiado al directorio 't/'. Se puede utilizar de la siguiente manera:

# no queremos usar rutas relativas, así que establecemos estas variables PROJECT_DIR=ruta/al/proyecto SRC_DIRS=$(PROJECT_DIR)/src TEST_DIR=$(PROJECT_DIR)/t # especificar dónde se encuentran el código fuente y los includes INCLUDE_DIRS=$(SRC_DIRS)/code SRC_DIRS=$(SRC_DIR)/code # TEST_SRC_DIRS = $(TEST_DIR) # cómo llamar al binario de prueba TEST_TARGET=ejemplo # dónde se encuentra la librería cpputest CPPUTEST_HOME=/usr/local # ejecuta MakefileWorker.mk con las variables definidas aquí include MakefileWorker.mk

Ten en cuenta que CPPUTEST_HOME debe estar donde se instaló CppUTest. Si has instalado un paquete de una distro, normalmente estará en /usr/local en un sistema linux/mac. Si usted ha comprobado el repositorio por su cuenta, es donde sea que la comprobación es.

Todas estas opciones están documentadas en MakefileWorker.mk.

MakefileWorker.mk también añade algunos objetivos makefile, incluyendo los siguientes:

  1. all - construye las pruebas indicadas por el makefile

  2. clean - elimina todos los archivos de objetos y gcov generados para las pruebas

  3. realclean - elimina cualquier objeto o archivo gcov en todo el árbol de directorios

  4. flags - enumera todas las banderas configuradas utilizadas para compilar las pruebas

  5. debug - lista todos los archivos fuente, objetos, dependencias y "cosas que limpiar".


Cobertura del código

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 a la makefile:

CPPUTEST_USE_GCOV=Y

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

En el Makefile de ejemplo, se desplegaría en '/usr/local/scripts/filterGcov.sh'. Si está ejecutando CppUTest desde un repo checkout, todo debería funcionar sin modificaciones.


Con eso en su lugar, puede simplemente ejecutar 'make gcov' y el análisis se generará para usted. En nuestro caso necesitaremos 'make -B' para reconstruir los archivos objeto con gcov activado:

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

Esto generará una serie de archivos en un nuevo directorio 'gcov'. Estos son:

  1. code.cpp.gcov - el archivo 'gcov' del código que se está probando

  2. gcov_error.txt - un informe de errores (en nuestro caso, debería estar vacío)

  3. gcov_output.txt - la salida real del comando gcov que se ejecutó

  4. gcov_report.txt - un resumen de la cobertura para cada archivo bajo prueba

  5. gcov_report.txt.html - una versión html de gcov_report


Detección de fugas de memoria Cpputest

Cpputest le permite detectar automáticamente fugas de memoria redefiniendo la familia de funciones estándar "malloc/free" para utilizar en su lugar sus propias envolturas. Esto le permite detectar rápidamente las fugas e informar de ellas en cada ejecución de la prueba. Esto está habilitado por defecto en MakefileWorker.mk, por lo que ya está activado con los pasos descritos hasta ahora.

To illustrate, let’s leak some memory in test_func() !

Going back to code.c, we add a malloc()  a la function, like so:

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

Ahora, después de recompilar, se produce el siguiente error:

test.cpp:9: error: Failure in TEST(AwesomeExamples, FirstExample) Memory leak(s) found. Alloc num (4) Leak size: 1 Allocated at: ./code.c and line: 6. Type: "malloc" Memory: <0x7fc9e94056d0> Content: 0000: 00 |.| Total number of leaks: 1

Esto muestra qué prueba causó la fuga, dónde se produjo la fuga en el código fuente y qué había en la memoria filtrada. Muy útil.

Esta función tiene un par de inconvenientes:

  1. Cpputest utiliza macros de preprocesador para redefinir dinámicamente todas las llamadas a las funciones estándar de gestión de memoria. Esto significa que sólo funcionará para las llamadas en el código fuente bajo prueba, ya que eso es lo que se compila con los overrides de CppUTest. Las fugas en las bibliotecas enlazadas no serán detectadas.

  2. A veces la memoria que se asigna para toda la vida del proceso no está destinada a ser liberada. Esto puede provocar muchos errores de spam si estás probando un módulo con este comportamiento. Para desactivar la detección de fugas, puedes hacer esto:

CPPUTEST_USE_MEM_LEAK_DETECTION=N


¿Le interesa saber más?

Esto es sólo la punta del iceberg cuando se trata de todas las características contenidas en esta herramienta. Además de lo básico que se discute aquí, también tiene un framework de mocking, una capa de integración directa con C, y un framework de plugins, por nombrar algunos significativos. El repositorio también contiene un directorio completo de scripts de ayuda que pueden ayudar a automatizar algunas de las partes rutinarias del trabajo con el framework.

Espero que esta información te ayude a mejorar la calidad de tu código C/C++ con esta magnífica herramienta.

Your new standard in Marketing, Pay & Sales. It's Bird

The right message -> to the right person -> en el right time.

By clicking "See Bird" you agree to Bird's Confidencialidad.

Your new standard in Marketing, Pay & Sales. It's Bird

The right message -> to the right person -> en el right time.

By clicking "See Bird" you agree to Bird's Confidencialidad.