Frage

Ich weiß, das ist eine schwierige und offene Frage, aber ich dachte, ich werfe sie mal auf den Boden und schaue, ob jemand irgendwelche interessanten Vorschläge hat.

Ich habe einen Codegenerator entwickelt, der unsere Python-Schnittstelle zu unserem C++-Code (generiert über SWIG) führt und den Code generiert, der erforderlich ist, um diesen als WebServices bereitzustellen.Als ich diesen Code entwickelt habe, habe ich es mit TDD gemacht, aber ich habe festgestellt, dass meine Tests höllisch brüchig sind.Da jeder Test im Wesentlichen überprüfen wollte, ob ich für ein bestimmtes Bit Eingabecode (das zufällig ein C++-Header ist) ein bestimmtes Bit Ausgabecode erhalten würde, habe ich eine kleine Engine geschrieben, die Testdefinitionen aus XML-Eingabedateien liest und Tests generiert Fälle aus diesen Erwartungen.

Das Problem ist, dass ich Angst davor habe, den Code überhaupt zu ändern.Das und die Tatsache, dass die Unit-Tests selbst Folgendes sind:komplex, und b:spröde.

Deshalb versuche ich, alternative Lösungsansätze für dieses Problem zu finden, und mir kommt der Gedanke, dass ich es vielleicht falsch angehe.Vielleicht muss ich mich mehr auf das Ergebnis konzentrieren, IE:Läuft der von mir generierte Code tatsächlich und macht er das, was ich möchte, und sieht der Code nicht so aus, wie ich es möchte?

Hat jemand Erfahrungen mit etwas Ähnlichem, den er gerne mitteilen möchte?

War es hilfreich?

Lösung

Ich fing an, eine Zusammenfassung meiner Erfahrungen mit meinem eigenen Codegenerator zu verfassen, ging dann zurück und las Ihre Frage noch einmal und stellte fest, dass Sie die gleichen Probleme bereits selbst angesprochen hatten. Konzentrieren Sie sich auf die Ausführungsergebnisse und nicht auf das Code-Layout/-Erscheinungsbild.

Das Problem ist, dass dies schwer zu testen ist, der generierte Code möglicherweise nicht für die tatsächliche Ausführung in der Umgebung des Unit-Testsystems geeignet ist und wie kodiert man die erwarteten Ergebnisse?

Ich habe festgestellt, dass Sie den Codegenerator in kleinere Teile zerlegen und diese einem Unit-Test unterziehen müssen.Wenn Sie mich fragen, ähnelt das Unit-Testen eines vollständigen Codegenerators eher einem Integrationstest als einem Unit-Test.

Andere Tipps

Denken Sie daran, dass „Unit-Tests“ nur eine Art von Tests sind.Sie sollten in der Lage sein, einen Unit-Test durchzuführen intern Teile Ihres Codegenerators.Was Sie hier wirklich sehen, sind Tests auf Systemebene (auch bekannt alsRegressionstests).Es ist nicht nur Semantik...Es gibt unterschiedliche Denkweisen, Herangehensweisen, Erwartungen usw.Es ist sicherlich mehr Arbeit, aber Sie müssen wahrscheinlich in den sauren Apfel beißen und eine End-to-End-Regressionstestsuite einrichten:C++-Dateien korrigiert -> SWIG-Schnittstellen -> Python-Module -> bekannte Ausgabe.Sie möchten unbedingt die bekannte Eingabe (fester C++-Code) mit der erwarteten Ausgabe vergleichen (was aus dem endgültigen Python-Programm kommt).Das direkte Überprüfen der Ergebnisse des Codegenerators wäre wie das Verändern von Objektdateien ...

Ja, Ergebnisse sind das EINZIGE, was zählt.Die eigentliche Aufgabe besteht darin, ein Framework zu schreiben, mit dem Ihr generierter Code unabhängig ausgeführt werden kann ...verbringe deine Zeit dort.

Wenn Sie unter *nux arbeiten, könnten Sie erwägen, das Unittest-Framework zugunsten eines Bash-Skripts oder Makefiles zu löschen.Unter Windows könnten Sie erwägen, eine Shell-App/Funktion zu erstellen, die den Generator ausführt und dann den Code (als einen anderen Prozess) verwendet und diesen einem Unittest unterzieht.

Eine dritte Möglichkeit wäre, den Code zu generieren und daraus dann eine App zu erstellen, die nur einen Unittest enthält.Auch hier benötigen Sie ein Shell-Skript oder so etwas, um dies für jede Eingabe auszuführen.Was die Codierung des erwarteten Verhaltens angeht, fällt mir auf, dass dies auf die gleiche Weise erfolgen könnte wie beim C++-Code, indem einfach die generierte Schnittstelle anstelle der C++-Schnittstelle verwendet würde.

Ich wollte nur darauf hinweisen, dass Sie immer noch feinkörnige Tests durchführen und gleichzeitig die Ergebnisse überprüfen können:Sie können einzelne Codeabschnitte testen, indem Sie sie in einen Setup- und Bestätigungscode verschachteln:

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

Vorausgesetzt, Sie haben Ihren generierten Code aus kleineren Blöcken zusammengestellt und die Blöcke ändern sich nicht häufig, können Sie mehr Bedingungen anwenden und etwas besser testen und hoffentlich vermeiden, dass alle Ihre Tests abbrechen, wenn Sie Einzelheiten eines Blocks ändern.

Beim Unit-Testen handelt es sich lediglich um das Testen einer bestimmten Einheit.Wenn Sie also eine Spezifikation für Klasse A schreiben, ist es ideal, wenn Klasse A nicht über die echten konkreten Versionen von Klasse B und C verfügt.

Ok, mir ist später aufgefallen, dass der Tag für diese Frage C++/Python enthält, 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 das obige System A Schnittstellen zu den Systemen B und C einfügt, können Sie nur System A einem Komponententest unterziehen, ohne dass die tatsächliche Funktionalität von einem anderen System ausgeführt wird.Dies ist Unit-Test.

Hier ist eine clevere Vorgehensweise für die Herangehensweise an ein System von der Erstellung bis zur Fertigstellung, mit einer anderen When-Spezifikation für jedes 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);
    }
}

Zusammenfassend lässt sich sagen, dass eine einzelne Einheit/Spezifikation mehrere Verhaltensweisen haben kann und die Spezifikation mit der Entwicklung der Einheit/des Systems wächst.Und wenn Ihr zu testendes System von anderen konkreten darin enthaltenen Systemen abhängt, seien Sie vorsichtig.

Meine Empfehlung wäre, eine Reihe bekannter Input-Output-Ergebnisse zu ermitteln, z. B. einige einfachere Fälle, die Sie bereits haben, und Unit-Test des erzeugten Codes.Es ist durchaus möglich, dass beim Wechseln des Generators die genaue erzeugte Saite etwas anders sein kann ...Aber was Sie wirklich interessiert, ist, ob es auf die gleiche Weise interpretiert wird.Wenn Sie also die Ergebnisse so testen, wie Sie den Code testen würden, wenn es Ihre Funktion wäre, werden Sie herausfinden, ob er auf die von Ihnen gewünschte Weise erfolgreich ist.

Grundsätzlich möchten Sie wissen, ob Ihr Generator das erzeugt, was Sie erwarten, ohne jede mögliche Kombination physisch zu testen (auch:unmöglich).Indem Sie sicherstellen, dass Ihr Generator in der von Ihnen erwarteten Weise konsistent ist, können Sie sicher sein, dass der Generator in immer komplexeren Situationen erfolgreich sein wird.

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

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

In meinem Fall generiert das Programm viele Arten von Code (C#, HTML, SCSS, JS usw.), die zu einer Webanwendung kompiliert werden.Die beste Möglichkeit, Regressionsfehler insgesamt zu reduzieren, besteht meiner Meinung nach darin, die Webanwendung selbst zu testen, nicht den Generator.

Verstehen Sie mich nicht falsch, es gibt immer noch Unit-Tests, bei denen ein Teil des Generatorcodes überprüft wird, aber das Beste für unser Geld waren UI-Tests für die generierte App selbst.

Da wir es generieren, generieren wir auch eine schöne Abstraktion in JS, die wir zum programmgesteuerten Testen der App verwenden können.Wir sind einigen hier skizzierten Ideen gefolgt: http://code.tutsplus.com/articles/maintainable-automated-ui-tests--net-35089

Das Tolle daran ist, dass es Ihr System wirklich durchgängig testet, von der Codegenerierung bis hin zu dem, was Sie tatsächlich generieren.Wenn ein Test fehlschlägt, lässt sich die Ursache leicht auf die Ursache zurückführen, an der der Generator kaputt gegangen ist.

Es ist ziemlich süß.

Viel Glück!

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top