Reach

Grow

Manage

Automate

Reach

Grow

Manage

Automate

Introducción a CppUTest

Pájaro

14 may 2017

Correo electrónico

1 min read

Introducción a CppUTest

Pájaro

14 may 2017

Correo electrónico

1 min read

Introducción a CppUTest

En SparkPost, dedicamos mucho tiempo y esfuerzo a probar nuestro código. Nuestra plataforma está escrita en C, y recientemente investigué la integración con un marco de pruebas unitarias llamado “CppUTest”, que proporciona pruebas al 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 en C que facilitó su uso con nuestro código en C, a pesar de que la mayor parte del marco está en C++. Este tutorial cubre cómo comenzar con CppUTest en tus propios proyectos.

En SparkPost, dedicamos mucho tiempo y esfuerzo a probar nuestro código. Nuestra plataforma está escrita en C, y recientemente investigué la integración con un marco de pruebas unitarias llamado “CppUTest”, que proporciona pruebas al estilo xUnit para C/C++. Este marco es sólido, rico en funciones y está en desarrollo activo, lo que lo convierte en una excelente opción. También proporciona una capa de integración en C que facilitó su uso con nuestro código en C de la plataforma, aunque la mayor parte del marco es C++. Este tutorial cubre cómo comenzar con CppUTest en tus propios proyectos.

Descargando CppUTest

La página del proyecto CppUTest está disponible aquí, y el repositorio está en github. También está incluido en los repositorios de gestión de paquetes para muchas distribuciones de linux, así como en homebrew en Mac OS. Los ejemplos que siguen se ejecutaron en Mac OS X, pero se derivan de código escrito para Red Hat, el sistema operativo en el que corre nuestra plataforma.

Los conceptos básicos están bien documentados en la página principal de CppUTest. Vamos a pasar rápidamente por eso y llegar a algunas de las características más interesantes.

Laying the Foundation

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

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

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

Comencemos escribiendo los archivos de 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

Ahora, hagamos las pruebas, que estarán en el directorio t/.  Lo primero que hay que hacer es configurar un ejecutor de pruebas que ejecute nuestros archivos de prueba. Esta también es la función ‘main’  que se ejecutará una vez que todo esté compilado:

// 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); }

A continuación, necesitamos escribir archivos make.  Necesitaremos dos: uno para los archivos del proyecto bajo src/, y uno para las pruebas.

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

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

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

Comencemos escribiendo los archivos de 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

Ahora, hagamos las pruebas, que estarán en el directorio t/.  Lo primero que hay que hacer es configurar un ejecutor de pruebas que ejecute nuestros archivos de prueba. Esta también es la función ‘main’  que se ejecutará una vez que todo esté compilado:

// 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); }

A continuación, necesitamos escribir archivos make.  Necesitaremos dos: uno para los archivos del proyecto bajo src/, y uno para las pruebas.

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

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

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

Comencemos escribiendo los archivos de 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

Ahora, hagamos las pruebas, que estarán en el directorio t/.  Lo primero que hay que hacer es configurar un ejecutor de pruebas que ejecute nuestros archivos de prueba. Esta también es la función ‘main’  que se ejecutará una vez que todo esté compilado:

// 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); }

A continuación, necesitamos escribir archivos make.  Necesitaremos dos: uno para los archivos del proyecto bajo src/, y uno para las pruebas.

Proyecto Makefile

El makefile del proyecto estará al mismo nivel que los directorios ‘src’ y ‘t’ en la raíz del proyecto. Debería verse así:

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

Note que esto utiliza ‘make -C’  para los objetivos de prueba, lo que significa que llamará a ‘make’  de nuevo usando el makefile en el directorio de prueba.

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

Tests Makefile

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

El repositorio de CppUTest proporciona un archivo llamado “MakefileWorker.mk”. Ofrece mucha funcionalidad que hace que la construcción con CppUTest sea simple. El archivo se encuentra en el directorio “build” en el repositorio git. Para este tutorial, asumiremos que ha sido copiado al directorio ‘t/’. Se puede usar de la siguiente manera:

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

Note que CPPUTEST_HOME debe configurarse donde CppUTest fue instalado. Si has instalado un paquete de distribución, típicamente estará bajo /usr/local en un sistema linux/mac. Si has descargado el repositorio por tu cuenta, está donde quiera que hayas hecho la descarga.

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

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

  1. all – construye las pruebas indicadas por el makefile

  2. clean – remueve todos los archivos de objeto y gcov generados para las pruebas

  3. realclean – remueve cualquier archivo de objeto o gcov en todo el árbol de directorios

  4. flags – lista todas las banderas configuradas usadas para compilar las pruebas

  5. debug – lista todos los archivos fuente, objetos, dependencias y ‘cosas a limpiar’

Code Coverage

Las pruebas unitarias no estarían completas sin un informe de cobertura. La herramienta indispensable para esto en proyectos que usan gcc es gcov, disponible como parte del conjunto estándar de utilidades gcc. Cpputest se integra fácilmente con gcov, todo lo que necesitas hacer es agregar esta línea al makefile:

CPPUTEST_USE_GCOV=Y

A continuación, necesitamos asegurarnos de que el script filterGcov.sh del este repositorio esté en ‘/scripts/filterGcov.sh’ relativo a donde sea que hayas configurado ‘CPPUTEST_HOME’. También necesita tener permisos de ejecución.

En el Makefile de ejemplo, se implementaría en ‘/usr/local/scripts/filterGcov.sh’. Si estás ejecutando CppUTest desde una comprobación de repositorio, todo debería funcionar sin modificaciones.




Con eso en su lugar, simplemente puedes ejecutar ‘make gcov’ y se generará el análisis para ti. En nuestro caso, necesitaremos ‘make -B’ para reconstruir los archivos objeto con gcov habilitado:

[]$ make -B gcov < salida de compilación > para d en /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 Ver directorio gcov para más detalles

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

  1. code.cpp.gcov – el archivo ‘gcov’ real para el 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

Cpputest Memory Leak Detection

Cpputest te permite detectar automáticamente la memoria filtrada redefiniendo la familia estándar de funciones "malloc/free" para usar sus propios envoltorios en su lugar. Esto le permite capturar rápidamente las fugas y reportarlas para cada ejecución de prueba. Esto está habilitado por defecto en MakefileWorker.mk, así que ya está activado con los pasos descritos hasta ahora.

Para ilustrar, ¡vamos a filtrar un poco de memoria en test_func() !

Volviendo a code.c, añadimos un malloc()  a la función, de la siguiente manera:

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 y línea: 6. Tipo: "malloc" Memoria: <0x7fc9e94056d0> Contenido: 0000: 00 |.| Número total de fugas: 1

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

Hay un par de advertencias con esta característica:

  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 solo funcionará para las llamadas en el código fuente bajo prueba, ya que eso es lo que está compilado con las sobrescrituras de Cpputest. No se detectarán filtraciones en las bibliotecas vinculadas.

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

CPPUTEST_USE_MEM_LEAK_DETECTION=N

¿Interesado en más?

Esto es solo la punta del iceberg cuando se trata de todas las características contenidas en esta herramienta. Además de lo básico discutido aquí, también tiene un marco de burla, una capa de integración directa con C y un marco de plugins, por nombrar algunos de los más importantes. El repositorio también contiene todo un directorio de scripts auxiliares que pueden ayudar a automatizar algunas de las partes rutinarias de trabajar con el marco.

¡Espero que la información aquí te ayude a mejorar la calidad de tu código C/C++ con esta gran herramienta!

Conectémosle con un experto de Bird.
Vea el poder completo del Bird en 30 minutos.

Al enviar, aceptas que Bird pueda contactarte sobre nuestros productos y servicios.

Puedes darte de baja en cualquier momento. Consulta el Aviso de Privacidad de Bird para obtener detalles sobre el procesamiento de datos.

Company

Newsletter

Mantente al día con Bird a través de actualizaciones semanales en tu buzón.

Conectémosle con un experto de Bird.
Vea el poder completo del Bird en 30 minutos.

Al enviar, aceptas que Bird pueda contactarte sobre nuestros productos y servicios.

Puedes darte de baja en cualquier momento. Consulta el Aviso de Privacidad de Bird para obtener detalles sobre el procesamiento de datos.

Company

Newsletter

Mantente al día con Bird a través de actualizaciones semanales en tu buzón.

Conectémosle con un experto de Bird.
Vea el poder completo del Bird en 30 minutos.

Al enviar, aceptas que Bird pueda contactarte sobre nuestros productos y servicios.

Puedes darte de baja en cualquier momento. Consulta el Aviso de Privacidad de Bird para obtener detalles sobre el procesamiento de datos.

R

Reach

G

Grow

M

Manage

A

Automate

Company

Newsletter

Mantente al día con Bird a través de actualizaciones semanales en tu buzón.