2008-08-14 14 views
22

Esta es una pregunta difícil y abierta que sé, pero pensé en tirarla al piso y ver si alguien tenía alguna sugerencia interesante.¿Cómo debo probar la unidad de un generador de código?

He desarrollado un generador de código que lleva nuestra interfaz python a nuestro código C++ (generado a través de SWIG) y genera el código necesario para exponer esto como WebServices. Cuando desarrollé este código, lo hice usando TDD, pero mis pruebas me parecieron frágiles como el infierno. Como cada prueba esencialmente quería verificar que para un bit dado de código de entrada (que resulta ser un encabezado C++) obtuve un bit dado de código de salida. Escribí un pequeño motor que lee definiciones de prueba de archivos de entrada XML y genera pruebas casos de estas expectativas.

El problema es que me da miedo entrar para modificar el código. Eso y el hecho de que la unidad se prueba a sí misma son a: compleja, y b: frágil.

Así que estoy tratando de pensar en enfoques alternativos a este problema, y ​​me parece que quizás estoy abordando el camino equivocado. Tal vez deba enfocarme más en el resultado, IE: ¿el código que genero realmente se ejecuta y hago lo que quiero, en lugar de que el código se vea como yo quiero?

¿Alguien ha tenido alguna experiencia de algo similar a esto que les gustaría compartir?

+0

En realidad, estoy enfrentando el mismo problema, y ​​ninguna de las respuestas a continuación es realmente satisfactoria. De acuerdo, puedes probar las piezas de un generador de código. El problema es cómo saber que el código generado es correcto, es decir, que no hay regresiones ni nada de eso, y por lo tanto, ¿cómo se escriben las pruebas automatizadas para el código generado (ya sea que se las llame unidades o pruebas de integración)? –

+1

@James: no hay una respuesta fácil ... Acabo de volver a leer esta pregunta y las respuestas y todos los problemas que tuve en ese momento vienen de regreso. Puedo darle otra oportunidad en las próximas semanas porque estoy terminando con varias regresiones de vez en cuando y cada vez es más crítico detectarlas. – jkp

+0

Es una gran comparación masiva de cuerdas. Podría ser más fácil usar un AST – Nikos

Respuesta

12

Comencé a escribir un resumen de mi experiencia con mi propio generador de código, luego volví y volví a leer su pregunta y descubrí que ya había tratado los mismos problemas, concéntrese en los resultados de ejecución en lugar del diseño del código /Mira.

El problema es que esto es difícil de probar, el código generado puede no ser adecuado para ejecutarse en el entorno del sistema de prueba de la unidad, y ¿cómo se codifican los resultados esperados?

He descubierto que necesita descomponer el generador de código en piezas más pequeñas y probarlas en unidades. La prueba unitaria de un generador de código completo se parece más a las pruebas de integración que a las pruebas unitarias si me preguntas.

+0

Sí, es como comparar una cadena masiva con otra, teniendo que lidiar con cosas como: El valor esperado es igual a ** "{\" opciones \ ": ** pero recibió ** {" options ": ** – Nikos

0

Sí, los resultados son lo único que importa. La verdadera tarea es escribir un marco que permita que el código generado se ejecute de forma independiente ... pase su tiempo allí.

0

Si se está ejecutando en * nux, podría considerar eliminar el marco de prueba de unit a favor de un script bash o makefile. en Windows podría considerar construir una aplicación/función de shell que ejecute el generador y luego use el código (como otro proceso) y pruebe eso.

Una tercera opción sería generar el código y luego crear una aplicación que incluya nada más que una prueba unitaria. Nuevamente, necesitaría un script de shell o no lo haga para cada entrada. En cuanto a cómo codificar el comportamiento esperado, se me ocurre que podría hacerse de la misma manera que lo haría con el código C++ simplemente utilizando la interfaz generada en lugar de la de C++.

4

Recuerde que las "pruebas unitarias" son solo un tipo de prueba. Debería poder probar la unidad interna piezas de su generador de código. Lo que realmente está viendo aquí es la prueba de nivel del sistema (por ejemplo, la prueba de regresión). No se trata solo de semántica ... hay diferentes mentalidades, enfoques, expectativas, etc. Ciertamente, es más trabajo, pero es probable que tenga que lidiar con el problema y establecer un conjunto de pruebas de regresión de extremo a extremo: archivos C++ corregidos -> SWIG interfaces -> módulos de python -> salida conocida. Realmente desea comprobar la entrada conocida (código C++ fijo) contra el resultado esperado (lo que sale del programa final de Python). Verificar los resultados del generador de código directamente sería como diferir los archivos de objetos ...

0

Sólo quería señalar que todavía se puede lograr pruebas de grano fino, mientras que la verificación de los resultados: se puede probar trozos de código individuales por los nidos de ellos dentro de algún tipo de configuración y verificación de código:

int x = 0; 
GENERATED_CODE 
assert(x == 100); 

Siempre que tenga su código generado ensamblado a partir de fragmentos más pequeños, y los fragmentos no cambian con frecuencia, puede ejercitar más condiciones y probar un poco mejor y, con suerte, evitar que se rompan todas las pruebas cuando cambia detalles de un fragmento.

0

Las pruebas de unidades son solo para probar una unidad específica. Entonces, si está escribiendo una especificación para la clase A, es ideal si la clase A no tiene las versiones reales concretas de la clase B y C.

Bien Noté que la etiqueta para esta pregunta incluye C++/Python, pero los principios son los mismos:

public class A : InterfaceA 
    { 
     InterfaceB b; 

     InterfaceC c; 

     public A(InterfaceB b, InterfaceC c) { 
      this._b = b; 
      this._c = c; } 

     public string SomeOperation(string input) 
     { 
      return this._b.SomeOtherOperation(input) 
       + this._c.EvenAnotherOperation(input); 
     } 
    } 

Debido a que el sistema anterior Un inyecta interfaces para sistemas B y C, puede unidad de prueba solo sistema de a, sin tener la funcionalidad real de ser ejecutado por cualquier otro sistema. Esta es una prueba unitaria.

Aquí es una manera inteligente para acercarse a un sistema desde la creación hasta la finalización, con un diferente cuando especificación de cada pieza de la conducta:

public class When_system_A_has_some_operation_called_with_valid_input : SystemASpecification 
{ 
    private string _actualString; 

    private string _expectedString; 

    private string _input; 

    private string _returnB; 

    private string _returnC; 

    [It] 
    public void Should_return_the_expected_string() 
    { 
     _actualString.Should().Be.EqualTo(this._expectedString); 
    } 

    public override void GivenThat() 
    { 
     var randomGenerator = new RandomGenerator(); 
     this._input = randomGenerator.Generate<string>(); 
     this._returnB = randomGenerator.Generate<string>(); 
     this._returnC = randomGenerator.Generate<string>(); 

     Dep<InterfaceB>().Stub(b => b.SomeOtherOperation(_input)) 
         .Return(this._returnB); 
     Dep<InterfaceC>().Stub(c => c.EvenAnotherOperation(_input)) 
         .Return(this._returnC); 

     this._expectedString = this._returnB + this._returnC; 
    } 

    public override void WhenIRun() 
    { 
     this._actualString = Sut.SomeOperation(this._input); 
    } 
} 

Así que en conclusión, una única unidad/especificación puede tener múltiples comportamientos, y la especificación crece a medida que desarrolla la unidad/sistema; y si su sistema bajo prueba depende de otros sistemas concretos dentro de él, tenga cuidado.

0

Mi recomendación sería averiguar un conjunto de resultados conocidos de entrada-salida, como algunos casos más simples que ya tiene en su lugar, y la unidad probar el código que se produce. Es muy posible que al cambiar el generador, la secuencia exacta que se produce pueda ser ligeramente diferente ... pero lo que realmente importa es si se interpreta de la misma manera. Por lo tanto, si prueba los resultados ya que probaría ese código si fuera su función, descubrirá si tiene éxito de la manera que desee.

Básicamente, lo que realmente desea saber es si su generador producirá lo que espera sin probar físicamente todas las combinaciones posibles (también: imposible). Al asegurarse de que su generador sea consistente de la manera que espera, puede sentirse mejor de que el generador tenga éxito en situaciones cada vez más complejas.

De esta manera, también puede crear un conjunto de pruebas de regresión (pruebas unitarias que deben seguir funcionando correctamente). Esto lo ayudará a asegurarse de que los cambios en su generador no violen otras formas de código. Cuando encuentre un error que las pruebas de su unidad no captaron, puede incluirlo para evitar roturas similares.

0

Me parece que necesita probar qué está generando más que cómo lo genera.

En mi caso, el programa genera muchos tipos de código (C#, HTML, SCSS, JS, etc.) que se compilan en una aplicación web. La mejor manera que he encontrado para reducir errores de regresión en general es probar la aplicación web en sí, no probando el generador.

No me malinterprete, todavía hay pruebas de unidades que controlan parte del código del generador, pero nuestra mayor ganancia ha sido las pruebas de UI en la propia aplicación generada.

Como lo estamos generando, también generamos una buena abstracción en JS que podemos usar para probar la aplicación de forma programática. Seguimos algunas ideas esbozadas aquí: http://code.tutsplus.com/articles/maintainable-automated-ui-tests--net-35089

La gran parte es que realmente prueba su sistema de extremo a extremo, desde la generación del código hasta lo que realmente está generando. Una vez que falla una prueba, es fácil seguirla hasta donde se rompió el generador.

Es bastante dulce.

¡Buena suerte!

Cuestiones relacionadas