5

Co jest uważane za najlepsze podejście do testu jednostkowego złożonej jednostki, takiej jak kompilator?Jednostka testująca kompilator

Przez lata napisałem kilka kompilatorów i interpretatorów i uważam, że ten rodzaj kodu jest dość trudny do przetestowania w dobry sposób.

Jeśli weźmiemy coś w rodzaju generowania abstrakcyjnego drzewa składniowego. jak byś przetestował to używając TDD?

Małe konstrukcje mogą być łatwe do przetestowania. np. coś w rodzaju:

string code = @"public class Foo {}"; 
AST ast = compiler.Parse(code); 

Ponieważ to nie wygeneruje dużej ilości węzłów ast.

Ale jeśli rzeczywiście chcesz przetestować, że kompilator może wygenerować AST na coś takiego sposobu:

[TestMethod] 
public void Can_parse_integer_instance_method_in_class() 
{ 
    string code = @"public class Foo { public int method(){ return 0;}}"; 
    AST ast = compiler.Parse(code); 

Co byś dochodzenia w sprawie? Ręczne definiowanie AST, które reprezentuje dany kod i stwierdzenie, że wygenerowany AST jest zgodny z ręcznie zdefiniowanym AST, wydaje się strasznie kłopotliwe, a nawet może być podatne na błędy.

Jaka jest najlepsza taktyka dla złożonych scenariuszy TDD?

+0

To tylko jeden z wielu przykładów, dlaczego testy jednostkowe są bezużyteczne i gorsze, a nacisk należy położyć na testy integracyjne. TDD jest dla CRUD, a nie dla poważnych rzeczy. W przypadku kompilatorów losowe generowanie kodu jest zdecydowanie najlepszą możliwą metodą. Np .: http://www.cs.utah.edu/~regehr/papers/pldi11-preprint.pdf –

+0

Możesz być także zainteresowany wyższym podejściem do bezpiecznej konstrukcji kompilatora: http://compcert.inria.fr/doc /index.html - formalna specyfikacja jest zdecydowanie lepszą gwarancją jakości niż jakiekolwiek możliwe testy. –

+0

@peer, o jakich "metodach" mówisz? Jeśli generowany jest parser (pomyśl "bison" i podobne), będziesz miał monolityczną gramatykę i nieczytelny stos wygenerowanego kodu. Nic do przetestowania poza gramatyką jako całością. Jeśli jest to ręczny parser rekursywny, to jeszcze trudniej jest przeprowadzić test jednostkowy (zobacz, powiedzmy, kod źródłowy Clang i spróbuj wymyślić, jak wyśmiewać ASTContext i strumień wejściowy dla każdego małego wpisu parsera). Testowanie jednostkowe jest naprawdę bezcelowe w odniesieniu do każdego dość skomplikowanego kodu. –

Odpowiedz

1

Przede wszystkim parsowanie jest zwykle banalną częścią projektu kompilatora. Z mojego doświadczenia wynika, że ​​nigdy nie zajmuje to więcej niż 10% czasu (chyba, że ​​mówimy o C++, ale nie zadawałbyś pytań tutaj, gdybyś je projektował), więc wolałbyś nie inwestować zbyt wiele czasu w testy parsera.

Mimo to TDD (lub jakkolwiek go nazwiesz) ma swój udział w tworzeniu środkowego końca, w którym często chcesz zweryfikować np. Optymalizacje, które właśnie dodałeś, spowodowały oczekiwaną transformację kodu. Z mojego doświadczenia wynika, że ​​takie testy są zwykle implementowane przez udostępnienie kompilatorowi specjalnie spreparowanych programów testowych i złożenie zestawu wyjściowego dla oczekiwanych wzorców (czy ta pętla rozwijana była czterokrotnie? Czy udało nam się uniknąć zapisywania pamięci to ta funkcja? Itp.). Proces agregacji nie jest tak dobry, jak analiza reprezentacji strukturalnej (S-exprs lub XML), ale jest tania i działa dobrze w większości przypadków. Wspieranie kompilatora jest bardzo trudne.

+0

Rzeczywiście używałem wyrażeń s w testach. na przykład. czyniąc moją AST zdolną do ToString() samą w wyrażenie s ... a następnie po prostu twierdzę, że wynik jest równy oczekiwanemu wyrażeniu s. Działa dobrze, ale czuje się hack'ish. lub? –

+0

Cóż, jest to hackish, ale zazwyczaj ludzie wybierają żyć z tym i nie inwestować zbyt wiele czasu w testy (nikt naprawdę nie lubi testów). Może pomóc tylko grep dla podwyrażenia i/lub pozwolić na pewną tolerancję (różne rejestry lub nazwy etykiet). – yugr

+0

BTW jest tutaj dobrym przykładem: [oświetlona infrastruktura LLVM] (http://llvm.org/docs/TestingGuide.html) – yugr

4

Po pierwsze, jeśli przetestujesz kompilator, nie uzyskasz wystarczającej liczby testów! Użytkownicy naprawdę polegają na wynikach generowanych przez kompilator, tak jakby były zawsze złotym standardem, więc naprawdę należy zdawać sobie sprawę z jakości. Jeśli możesz, sprawdź przy każdym teście, jaki możesz wymyślić!

Po drugie, użyj wszystkich dostępnych metod testowania, ale użyj ich w razie potrzeby. Rzeczywiście, możesz być w stanie udowodnić matematycznie, że pewna transformacja jest prawidłowa. Jeśli możesz to zrobić, powinieneś.

Jednak każdy kompilator, w którym widziałem niektóre elementy wewnętrzne, zawiera heurystykę i dużo zoptymalizowanego, ręcznie wykonanego kodu w jego wnętrzach; w ten sposób wspomagane metody dowodzenia zwykle nie mają już zastosowania. Tutaj jest testowanie i mam na myśli dużo!

trakcie zbierania testy, należy rozważyć różne przypadki:

  1. Pozytywna Standard-Zgodność: Twój nakładka powinna przyjąć pewne wzorce kod i kompilator musi produkować ich prawidłowo uruchomiony program.Testy w tej kategorii wymagają kompilatora lub generatora ze złotym wzorcem, który generuje poprawne wyniki programu testowego; lub obejmuje ręcznie pisane programy, które obejmują sprawdzanie wartości dostarczonych przez ludzkie rozumowanie.
  2. Testy negatywne: każdy kompilator musi odrzucić wadliwy kod, np. Błędy składni, niedopasowania typu i tak dalej. Musi generować określone rodzaje komunikatów o błędach i ostrzeżeniach. Nie znam żadnej metody automatycznego generowania takich testów. Więc te też muszą być napisane przez ludzi.
  3. Testy transformacji: jeśli pojawią się fantazyjne optymalizacje w kompilatorze (środkowy koniec), prawdopodobnie masz na myśli przykładowy kod demonstrujący optymalizację. Bądź świadomy transformacji przed i po takim module, mogą potrzebować specjalnych opcji do kompilatora lub kompilatora bare-bone z tylko tym modułem podłączonym. Przetestuj także rozsądny duży zestaw otaczających kombinacji modułów. Zwykle przeprowadzałem testy regresji na reprezentacji pośredniej przed i po określonej transformacji, definiując odniesienie za pomocą intensywnego wnioskowania z kolegami. Spróbuj napisać kod po obu stronach transformacji, tzn. Fragmenty kodu, które chcesz przekształcić i nieco inne, które nie mogą być.

To brzmi jak strasznie dużo pracy! Tak, ale jest pomoc: istnieje kilka komercyjnych zestawów testowych dla kompilatorów (C-) na świecie i ekspertów, którzy mogą ci pomóc w ich stosowaniu. Oto krótka lista tych znanych mi:

+1

To są szlachetne testy integracyjne wysokiego poziomu, a nie te dziwne "testy jednostkowe" tak bardzo lubiane przez hordy koderów internetowych. –

Powiązane problemy