Buenos ejemplos de pruebas unitarias para desarrolladores C integrados [cerrado]

20

La próxima semana hablaré con mi departamento sobre las pruebas unitarias y el desarrollo basado en pruebas. Como parte de esto, voy a mostrar algunos ejemplos del mundo real de un código que he escrito recientemente, pero también me gustaría mostrar algunos ejemplos muy simples que escribiré en la charla.

He estado buscando en la web buenos ejemplos, pero he estado luchando por encontrar alguno que sea particularmente aplicable a nuestra área de desarrollo. Casi todo el software que escribimos son sistemas de control profundamente integrados que se ejecutan en pequeños microcontroladores. Hay una gran cantidad de código C que es fácilmente aplicable a las pruebas unitarias (hablaré sobre las pruebas unitarias en la PC en lugar de en el objetivo en sí) siempre y cuando se mantenga alejado de la capa 'inferior': lo que habla directamente a los periféricos del microcontrolador. Sin embargo, la mayoría de los ejemplos que he encontrado tienden a basarse en el procesamiento de cadenas (por ejemplo, el excelente ejemplo de los números romanos de inmersión en Python) y dado que casi nunca usamos cadenas, esto no es realmente adecuado (sobre las únicas funciones de biblioteca que nuestro código generalmente usa son memcpy, memcmpy memset,strcat o expresiones regulares no es del todo correcto).

Entonces, a la pregunta: ¿alguien puede ofrecer algunos buenos ejemplos de funciones que pueda usar para demostrar las pruebas unitarias en una sesión en vivo? Una buena respuesta en mi opinión (sujeta a cambios) probablemente sería:

  • Una función que es lo suficientemente simple que cualquiera (incluso aquellos que solo escriben código ocasionalmente) puede entender;
  • Una función que no parece inútil (es decir, calcular la paridad o CRC es probablemente mejor que una función que multiplica dos números y agrega una constante aleatoria);
  • Una función que es lo suficientemente corta como para escribir frente a una sala de personas (puedo aprovechar los muchos portapapeles de Vim para reducir errores ...);
  • Una función que toma números, matrices, punteros o estructuras como parámetros y devuelve algo similar, en lugar de manejar cadenas;
  • Una función que tiene un error simple (p. Ej., En >lugar de >=) que es fácil de implementar y que aún funcionaría en la mayoría de los casos, pero que se rompería con algún caso particular: fácil de identificar y corregir con una prueba unitaria.

¿Alguna idea?

Aunque probablemente no sea relevante, las pruebas en sí mismas probablemente se escribirán en C ++ usando el Marco de prueba de Google: todos nuestros encabezados ya tienen el #ifdef __cplusplus extern "C" {envoltorio alrededor de ellos; Esto ha funcionado bien con las pruebas que he hecho hasta ahora.

DrAl
fuente
Tomando el "problema" aquí como presentar una presentación para vender TDD a la gerencia, me parece que se ajusta razonablemente al formato deseado. El OP parece estar solicitando soluciones existentes para este problema.
Technophile

Respuestas:

15

Aquí hay una función simple que se supone que genera una suma de comprobación sobre bytes len .

int checksum(void *p, int len)
{
    int accum = 0;
    unsigned char* pp = (unsigned char*)p;
    int i;
    for (i = 0; i <= len; i++)
    {
        accum += *pp++;
    }
    return accum;
}

Tiene un error de poste de cerca: en la declaración for, la prueba debería ser i < len.

Lo divertido es que si lo aplicas a una cadena de texto como esta ...

char *myString = "foo";
int testval = checksum(myString, strlen(myString));

obtendrá la "respuesta correcta"! Esto se debe a que el byte adicional que se sumaba a la suma de verificación era el terminador de cadena cero. Por lo tanto, puede terminar colocando esta función de suma de verificación en el código, y tal vez incluso enviarla, y nunca notar un problema, es decir, hasta que comience a aplicarla a algo que no sea cadenas de texto.

Aquí hay una prueba unitaria simple que marcará este error (la mayoría de las veces ... :-)

void main()
{
    // Seed the random number generator
    srand(time(NULL));

    // Fill an array with junk bytes
    char buf[1024];
    int i;
    for (i = 0; i < 1024; i++)
    {
        buf[i] = (char)rand();
    }

    // Put the numbers 0-9 in the first ten bytes
    for (i = 0; i <= 9; i++)
    {
        buf[i] = i;
    }

    // Now, the unit test. The sum of 0 to 9 should
    // be 45. But if buf[10] isn't 0 - which it won't be,
    // 255/256 of the time - this will fail.
    int testval = checksum(buf, 10);
    if (testval == 45)
    {
        printf("Passed!\n");
    }
    else
    {
        printf("Failed! Expected 45, got %d\n", testval);
    }
}
Bob Murphy
fuente
¡Muy bien! Este es el tipo de respuesta que esperaba: gracias.
DrAl
Cuando crea el búfer ya tiene basura en ese trozo de memoria, ¿es realmente necesario inicializarlo con números aleatorios?
Snake Sanders
@SnakeSanders Yo diría que sí, porque quieres que las pruebas unitarias sean lo más deterministas posible. Si el compilador que usa pone un 0 en su máquina de desarrollo y un 10 en su máquina de prueba, tendrá un tiempo terrible para encontrar el error. Creo que hacer que dependa del tiempo en lugar de una semilla fija es una mala idea, por la misma razón.
Andrew dice Reinstate Monica
Confiar en comportamientos no deterministas en una prueba unitaria es una mala idea. Una prueba escamosa le dará dolores de cabeza tarde o temprano ...
sigy
2

¿Qué pasa con la implementación de una función de clasificación como la clasificación de burbujas ? Una vez que tenga la función de clasificación funcionando, puede continuar con la búsqueda binaria, que es igual de buena para introducir pruebas unitarias y TDD.

La clasificación y la búsqueda dependen de las comparaciones, que es fácil equivocarse. También implica intercambiar punteros alrededor de los cuales debe hacerse con cuidado. Ambos son propensos a errores, así que no dude en equivocarse :)

Algunas ideas más:

  • Las pruebas unitarias ayudan mucho al refactorizar. Entonces, una vez que su ordenación de burbujas funciona, puede cambiarla a una ordenación más poderosa qsort, y las pruebas aún deberían pasar, demostrando que su nueva función de ordenación también funciona.
  • La clasificación es fácil de probar, el resultado se clasifica o no, lo que lo convierte en un buen candidato.
  • Lo mismo para buscar; existe o no existe.
  • Escribir pruebas para ordenar abre discusiones como qué tipo de entrada usar para la prueba (elementos cero, entrada aleatoria, entradas duplicadas, matrices enormes, etc.).
Martin Wickman
fuente
¿Tiene alguna sugerencia específica para un error simple que muestre cómo las pruebas hacen la vida más fácil?
DrAl
@DrAl: Actualicé mi respuesta con eso.
Martin Wickman