kackb00n: Unit-Tests

Hi

angenommen ich hab ein modulares System, dass bestimme Interfaces bereit stellt, zB;

interface {
    void Add(Item i);
    Item Get();
    void Remove();
}

Nun bekomm ich eine fertige DLL/SO/JAR-Datei und darin ist eine Implementation des Interfaces. Wie schreib ich jetzt die Unit-Tests?

Problem:

  • um Add zu testen muss ich Get auch benutzen
  • um Get zu testen muss ich Add auch benutzen
  • im Remove zu testen muss ich sowohl Add als auch Get benutzen

Wie implementiert ihr die Tests?

MfG

akzeptierte Antworten

  1. Wie implementiert ihr die Tests?

    Erste Voraussetzung ist, dass eine sog. "Unit" nicht an irgendeine Klasse oder Programm oder sonstigen Code gebunden ist. D.h., dass eine Unit eben nicht eine bestimmte Instanz erfordert sondern auch mit einer Attrappe (Mock, Mockobject) aufgerufen werden kann.

    Als Nächstes sollte es dem Test möglichst einfach sein, Erfolg/Misserfolg einer bestimmten Funktionalität feststellen zu können. D.h., dass ein Testergebnis maschinell verwertbar sein sollte.

    MfG

    PS: Meine Units sind Interfaces und Methoden einer Factory, jeweils in einer dedizierten Datei. Da habe ich die Möglichkeit, den Code für einen Test in derselben Datei zu notieren, das ermöglicht Tests bereits während des Entwickeln. So sind auch spätere Erweiterungen schnell getestet, weil der Test sozusagen eingebaut ist. Auf diese Art und Weise sind auch Fehler sehr schnell eingegrenzt.

    1. Tach!

      Als Nächstes sollte es dem Test möglichst einfach sein, Erfolg/Misserfolg einer bestimmten Funktionalität feststellen zu können. D.h., dass ein Testergebnis maschinell verwertbar sein sollte.

      Ach! Genauso geht man ja test-driven vor. Die Frage war nicht, was TDD ist. Diese Grundfunktionalität beinhalten die TDD-Frameworks.

      PS: Meine Units sind Interfaces und Methoden einer Factory, jeweils in einer dedizierten Datei. Da habe ich die Möglichkeit, den Code für einen Test in derselben Datei zu notieren, das ermöglicht Tests bereits während des Entwickeln. So sind auch spätere Erweiterungen schnell getestet, weil der Test sozusagen eingebaut ist. Auf diese Art und Weise sind auch Fehler sehr schnell eingegrenzt.

      Selbst wenn man den Test-Code nicht mit dem Produktivcode vermischt, hat man die von dir erwähnten Möglichkeiten. Ob Fehler schnell eingrenzbar sind, hängt nicht vom Test ab, sondern vom Fehler selbst. Wenn TDD eine Hilfe ist, dann spielt es keine Rolle, wo sich dessen Code befindet. Und vor allem hat man bei einer Separierung den Test-Code und dessen Kompilat nicht unnötig in der fertigen Anwendung.

      dedlfix.

      1. Ob Fehler schnell eingrenzbar sind, hängt nicht vom Test ab, sondern vom Fehler selbst.

        Da irrst Du Dich gewaltig! Das würde ja bedeuten dass es Fehler gäbe die gar nicht gefunden werden können. Aber wahrscheinlich bist Du kein Praktiker. MfG

        1. Tach!

          Ob Fehler schnell eingrenzbar sind, hängt nicht vom Test ab, sondern vom Fehler selbst.

          Da irrst Du Dich gewaltig! Das würde ja bedeuten dass es Fehler gäbe die gar nicht gefunden werden können.

          Mit TDD testet man das Verhalten nach außen hin. Man findet damit nur fehlerhaftes Verhalten. Ob die dafür ursächlichen Fehler gefunden werden können oder nicht, liegt nicht am Test, sondern daran, ob man die ursächliche Stelle im Code findet.

          dedlfix.

        2. Hallo pl,

          Ob Fehler schnell eingrenzbar sind, hängt nicht vom Test ab, sondern vom Fehler selbst.

          Da irrst Du Dich gewaltig! Das würde ja bedeuten dass es Fehler gäbe die gar nicht gefunden werden können.

          Mit Tests kannst du nur die Anwesenheit eines Fehlers beweisen, nicht die Abwesenheit. Insofern: völlig richtig, du kannst mit Tests nicht alle Fehler finden.

          LG,
          CK

      2. Hallo dedlfix,

        Ob Fehler schnell eingrenzbar sind, hängt nicht vom Test ab, sondern vom Fehler selbst.

        Nein, das hängt durchaus auch von den Tests ab. Testet der Test zu viel auf einmal, ist der Fehler schwerer zu debuggen als wenn der Test nur genau eine Sache testet.

        LG,
        CK

    2. @@pl

      Erste Voraussetzung ist, dass eine sog. "Unit" nicht an irgendeine Klasse oder Programm oder sonstigen Code gebunden ist.

      Unit tests schreibt man üblicherweise für Klassen.

      Da habe ich die Möglichkeit, den Code für einen Test in derselben Datei zu notieren

      Das ist unsinnig. Man will die Test während der Entwicklung, aber nicht auf dem Produktivsystem haben.

      das ermöglicht Tests bereits während des Entwickeln.

      Das ist Quatsch. Test driven development hängt keinesfalls daran, dass Tests in derselben Datei notiert wären.

      BTW, ich habe kürzlich was über Test driven HTML development erzählt.

      LLAP 🖖

      --
      “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
      1. Das ist unsinnig. Man will die Test während der Entwicklung, aber nicht auf dem Produktivsystem haben.

        Logisch. Dafür ist gesorgt.

        Das ist Quatsch. Test driven development hängt keinesfalls daran, dass Tests in derselben Datei notiert wären.

        Hab ich auch nie behauptet.

        Aber schön dass Du auch da bist. Wie ein Huhn was gackert wenn andere Eier legen 😉

        1. @@pl

          Man will die Test während der Entwicklung, aber nicht auf dem Produktivsystem haben.

          Logisch. Dafür ist gesorgt.

          So in der Art?

          program-code
          
          IF development-mode
          {
              tests
          }
          

          Das wäre denkbar. Ich glaube aber nicht, dass dies der Wartbarkeit des Codes besonders zuträglich ist.

          Test driven development hängt keinesfalls daran, dass Tests in derselben Datei notiert wären.

          Hab ich auch nie behauptet.

          Das klang so.

          LLAP 🖖

          --
          “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
          1. So in der Art?

            Nein. Der Testcode ist unterhalb eines Token notiert und wird im produktiv-Mode gar nicht compiliert. Ich hab das nur erwähnt, weil ein TDD späteren Unit-Tests förderlich aber auch unabhängig davon ist. Wichtig ist, dass eine Unit gemockt werden kann und das kann ich mit allen meinen Unit's machen: Über ein externes Script die alle aufrufen. Das braucht nur eine Konfiguration wo die Namen+Prüfkriterium drinstehen.

            PS: Wenn Euch solche Auskünfte nichts wert sind und in Eurem Forum nur negativ bewertet werden, kommt Ihr Euch da nicht ein bischen komisch vor!?

  2. Tach!

    Nun bekomm ich eine fertige DLL/SO/JAR-Datei und darin ist eine Implementation des Interfaces. Wie schreib ich jetzt die Unit-Tests?

    Eigentlich ist das die Aufgabe des eigentlichen Autors. Aber gut, wenn du einen Grund hast, dessen Ergebnis zu überwachen, dann soll es eben so sein.

    Problem:

    • um Add zu testen muss ich Get auch benutzen
    • um Get zu testen muss ich Add auch benutzen
    • im Remove zu testen muss ich sowohl Add als auch Get benutzen

    Wie implementiert ihr die Tests?

    Wenn du die Collection selbst nicht zur Verfügung hast oder vorgeben kannst, und kein andere Schnittstelle zu ihr existiert, wirst du dich auf die Möglichkeiten dieses Interfaces beschränken müssen. Ohne die Collection geht es nicht. Und es wird auch schwer werden, wenn du sie nicht komplett leeren kannst. Dann hat sie ja sonstwelchen Inhalt und kann die Tests torpedieren.

    dedlfix.

  3. Das API ist zumindest mal merkwürdig. Auf Grund der gegeben Methoden erinnert es am ehesten an eine Queue (FIFO-Liste) mit folgender Semantik:

    • Add(Item i) hängt vorn einen Eintrag an die Queue
    • Get() liefert das älteste Element, löscht es aber nicht
    • Remove() löscht das älteste Element, liest es aber nicht

    Vielleicht ist das tatsächlich spezifizierte Verhalten anders, jedenfalls musst Du die exakte Spec kennen, sonst kannst Du keine Tests machen. Dass Du ohne Einblick ins Objekt die meisten Tests nicht isoliert auf einer Methode machen kannst, ist normal, deswegen brauchst Du Dir keinen Kopf zu machen.

    Unter der Annahme, dass ich das gewünschte Verhalten des Interface richtig erraten habe, hier mal ein paar Tests, die ich schreiben würde:

    // Teste ob Add(null) das gewünschte Ergebnis liefert
    object testObject = GetNewTestObject();
    testObject.Add(null);
    // Muss entweder funktionieren oder eine Exception werfen - hängt von der Impl oder Spec ab
    
    // Teste, ob ein Add in eine leere 'Queue' das geAddete-Element liefert
    object testObject = GetNewTestObject();
    Item i = new Item();
    testObject.Add(i);
    Assert.AreSame(i, Get());
    
    // Teste, ob FIFO-Verhalten vorliegt
    object testObject = GetNewTestObject();
    Item i1 = new Item();
    Item i2 = new Item();
    testObject.Add(i1);
    testObject.Add(i2);
    Assert.AreSame(i1, testObject.Get());
    testObject.Remove();
    Assert.AreSame(i2, testObject.Get());
    
    // Teste, ob ein Get ohne zwischenzeitliches Remove reproduzierbar das gleiche Item liefert
    object testObject = GetNewTestObject();
    Item i1 = new Item();
    Item i2 = new Item();
    testObject.Add(i1);
    testObject.Add(i2);
    Assert.AreSame(i1, Get());
    Assert.AreSame(i1, Get());
    

    An der Stelle siehst Du schon, dass auch Tests geplant werden müssen. Die Aufgabe, ein TestObjekt zu erzeugen mit N Elementen in einer definierten Reihenfolge, wiederholt sich. Also: Funktion schreiben, gemeinsamen Code auslagern. Das Folgende ist C# Syntax, das musst Du natürlich für deinen Fall anpassen.

    function Item[] FillTestobjectWithItems(TestObject t, int n)
    {
       Item[] testItems = new Item[n];
       for (int i=0; i<n; i++)
       {
          testItems[i] = new Item();
          // Ggf. Item irgendwie idenfizierbar identifzieren
          t.Add(testItems[i]);      
       }
       return testItems;
    }
    

    Mit dieser Funktion vereinfacht sich Testfall 3 zu:

    // Teste, ob FIFO-Verhalten vorliegt
    TestObject testObject = GetNewTestObject();
    Item[] testItems = FillTestobjectWithItems(testObject, 5);
    
    for (int i=0; i<testItems.Length; i++)
    {
       Assert.AreSame(testItems[i], testObject.Get();
       testObject.Remove();
    }
    

    Und so weiter und so weiter. Viel Spaß :)

    Rolf