c++ unit-testing boost assert boost-test

c++ - Pruebas para afirmar en el marco de Boost Test



unit-testing assert (6)

Utilizo el marco de prueba de Boost para probar mi código de C ++ y me pregunté si es posible probar si una función se confirmará. Sí, suena un poco extraño, pero tengan paciencia conmigo! Muchas de mis funciones comprueban los parámetros de entrada al ingresar, afirmando si son inválidos, y sería útil probarlo. Por ejemplo:

void MyFunction(int param) { assert(param > 0); // param cannot be less than 1 ... }

Me gustaría poder hacer algo como esto:

BOOST_CHECK_ASSERT(MyFunction(0), true); BOOST_CHECK_ASSERT(MyFunction(-1), true); BOOST_CHECK_ASSERT(MyFunction(1), false); ...

Puedes comprobar si se lanzaron excepciones usando Boost Test, así que me pregunté si también había algo de magia afirmativa ...


No lo creo. Siempre puede escribir su propia afirmación que arroje una excepción y luego use BOOST_CHECK_NOTHROW () para esa excepción.


Creo que esta pregunta y algunas respuestas confunden la detección de errores en tiempo de ejecución con la detección de errores. También confunden la intención y el mecanismo.

El error en tiempo de ejecución es algo que puede ocurrir en un programa 100% correcto. Necesita detección, y necesita informes y manejo adecuados, y debe ser probado. También se producen errores y, para comodidad del programador, es mejor detectarlos antes de tiempo utilizando comprobaciones de condiciones previas o comprobaciones invariables o declaraciones aleatorias. Pero esta es la herramienta del programador. El mensaje de error no tendrá sentido para el usuario ordinario, y no parece razonable probar el comportamiento de la función en los datos que el programa adecuadamente escrito nunca le pasará.

En cuanto a la intención y el mecanismo, debe notarse que la excepción no es nada mágica. Hace algún tiempo, Peter Dimov dijo en la lista de correo de Boost (aproximadamente) que "las excepciones no son más que un mecanismo de salto no local". Y esto es muy cierto. Si tiene una aplicación donde es posible continuar después de algún error interno, sin el riesgo de que algo se corrompa antes de la reparación, puede implementar una declaración personalizada que arroje una excepción de C ++. Pero no cambiaría la intención, y no haría que las pruebas para afirmar sean mucho más razonables.


Hay dos tipos de errores que me gusta verificar: invariantes y errores de tiempo de ejecución.

Las invariantes son cosas que siempre deberían ser ciertas, sin importar qué. Para aquellos, uso aseveraciones. Cosas como tu no deberían pasarme un puntero cero para el buffer de salida que me estás dando. Eso es un error en el código, claro y simple. En una compilación de depuración, se afirmará y me dará la oportunidad de corregirlo. En una compilación minorista, provocará una infracción de acceso y generará un minivolcado (Windows, al menos en mi código) o un coredump (Mac / Unix). No hay nada que pueda hacer que tenga sentido para lidiar con la desreferenciación de un puntero cero. En Windows catch (...) puede suprimir violaciones de acceso y dar al usuario una falsa sensación de confianza de que las cosas están bien cuando ya han salido horriblemente mal.

Esta es una razón por la que he llegado a creer que la catch (...) generalmente es un olor de código en C ++ y el único lugar razonable donde puedo pensar que está presente es en main (o WinMain ) justo antes de generar un Core Dump y cortésmente salir de la aplicación.

Los errores en tiempo de ejecución son "No puedo escribir este archivo debido a los permisos" o "No puedo escribir este archivo porque el disco está lleno". Para este tipo de errores, lanzar una excepción tiene sentido porque el usuario puede hacer algo al respecto, como cambiar el permiso en un directorio, eliminar algunos archivos o elegir una ubicación alternativa para guardar el archivo. Estos errores de tiempo de ejecución son corregibles por el usuario. La violación de un invariante no puede ser corregida por el usuario, solo por un programador. (A veces los dos son iguales, pero normalmente no lo son).

Las pruebas de la unidad deben obligar al código a arrojar las excepciones de error en tiempo de ejecución que su código podría generar. Es posible que también desee forzar excepciones de sus colaboradores para garantizar que su sistema bajo prueba sea excepcionalmente seguro.

Sin embargo, no creo que tenga sentido intentar obligar a su código a afirmar contra invariantes con pruebas unitarias.


Al tener el mismo problema, hojeé la documentación (y el código) y encontré una "solución".

El Boost UTF usa boost::execution_monitor (en <boost/test/execution_monitor.hpp> ). Esto está diseñado con el objetivo de capturar todo lo que podría suceder durante la ejecución de la prueba. Cuando se encuentra una afirmación, execution_monitor lo intercepta y lanza boost::execution_exception . Por lo tanto, al usar BOOST_REQUIRE_THROW puede afirmar el error de una afirmación.

asi que:

#include <boost/test/unit_test.hpp> #include <boost/test/execution_monitor.hpp> // for execution_exception BOOST_AUTO_TEST_CASE(case_1) { BOOST_REQUIRE_THROW(function_w_failing_assert(), boost::execution_exception); }

Debería hacer el truco. (Esto funciona para mi.)

Sin embargo (o renuncias):

  • Esto funciona para mi. Es decir, en Windows XP, MSVC 7.1, impulso 1.41.0. Puede ser inadecuado o roto en su configuración.

  • Puede que no sea la intención del autor de Boost Test. (aunque parece ser el propósito de execution_monitor).

  • Tratará cada forma de error fatal de la misma manera. Podría ser que algo que no sea tu afirmación está fallando. En este caso, podría perderse un error de corrupción de la memoria ega y / o perder una afirmación fallida fallida.

  • Podría romperse en futuras versiones de impulso.

  • Espero que falle si se ejecuta en la configuración de liberación, ya que la afirmación se desactivará y se ejecutará el código que se estableció para evitar. Resultando en un comportamiento muy indefinido.

  • Si, en Release config for msvc, ocurriera algún tipo de error de afirmación u otro error fatal, no se detectaría. (vea los documentos de execution_monitor).

  • Si usa assert o no depende de usted. Me gustan.

Ver:

Además, gracias a Gennadiy Rozental (autor de Boost Test), si lees esto, Great Work !!


En el trabajo me encontré con el mismo problema. Mi solución es usar una bandera de compilación. Cuando mi marcador GROKUS_TESTABLE está en mi GROKUS_ASSERT se convierte en una excepción y con Boost puede probar las rutas de acceso del código que generan excepciones. Cuando GROKUS_TESTABLE está desactivado, GROKUS_ASSERT se traduce a c ++ assert ().

#if GROKUS_TESTABLE #define GROKUS_ASSERT ... // exception #define GROKUS_CHECK_THROW BOOST_CHECK_THROW #else #define GROKUS_ASSERT ... // assert #define GROKUS_CHECK_THROW(statement, exception) {} // no-op #endif

Mi motivación original era ayudar a la depuración, es decir, assert () se puede depurar rápidamente y las excepciones a menudo son más difíciles de depurar en gdb. Mi indicador de compilación parece equilibrar la depuración y la capacidad de prueba bastante bien.

Espero que esto ayude


Lo siento, pero estás atacando tu problema de la manera incorrecta.

"assert" es el engendro del diablo (también conocido como "C") y es inútil con cualquier lenguaje que tenga las excepciones adecuadas. Es mejor volver a implementar una funcionalidad tipo afirmación con excepciones. De esta forma, realmente tiene la oportunidad de manejar los errores de la manera correcta (incluidos los procedimientos de limpieza adecuados) o activarlos a voluntad (para pruebas unitarias).

Además, si su código se ejecuta alguna vez en Windows, cuando falla una aserción, obtiene una ventana emergente inútil que le ofrece la posibilidad de depurar / abortar / reintentar. Agradable para pruebas unitarias automatizadas.

Así que hazte un favor y vuelve a codificar una función afirmar que arroja excepciones. Aquí hay uno: ¿cómo puedo assert () sin usar abort ()?

Ajústalo en una macro para que obtengas _ _FILE _ _ y _ _ LINE _ _ (útil para la depuración) y listo.