2012-03-05 8 views
9

Estoy escribiendo un compilador Tiger en C# y voy a traducir el código Tiger en IL.Pruebas de unidad de escritura en mi compilador (que genera IL)

Al implementar la comprobación semántica de cada nodo en mi AST, creé muchas pruebas unitarias para esto. Eso es bastante simple, ya que mi método CheckSemantic se ve así:

public override void CheckSemantics(Scope scope, IList<Error> errors) { 
... 
} 

así, si quiero escribir algo de unidad de prueba para la comprobación semántica de algún nodo, todo lo que tengo que hacer es construir un AST, y la llamada ese método. Entonces puedo hacer algo como:

Assert.That(errors.Count == 0); 

o

Assert.That(errors.Count == 1); 
Assert.That(errors[0] is UnexpectedTypeError); 
Assert.That(scope.ExistsType("some_declared_type")); 

pero estoy empezando la generación de código en este momento, y no sé lo que podría ser una buena práctica al escribir pruebas unitarias para esa fase.

Estoy usando la clase ILGenerator. He pensado en lo siguiente:

  • generar el código del programa de ejemplo quiero probar
  • Guardar ese ejecutable
  • ejecutar ese archivo y almacenar el resultado en un archivo
  • oponer ese archivo

pero me pregunto si hay una mejor manera de hacerlo?

Respuesta

14

Eso es exactamente lo que hacemos en el equipo del compilador de C# para probar nuestro generador de IL.

También ejecutamos el ejecutable generado a través de ILDASM y verificamos que el IL se produce como se espera y lo ejecutamos a través de PEVERIFY para garantizar que estamos generando código verificable. (Excepto, por supuesto, en aquellos casos en los que estamos generando deliberadamente código no comprobable.)

+0

Es bueno saber eso. Lo haré de esa manera entonces. Estaba un poco preocupado por el rendimiento de muchas pruebas en ejecución y por la creación, lectura y eliminación de archivos en el disco. –

+3

@OscarMederos: si el rendimiento no es lo suficientemente bueno, entonces (1) haga un perfil para determinar qué es lento y solucione si puede, o (2) cambie su estrategia de prueba para que algunas pruebas se ejecuten en cada registro, algunas pruebas corre durante la noche, y algunos pasan el fin de semana. De esta forma obtendrá un buen equilibrio entre descubrir problemas rápidamente y seguir realizando pruebas exhaustivas. –

0

Se puede pensar en las pruebas de como hacer dos cosas:

  1. que le permite saber si la salida ha cambiado
  2. que le permite saber si la salida es incorrecta

Determinar si algo ha cambiado es a menudo considerablemente más rápido que determinar si algo es incorrecto, por lo que puede ser una buena estrategia ejecutar pruebas de detección de cambios con mayor frecuencia que la detección de errores pruebas.

En su caso, no necesita ejecutar los ejecutables producidos por el compilador siempre que pueda determinar rápidamente que el ejecutable no ha cambiado desde que se produjo una copia conocida buena (o supuestamente buena) del mismo ejecutable.

Normalmente necesita realizar una pequeña cantidad de manipulación en el resultado que está probando para eliminar las diferencias que se esperan (por ejemplo, establecer fechas insertadas en un valor fijo), pero una vez que lo haya hecho, detección de cambios las pruebas son fáciles de escribir porque la validación es básicamente una comparación de archivos: ¿el resultado es el mismo que el último producto bueno conocido? Sí: Pase, No: Falló.

El punto es que si observa problemas de rendimiento al ejecutar los ejecutables producidos por su compilador y detectar cambios en el resultado de esos programas, puede optar por ejecutar pruebas que detecten cambios una etapa anterior comparando los ejecutables.

1

He creado un post-compiler in C# y que utiliza este enfoque para probar la CIL mutado:

  1. Save the assembly en un temp file, que se eliminará después de que he terminado con ella.
  2. Utilice PEVerify para check the assembly; si hay un problema, lo copio en un lugar conocido para un análisis de error adicional.
  3. Pruebe los contenidos del conjunto. En mi caso soy mayormente loading the assembly dynamically en un Dominio de aplicación separado (para poder desmontarlo más adelante) y ejerciendo un class in there (por lo que es como un ensamble de autoverificación: here's a sample implementation).

También he dado algunas ideas sobre cómo escalar las pruebas de integración en this answer.

Cuestiones relacionadas