2008-08-14 9 views
22

Das ist eine schwierige und offene Frage, die ich kenne, aber ich dachte, ich würde sie zu Boden werfen und sehen, ob jemand interessante Vorschläge hat.Wie soll ich einen Code-Generator testen?

Ich habe einen Code-Generator entwickelt, der unsere Python-Schnittstelle zu unserem C++ - Code (generiert über SWIG) bringt und Code generiert, der benötigt wird, um dies als WebServices verfügbar zu machen. Als ich diesen Code entwickelt habe, habe ich es mit TDD gemacht, aber ich habe meine Tests als spröde empfunden. Weil jeder Test im Wesentlichen sicherstellen wollte, dass ich für ein gegebenes Bit an Eingabecode (was zufällig ein C++ - Header ist) ein bestimmtes Bit an ausgegebenem Code bekam, schrieb ich eine kleine Engine, die Testdefinitionen aus XML-Eingabedateien liest und Test generiert Fälle von diesen Erwartungen.

Das Problem ist, ich fürchte, gehen Sie hinein, um den Code überhaupt zu ändern. Das und die Tatsache, dass sich die Einheit selbst testet, sind: komplex und b: spröde.

Also versuche ich an alternative Ansätze für dieses Problem zu denken, und es fällt mir auf, dass ich es vielleicht in die falsche Richtung gehe. Vielleicht muss ich mich mehr auf das Ergebnis konzentrieren, IE: Läuft der Code, den ich generiere, tatsächlich und macht, was ich will, anstatt dass der Code so aussieht, wie ich es möchte?

Hat jemand irgendwelche Erfahrungen von etwas Ähnliches, das sie teilen möchten?

+0

Ich bin tatsächlich mit dem gleichen Problem konfrontiert, und keine der folgenden Antworten sind wirklich zufriedenstellend. Zugegeben, Sie können die Teile eines Codegenerators testen. Das Problem ist, woher wissen Sie, dass der generierte Code korrekt ist, d. H., Dass es keine Regressionen oder Ähnliches gibt, und wie schreiben Sie daher automatisierte Tests für generierten Code (ob Unit- oder Integrationstests genannt)? –

+1

@James: Es gibt keine einfache Antwort, gibt es ... Ich habe diese Frage nur noch einmal gelesen und die Antworten und alle Probleme, die ich damals hatte, kommen zurück. Ich werde das in den nächsten Wochen noch einmal versuchen, weil ich von Zeit zu Zeit verschiedene Regressionen bekomme und es immer wichtiger wird, diese zu erkennen. – jkp

+0

Es ist ein großer, massiver String-Vergleich. Könnte einfacher sein mit einem AST – Nikos

Antwort

12

Ich fing an, eine Zusammenfassung meiner Erfahrungen mit meinem eigenen Codegenerator zu schreiben, ging dann zurück und las deine Frage erneut und stellte fest, dass du die gleichen Probleme bereits selbst angesprochen hast, konzentriere dich auf die Ausführungsergebnisse anstatt auf das Code-Layout /aussehen.

Problem ist, das ist schwer zu testen, der generierte Code ist möglicherweise nicht geeignet, um tatsächlich in der Umgebung des Einheitentestsystems zu laufen, und wie kodieren Sie die erwarteten Ergebnisse?

Ich habe festgestellt, dass Sie den Code-Generator in kleinere Stücke zerlegen und diese testen müssen. Das Testen eines vollständigen Code-Generators ist eher ein Integrationstest als ein Komponententest, wenn Sie mich fragen.

+0

Ja ist es wie Vergleichen einer massiven Zeichenfolge mit einem anderen, mit Sachen wie zu tun haben: Erwarteter Wert zu gleich: ** "{\" Optionen \ ": ** aber erhalten ** {" Optionen ": ** – Nikos

0

Ja, Ergebnisse sind das EINZIGE, was zählt. Die eigentliche Aufgabe besteht darin, ein Framework zu schreiben, das es ermöglicht, dass Ihr generierter Code selbstständig läuft ... verbringen Sie Ihre Zeit dort.

0

Wenn Sie mit * nux arbeiten, sollten Sie das Unittest-Framework zugunsten eines Bash-Skripts oder Makefiles dumpen. Unter Windows könnten Sie eine Shell-App/-Funktion erstellen, die den Generator ausführt und dann den Code (als einen anderen Prozess) verwendet und diesen unittest.

Eine dritte Option wäre, den Code zu generieren und dann eine App daraus zu erstellen, die nur einen Komponententest enthält. Wiederum würden Sie ein Shell-Skript oder whatnot benötigen, um dies für jede Eingabe auszuführen. Wie das zu erwartende Verhalten zu kodieren ist, fällt mir auf, dass es auf die gleiche Weise wie für den C++ - Code getan werden könnte, indem nur die generierte Schnittstelle und nicht die C++ - Schnittstelle verwendet wird.

4

Erinnern Sie sich, dass "Unit Testing" nur eine Art von Tests ist. Sie sollten in der Lage sein, die internen Teile Ihres Codegenerators Einheit zu testen. Was Sie hier wirklich betrachten, ist das Testen auf Systemebene (a.k.a. Regressionstests). Es ist nicht nur Semantik ... es gibt verschiedene Denkweisen, Ansätze, Erwartungen, etc. Es ist sicherlich mehr Arbeit, aber Sie müssen wahrscheinlich in den sauren Apfel beißen und eine End-to-End-Regressionstest-Suite einrichten: feste C++ - Dateien -> SWIG Schnittstellen -> Python-Module -> bekannte Ausgabe. Sie möchten die bekannte Eingabe (fester C++ - Code) wirklich mit der erwarteten Ausgabe vergleichen (was aus dem endgültigen Python-Programm kommt). Die Ergebnisse des Code-Generators direkt zu prüfen, wäre vergleichbar mit Objektdateien ...

0

Ich wollte nur darauf hinweisen, dass Sie immer noch feinkörnige Tests erzielen können, während die Ergebnisse zu überprüfen: Sie sie innerhalb einiger Setup und Verifizierungscode durch Verschachtelung der einzelnen Teile des Codes testen:

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

Vorausgesetzt, Sie haben Ihr generierter Code wird aus kleineren Chunks zusammengesetzt, und die Chunks ändern sich nicht häufig. Sie können mehr Bedingungen ausüben und ein wenig besser testen und hoffentlich vermeiden, dass alle Tests kaputt gehen, wenn Sie die Details eines Chunks ändern.

0

Gerätetest ist nur das Testen einer bestimmten Einheit. Also, wenn Sie eine Spezifikation für Klasse A schreiben, ist es ideal, wenn Klasse A nicht die realen konkreten Versionen der Klasse B und C hat.

Ok Ich bemerkte später das Tag für diese Frage enthält C++/Python, aber die Prinzipien sind die gleichen:

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

Da die oben System A Schnittstellen zu den Systemen B und C injiziert, können Sie Unit-Test nur System A, ohne echte Funktionalität von keinem anderen System ausgeführt werden. Dies ist ein Komponententest.

Hier ist eine clevere Art und Weise für ein System, von der Erstellung bis zur Fertigstellung nähert, mit einem anderen Wenn Spezifikation für jedes Stück Verhalten:

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

Also abschließend, kann eine einzelne Einheit/Spezifikation mehr Verhaltensweisen haben, und die Spezifikation wächst mit der Entwicklung der Einheit/des Systems; und wenn Ihr System im Test von anderen konkreten Systemen abhängt, passen Sie auf.

0

Meine Empfehlung wäre, eine Reihe bekannter Eingabe-Ausgabe-Ergebnisse zu ermitteln, z. B. einige einfachere Fälle, die bereits vorhanden sind, und Unit-Test den Code, der produziert wird. Es ist durchaus möglich, dass beim Ändern des Generators die genaue Zeichenfolge, die erzeugt wird, etwas anders ist ... aber was Sie wirklich interessiert, ist, ob es auf die gleiche Weise interpretiert wird. Wenn Sie also die Ergebnisse testen, wie Sie diesen Code testen würden, wenn es Ihr Feature wäre, werden Sie herausfinden, ob es in der von Ihnen gewünschten Weise erfolgreich ist.

Was Sie wirklich wissen wollen, ist, ob Ihr Generator produziert, was Sie erwarten, ohne jede mögliche Kombination physikalisch zu testen (auch: unmöglich). Indem Sie sicherstellen, dass Ihr Generator in der von Ihnen erwarteten Weise konsistent ist, können Sie besser fühlen, dass der Generator in immer komplexeren Situationen erfolgreich sein wird.

Auf diese Weise können Sie auch eine Reihe von Regressionstests erstellen (Komponententests, die weiterhin ordnungsgemäß funktionieren müssen). Dadurch können Sie sicherstellen, dass Änderungen an Ihrem Generator andere Codeformen nicht beschädigen. Wenn Sie auf einen Fehler stoßen, den Ihre Komponententests nicht erfasst haben, möchten Sie ihn möglicherweise einbeziehen, um einen ähnlichen Fehler zu vermeiden.

0

Ich finde, dass Sie testen müssen, was Sie mehr generieren als wie Sie es generieren.

In meinem Fall erzeugt das Programm viele Arten von Code (C#, HTML, SCSS, JS, etc.), die in eine Webanwendung kompilieren. Der beste Weg, Regressionsfehler insgesamt zu reduzieren, besteht darin, die Webanwendung selbst zu testen und nicht den Generator zu testen.

Versteh mich nicht falsch, es gibt immer noch Komponententests, die einen Teil des Generatorcodes auschecken, aber unser größter Knall für unser Geld war UI-Tests an der generierten App selbst.

Da wir es generieren, generieren wir auch eine schöne Abstraktion in JS, die wir verwenden können, um die App programmatisch zu testen. Wir haben einige der folgenden Ideen befolgt: http://code.tutsplus.com/articles/maintainable-automated-ui-tests--net-35089

Der große Teil ist, dass es wirklich Ihr System Ende-zu-Ende testet, von der Code-Generierung bis zu dem, was Sie tatsächlich generieren. Wenn ein Test fehlschlägt, ist es einfach, ihn zu dem Ort zurückzubringen, an dem der Generator ausgefallen ist.

Es ist ziemlich süß.

Viel Glück!