Aquí hay un ejemplo de cómo implementaría múltiples pruebas en un solo programa de prueba para una función dada que podría llamar a una función de biblioteca.
Supóngase que se desea probar el módulo siguiente:
#include <stdlib.h>
int my_div(int x, int y)
{
if (y==0) exit(2);
return x/y;
}
A continuación, crear el siguiente programa de prueba:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <setjmp.h>
// redefine assert to set a boolean flag
#ifdef assert
#undef assert
#endif
#define assert(x) (rslt = rslt && (x))
// the function to test
int my_div(int x, int y);
// main result return code used by redefined assert
static int rslt;
// variables controling stub functions
static int expected_code;
static int should_exit;
static jmp_buf jump_env;
// test suite main variables
static int done;
static int num_tests;
static int tests_passed;
// utility function
void TestStart(char *name)
{
num_tests++;
rslt = 1;
printf("-- Testing %s ... ",name);
}
// utility function
void TestEnd()
{
if (rslt) tests_passed++;
printf("%s\n", rslt ? "success" : "fail");
}
// stub function
void exit(int code)
{
if (!done)
{
assert(should_exit==1);
assert(expected_code==code);
longjmp(jump_env, 1);
}
else
{
_exit(code);
}
}
// test case
void test_normal()
{
int jmp_rval;
int r;
TestStart("test_normal");
should_exit = 0;
if (!(jmp_rval=setjmp(jump_env)))
{
r = my_div(12,3);
}
assert(jmp_rval==0);
assert(r==4);
TestEnd();
}
// test case
void test_div0()
{
int jmp_rval;
int r;
TestStart("test_div0");
should_exit = 1;
expected_code = 2;
if (!(jmp_rval=setjmp(jump_env)))
{
r = my_div(2,0);
}
assert(jmp_rval==1);
TestEnd();
}
int main()
{
num_tests = 0;
tests_passed = 0;
done = 0;
test_normal();
test_div0();
printf("Total tests passed: %d\n", tests_passed);
done = 1;
return !(tests_passed == num_tests);
}
Al redefinir assert
para actualizar una variable booleana, puede continuar en caso de una la aserción falla y ejecuta múltiples pruebas, haciendo un seguimiento de cuántas tuvieron éxito y cuántas fallaron.
Al inicio de cada prueba, establezca rslt
(las variables utilizadas por la macro assert
) en 1 y establezca las variables que controlan las funciones de código auxiliar.Si se llama a uno de sus stubs más de una vez, puede configurar matrices de variables de control para que los stubs puedan verificar diferentes condiciones en diferentes llamadas.
Dado que muchas funciones de la biblioteca son símbolos débiles, se pueden redefinir en su programa de prueba para que se llamen. Antes de llamar a la función para probar, puede establecer un número de variables de estado para controlar el comportamiento de la función de stub y verificar las condiciones en los parámetros de la función.
En los casos en que no pueda redefinir de esa manera, asigne un nombre diferente a la función de código auxiliar y redefina el símbolo en el código para probar. Por ejemplo, si desea unir fopen
pero encuentra que no es un símbolo débil, defina su stub como my_fopen
y compile el archivo para probar con -Dfopen=my_fopen
.
En este caso particular, la función a probar puede llamar al exit
. Esto es complicado, ya que exit
no puede regresar a la función que se está probando. Este es uno de los raros momentos en los que tiene sentido usar setjmp
y longjmp
. Utiliza setjmp
antes de ingresar a la función para probar, luego, en el exit
topado, llama al longjmp
para regresar directamente a su caso de prueba.
También tenga en cuenta que el exit
redefinido tiene una variable especial que verifica si realmente desea salir del programa y llama al _exit
para hacerlo. Si no lo hace, es posible que su programa de prueba no se cierre limpiamente.
Este conjunto de pruebas también cuenta el número de pruebas intentadas y fallidas y devuelve 0 si se pasaron todas las pruebas y 1 en caso contrario. De esta forma, make
puede verificar si fallan las pruebas y actuar en consecuencia.
El código de prueba de salida voluntad por encima de lo siguiente:
-- Testing test_normal ... success
-- Testing test_div0 ... success
Total tests passed: 2
Y el código de retorno será 0.
La mejor respuesta hasta el momento. Incluso se muestra cómo manejar la necesidad práctica de la Inyección de Dependencia en C, que es la técnica principal para aislar el alcance del código para lograr una verdadera prueba unitaria. – kirakun