2012-03-05 5 views
9

Sto scrivendo un compilatore Tiger in C# e ho intenzione di tradurre il codice Tiger in IL.Test di unità di scrittura nel mio compilatore (che genera IL)

Durante l'implementazione del controllo semantico di ogni nodo nel mio AST, ho creato un sacco di unit test per questo. Che è piuttosto semplice, perché il mio metodo CheckSemantic assomiglia a questo:

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

così, se voglio scrivere qualche unit test per la verifica semantica di qualche nodo, tutto quello che devo fare è costruire un AST, e la chiamata quel metodo. Allora posso fare qualcosa di simile:

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

o

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

ma sto iniziando la generazione del codice in questo momento, e non so che cosa potrebbe essere una buona pratica durante la scrittura di unit test per quella fase.

Sto utilizzando la classe ILGenerator. Ho pensato a quanto segue:

  • generare il codice del programma di esempio che voglio testare
  • Salva che eseguibile
  • Esegui il file, e memorizzare l'output in un file di
  • valere nei confronti quel file

ma mi chiedo se c'è un modo migliore di farlo?

risposta

14

Questo è esattamente ciò che facciamo nel team del compilatore C# per testare il nostro generatore IL.

Eseguiamo anche l'eseguibile generato tramite ILDASM e verifichiamo che l'IL sia prodotto come previsto, e lo eseguiamo attraverso PEVERIFY per garantire che stiamo generando codice verificabile. (Tranne ovviamente nei casi in cui stiamo deliberatamente la generazione di codice non verificabile.)

+0

È bello saperlo. Lo farò in quel modo allora. Ero solo un po 'preoccupato per le prestazioni di molti test in esecuzione e per la creazione, la lettura e l'eliminazione di file sul disco. –

+3

@OscarMederos: Se le prestazioni non sono abbastanza buone allora (1) fai il profiling per capire cosa è lento e correggilo se puoi, oppure (2) cambia la tua strategia di test in modo che alcuni test vengano eseguiti su ogni check-in, alcuni test correre durante la notte, e alcuni correre nel fine settimana. In questo modo si ottiene un buon equilibrio tra la scoperta rapida dei problemi e l'esecuzione di test approfonditi. –

0

si può pensare di test come fare due cose:

  1. ti permette di sapere se l'uscita è cambiato
  2. ti permette di sapere se l'uscita non è corretta

Determinare se qualcosa è cambiato è spesso considerevolmente più veloce della determinazione se qualcosa non è corretto, quindi può essere una buona strategia per eseguire test di rilevamento del cambiamento più frequentemente di quelli di rilevamento di errata test.

Nel tuo caso non è necessario eseguire gli eseguibili prodotti dal compilatore ogni volta se è possibile determinare rapidamente che l'eseguibile non è cambiato da quando è stata prodotta una copia nota (o presunta buona) dello stesso eseguibile.

In genere, è necessario eseguire una piccola quantità di manipolazione sull'output che si sta testando per eliminare le differenze previste (ad esempio, l'impostazione di date incorporate su un valore fisso), ma una volta eseguita tale operazione, rilevare il rilevamento i test sono facili da scrivere perché la validazione è fondamentalmente un confronto tra file: l'output è lo stesso dell'ultimo output noto? Sì: Pass, No: Fail.

Quindi il punto è che se si riscontrano problemi di prestazioni nell'esecuzione degli eseguibili prodotti dal compilatore e nel rilevamento delle modifiche nell'output di tali programmi, è possibile scegliere di eseguire test che rilevano le modifiche precedenti, confrontando gli stessi eseguibili.

1

ho creato un post-compiler in C# e ho usato questo metodo per testare il CIL mutato:

  1. Save the assembly in un temp file, che saranno cancellati dopo ho finito con esso.
  2. Utilizzare PEVerify a check the assembly; se c'è un problema lo copio in un posto conosciuto per un'ulteriore analisi degli errori.
  3. Verificare i contenuti dell'assieme. Nel mio caso sono per lo più loading the assembly dynamically in un AppDomain separato (quindi posso abbatterlo in seguito) e nell'esercizio di un class in there (quindi è come un gruppo di autocontrollo: here's a sample implementation).

Ho anche fornito alcune idee su come ridimensionare i test di integrazione in this answer.

Problemi correlati