Pregunta

Esta es una pregunta difícil y abierta, lo sé, pero pensé en tirarla al suelo y ver si alguien tenía alguna sugerencia interesante.

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 exponerlo como servicios web.Cuando desarrollé este código lo hice usando TDD, pero descubrí que mis pruebas eran muy frágiles.Debido a que cada prueba esencialmente quería verificar que para un fragmento determinado de código de entrada (que resulta ser un encabezado de C++) obtendría un fragmento determinado 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 modificar el código.Eso y el hecho de que las pruebas unitarias en sí mismas son:complejo, y b:frágil.

Así que estoy tratando de pensar en enfoques alternativos para este problema, y ​​me parece que tal vez lo estoy abordando de manera equivocada.Quizás necesito centrarme más en el resultado, es decir:¿El código que genero realmente se ejecuta y hace lo que quiero, en lugar de tener el aspecto que quiero?

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

¿Fue útil?

Solución

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

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

Descubrí que es necesario dividir el generador de código en partes más pequeñas y realizar pruebas unitarias.Si me preguntas, la prueba unitaria de un generador de código completo se parece más a una prueba de integración que a una prueba unitaria.

Otros consejos

Recuerde que las "pruebas unitarias" son sólo un tipo de prueba.Debería poder realizar una prueba unitaria del interno piezas de su generador de código.Lo que realmente estás viendo aquí son pruebas a nivel de sistema (también conocidas como pruebas a nivel de sistema).pruebas de regresión).No es sólo semántica...hay diferentes mentalidades, enfoques, expectativas, etc.Ciertamente es más trabajo, pero probablemente necesites hacer el esfuerzo y configurar un conjunto de pruebas de regresión de un extremo a otro:archivos C++ fijos -> interfaces SWIG -> módulos de Python -> salida conocida.Realmente desea comparar la entrada conocida (código C++ fijo) con la salida esperada (lo que sale del programa Python final).Verificar los resultados del generador de código directamente sería como diferenciar archivos de objetos...

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...pasa tu tiempo allí.

Si está ejecutando *nux, podría considerar deshacerse del marco unittest en favor de un script bash o un archivo MAKE.en Windows, podría considerar crear una aplicación/función de shell que ejecute el generador y luego use el código (como otro proceso) y lo pruebe por unidad.

Una tercera opción sería generar el código y luego crear una aplicación a partir de él que no incluya nada más que una prueba unitaria.Nuevamente, necesitaría un script de shell o cualquier otra cosa para ejecutar esto 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 usando la interfaz generada en lugar de la de C++.

Solo quería señalar que aún se pueden realizar pruebas detalladas mientras se verifican los resultados:Puedes probar fragmentos de código individuales anidándolos dentro de algún código de configuración y verificación:

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

Siempre que tenga el código generado ensamblado a partir de fragmentos más pequeños y que los fragmentos no cambien con frecuencia, puede ejercer más condiciones y probar un poco mejor y, con suerte, evitar que todas sus pruebas se rompan cuando cambia los detalles específicos de un fragmento.

Las pruebas unitarias son simplemente probar una unidad específica.Entonces, si está escribiendo una especificación para la clase A, lo ideal es que la clase A no tenga las versiones concretas reales de las clases B y C.

Ok, luego 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 A anterior inyecta interfaces a los sistemas B y C, puede realizar una prueba unitaria solo del sistema A, sin que ningún otro sistema ejecute una funcionalidad real.Esta es una prueba unitaria.

A continuación se muestra una forma inteligente de abordar un sistema desde su creación hasta su finalización, con una especificación When diferente para cada comportamiento:

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

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

Mi recomendación sería determinar un conjunto de resultados de entrada y salida conocidos, como algunos casos más simples que ya tenga implementados, y prueba unitaria del código que se produce.Es muy posible que al cambiar el generador la cadena exacta que se produce sea ligeramente diferente...pero lo que realmente te importa es si se interpreta de la misma manera.Por lo tanto, si prueba los resultados como probaría ese código si fuera su característica, descubrirá si tiene éxito en la forma que desea.

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 en la forma que usted espera, podrá sentirse mejor de que el generador tendrá é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 le ayudará a asegurarse de que los cambios en su generador no rompan otras formas de código.Cuando encuentre un error que las pruebas de su unidad no detectaron, es posible que desee incluirlo para evitar fallas similares.

Creo que necesitas probar más lo que estás generando que cómo lo generas.

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 los errores de regresión en general es probar la aplicación web en sí, no probar el generador.

No me malinterpretes, todavía hay pruebas unitarias que revisan parte del código del generador, pero nuestro mayor beneficio han sido las pruebas de UI en la aplicación generada.

Dado que lo estamos generando, también generamos una buena abstracción en JS que podemos usar para probar la aplicación mediante programación.Seguimos algunas ideas descritas aquí: http://code.tutsplus.com/articles/maintainable-automated-ui-tests--net-35089

Lo mejor es que realmente prueba su sistema de un extremo a otro, desde la generación del código hasta lo que realmente está generando.Una vez que una prueba falla, es fácil rastrearla hasta donde se rompió el generador.

Es muy dulce.

¡Buena suerte!

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top