Tuesday 8 May 2012

Ein Test ohne Assert

Daniel Minigshofer von it-agile schreibt über Test-Antipatterns und macht Vorschläge zum besseren Umgang mit Exceptions. Dabei schreibt er:
Es wäre auch möglich ohne eine Assert-Anweisung diesen Test zu schreiben. Jedoch einen Test ohne Assert ist kein wirklicher Test. Außerdem ist es für andere Entwickler nicht ersichtlich was hier eigentlich getestet wird.
Leider geht er nicht weiter darauf ein, und lässt mich allein mit dem Sesamstraßen-Dreiklang: "Wieso? Weshalb? Warum?"

Science to the rescue!


1. Definiere "wirklicher Test"
2. Finde Gegenbeispiele, die der Definition genügen, aber kein Assert haben.

Der erste Teil scheint einfach: "Given" - "When" - "Then" sollte völlig ausreichen.
Im ersten Schritt bereite ich die Elemente vor, die ich brauche, um das gewünschte Verhalten bestimmen, im zweiten Schritt löse ich das Verhalten aus, im dritten Schritt formuliere ich eine Annahme, der die Wirklichkeit standhalten soll.

Augenscheinlich ist nach Daniels Definition die Verständlichkeit für Dritte nicht Teil der Eigenschaften eines wirklichen Tests, aber ich denke, dass ein Test ohne erklärende Zusammenfassung langfristig nicht sehr wertvoll ist. Laden wir das noch dazu, und fassen zusammen:
Ein Test besteht aus Vorbereitung, Durchführung und Überprüfung und einer erklärenden Beschreibung.

Trommelwirbel, Tusch, Gegenbeispiel: Objektkonstruktion.
Gegeben sei ein schlecht strukturiertes komplexes Objekt, zum Beispiel ein SSH-Klient.
Er kann sich auf verschiedene Arten gegenüber dem Server authentisieren, etwa über Benutzernamen und Passwort oder über ein Schlüsselpaar.
Leider kann er nicht beides gleichzeitig, und wenn ich den Konstruktor überfordere, indem ich Schlüssel und Passwort angebe, schmeißts Exceptions, natürlich ebenso, wenn ich einen öffentlichen, aber keinen privaten Schlüssel benenne.

Das fiktionale ganze sieht also etwa so aus:

public SSHClient(String host, int port, String username, String password, String pathToPublicKey, String pathToPrivateKey)

Mein Test soll zeigen, welche Argumente ich gemeinsam verwenden darf, um das System anzusprechen.



@Test
public void canBeConstructedWithUsernameAndPasswordAlone(){
    String host = "myhost.example.com";
    int port = 22;
    String user = "urs";
    String pass = "asecret"
    new SSHClient(host, port, user, pass, null, null);
}


Beschreibung? Check.
Vorbereitung? Check.
Durchführung? Check.
Zusicherung? Check - denn die Konstruktion ist erfolgreich und schmeißt keine Exception.

Ich sage "wirklicher Test".

 Die Sinnhaftigkeit des ganzen ergibt sich aus dem Kontext, etwa wenn weitere Tests zeigen, dass andere Parameterkombinationen Exceptions mit sinnvollen Texten werfen, die den Klienten schnell und direkt auf seinen Fehler hinweisen.

Warum sollte ich mehr schreiben, als nötig ist? Sagts mir in den Kommentaren!

5 comments:

  1. Hallo Urs, bitte verlinke doch den konkreten Artikel http://danielminigshofer.blogspot.de/2012/05/4-der-habgierige-fanger-10-anti.html und nicht nur den Blog.

    Mir ist wichtig, was der Test bzw Text mir "erzählt". GWT ist nur ein meist nützliches, aber kein notwendiges Gerüst. Wenn ich versuche den Test - in beiden Varianten - von Daniel zu lesen, dann erfahre ich Implementationsdetails: Das erste Objekt soll die Id eins zugewiesen bekommen - das finde ich - so ohne Kontext nicht spannend und der ganze Test wäre IMHO ein Löschkandidat.

    Nichts desto trotz stimme ich auch der Hauptaussage von Daniel zu, es ist es ein Antipattern in Tests explizit zu erwarten das keine Exception geworfen werden, denn eine Exception wird so oder so vom Framework als Fehler angezeigt.

    ReplyDelete
    Replies
    1. Upsala, der Link beim Umstellen der Einleitung verlorengegangen. Danke.

      Mit der Hauptaussage von Daniel bin ich ebenfalls auf einer Linie, nur das Detail mit dem Assert kam mir komisch vor.

      Hast Du ein Beispiel für einen Test ohne GWT? Ich habe das Gefühl, dass die Struktur immer da ist, und sich nur unterscheidet, wie deutlich Du sie herausarbeitest.
      Was macht für Dich einen "wirklichen Test" aus?

      Delete
  2. Hallo Urs,

    vielen Dank für deinen Kommentar und bitte korrigiere meinen Namen ;-)

    GWT kommt eigentlich aus dem BDD. Im Bereich von Unit-Tests würde ich hier vom AAA-Pattern sprechen (Arrange, Act, Assert).

    Als Beschreibung des Tests würde ich den Namen des Tests sehen.

    Der Name des Tests ist in meinem Beispiel vielleicht nicht aussagekräftig gewählt. Es handelt sich aber hier nur um ein Beispiel, um zu sehen, dass man nicht darauf testen sollte ob eine Exception aufgetreten ist. Jede nicht explizit gefangene Exception wird von Unit-Test-Framework automatisch gefangen und somit ein roter Test protokolliert (Das Framework macht immer einen impliziten Assert).

    Ein wirklicher Unit-Test, ist für mich ein Test der dem AAA-Pattern entspricht.

    Viele Grüße,
    Daniel

    ReplyDelete
    Replies
    1. Hey Daniel,
      bitte entschuldige den vertippten Namen.

      Die Frage, die ich lang beschrieben habe, ist eigentlich ganz kurz: Ist es ein "Assert" im Sinne des AAA, wenn ich mich auf das Exception-Handling meines Frameworks verlasse?

      Verstehe ich richtig, dass Dir das implizite Assert genügt?

      Delete
  3. Hey Urs,

    kein Problem ;-) der Fehler kommt häufiger vor.

    Warum sollte es nicht okay sein, sich auf das Exception-Handling des Frameworks zu verlassen?

    Ein impliziter Assert ist genauso ein Assert wie ein expliziter. Somit meine Antwort zu der Frage Ja es reicht ein impliziter Assert.

    Dazu ein Beispiel: Würde ich bei einem Test wie z.B.

    Assert.IsNotNull(myObject)
    Assert.AreEqual(myObject.Name,"bla")

    die erste Prüfung auf Null löschen. Warum? Das Framework gibt mir sowieso Meldung darüber wenn myObject null ist. Ich teste implizit ob myObject nicht null ist in der AreEqual-Methode.

    Das Framework kann natürlich die richtige Exception verschlucken, wie ich bei meinem Post gezeigt habe. Das wäre natürlich Fatal.

    Deine Frage warum ein Test, der ohne Assert kein richtiger Test ist, wollte ich demnächst in einem weiteren Post beantworten mit dem Antipattern von "The Secret Catcher".

    Ein weiteres Beispiel für einen Test ohne Assert wäre in NUnit ein Test, der eine Exception erwaret. Hier schreibt man in der Methode auch nur Arrange und Act. Das Assert steht als Attribut über der Methode

    [ExpectedException( typeof( ArgumentException ) )]
    public void TestMethod(){}

    Das Unit-Test-Framework kümmert sich implizit um die Sicherstellung.

    Viele Grüße,
    Daniel

    ReplyDelete