Bessere Specs mit RSpec, ein Erfahrungsbericht

Veröffentlicht am 24. Juli 2012 von Julien Gantner

Bis vor Kurzem war das Testen unseres Codes eher eine Last als eine Freude. Wir entwickeln agil nach Scrum und sind der Überzeugung, dass Tests absolut notwendig sind, um die gewünschte Codequalität zu erreichen.

Vor Kurzem habe ich mich dann auf die Suche nach einer Problemlösung gemacht. Ich konnte einfach nicht glauben, dass das Testen eine Last sein muss. Meine Ziele waren ganz einfach:

  • Tests schreiben soll Spaß machen!
  • Ich will es schaffen die Tests vor der Implementierung zu schreiben (test first), ohne dass es sich zu umständlich anfühlt.

Nach einiger Suche im Web bin ich auf die RSpec best practices von Jared Carroll gestoßen. Er beschreibt in seinem Blogpost einige Tipps, die einem das Leben leichter machen. Ich möchte die Best Practices hier vorstellen und beschreiben, wie sie mein Verhältnis zu Tests und RSpec verändert haben:

First #describe what you are doing ...

Diese Best Practice besagt, dass man als Parameter eines describe Blocks nur einen Methodennamen benutzt. Zusätzlich sollte man sich dabei an die Konvention halten, das Instanzmethoden ein ‘#’ und Klassenmethoden einen ‘.’ als Präfix erhalten. Dieser Tipp ist recht hilfreich, da man auf einen Blick erkennt, um welche Methode es in diesem Spec geht und ob diese eine Instanz- oder Klassenmethode ist. So weit so gut. Nützlich aber noch nicht weltbewegend.

…Then establish the #context

Hier geht es darum, dass man die context Methode von RSpec benutzt, um die verschiedenen Vorbedingungen von Tests zu beschreiben. Für die Beschreibung des Blocks schlägt er vor explizit given oder when als erstes Wort zu verwenden, um die Rahmenbedingungen des Kontextes klar zu machen. Zunächst dachte ich mir nicht viel dabei, aber als ich versuchte das in der Praxis anzuwenden traf es mich wie ein Schlag ins Gesicht. Durch diesen einfachen Trick wird man gezwungen mehr, und vor allem in der richtigen Weise, über die verschiedenen Vorbedingungen für Methoden nachzudenken. Mir persönlich hat das enorm dabei geholfen sinnvolle Testfälle zu schreiben.

#it only expects one thing

Indem man innerhalb eines it Blocks nur eine Sache prüft, erhöht man die Lesbarkeit der Testfälle. Wirklich interessant war für mich hier, dass man dadurch gezwungen wird, mehr it-Blöcke zu schreiben, die jeweils verschiedene Teilaspekte der Funktionalität abdecken. Zu meiner Überraschung hat dies bei uns dazu geführt, dass man mit einem Blick auf die RSpec-Ausgabe (documentation format) sehen konnte was das Problem war. Die Details des RSpec-Fehlers waren dabei oft unnötige Information. Nach meinem Empfinden macht das auch die Testfälle besser wartbar. Zum Einen da es einfach zu erkennen ist, wo das Problem liegt, auch ohne sich tief in den Code einlesen zu müssen. Zum Anderen sind durch Änderungen in den Tests nur wenige Zeilen Code betroffen, da die Tests genauer auf die eigentliche Funktionalität ausgelegt werden. Im Gegensatz zu unseren vorherigen Tests, die eher Arbeitsabläufe testeten, in denen die zu testende Funktion aufgerufen wurde. Ein weiterer wichtiger Ratschlag in dieser Best Practice ist, dass die Testbeschreibung nicht mit should anfangen sollte. Beim Blick in unsere Tests habe ich das in nahezu jedem Fall gefunden. Es war wie Jared Carroll in seinem Post schrieb: die Ausgabe unserer Testsuite war sehr schwer zu lesen.

Prefer explicitness

Auch hierbei geht alles um die Lesbarkeit der RSpec Ausgabe. Hier gilt besonders, dass es darum geht die Absicht des Testfalls klar zu machen. Mir ist beim verfassen der Spec-Beschreibung oft aufgefallen, dass ich noch weitere Testfälle bzw. Randfälle nicht bedacht habe. Es hilft dabei, wenn man sich für jeden Testfall die Zeit nimmt, über die Beschreibung nachzudenken. Ziel soll es sein, eine einfache und prägnante Formulierung zu finden.

Run specs to confirm readability

Wenn man es sich zur Gewohnheit macht die Specs regelmäßig auszuführen und die Ausgabe zu lesen, fallen einem oft Unstimmigkeiten beim Schreiben der Specs auf. Diese können dann sofort behoben werden, was den Aufwand verringert und das Verständnis des beschriebenen Codestücks erhöht. Auf diese Weise lassen sich auch konzeptuelle Fehler einfacher erkennen. Mit Hilfe von Tools wie watchr oder guard ist es ein Leichtes seine Spec Ausgaben immer im Blick zu haben, während man die Specs schreibt.

Use the right matcher

Um die Specs lesbarer zu gestalten, sollten sich auch die Implementierung der Specs leicht lesen lassen. Dazu hilft es enorm, die Vielfalt der Matcher, die RSpec zur Verfügung stellt, zu nutzen. Zusätzlich kann man recht einfach neue Matcher implementieren falls RSpec keinen passenden mitbringt. Dadurch lassen sich auch komplizierte Bedingungen hinter prägnanten, einfachen und an die Domäne angepassten Matchern verbergen.

Formatting

Diese Ratschläge helfen dabei, sich schnell in Dateien zurecht zu finden, die man nicht selbst geschrieben hat. Durch das Absetzen der einzelnen Blöcke ergibt sich in den Specs ein visuell erkennbares Muster und man kann einfach erkennen, wo ein Block beginnt oder endet.

Fazit

Nachdem ich diese Best Practices gelesen und umgesetzt habe, schreiben sich Specs einfacher als vorher. Es ist absolut erstaunlich, was diese einfachen Regeln bewirken. Durch die Umformulierung der Spec-Beschreibungen hat sich das Testen verändert. Weg vom notwendigen Übel hin zu einem sinnvollen Hilfsmittel, das einem das Leben leichter macht. Mir ist sehr wohl bewusst, dass das der Vorteil von Specs mit RSpecs sein soll oder ist. Allerdings war meine bzw. unsere Herangehensweise an das Testen stark vom “Unit-Testvorgehen” geprägt, wie man es z.B.: mit JUnit im Studium lernt. Mir hat dieses Vorgehen allerdings nie wirklich zugesagt, da man dazu neigt, den Test nicht auf die gewünschte Funktionalität auszulegen, sondern auf die Implementierung. Specs funktionieren anders: Man definiert sich ein gewünschtes Verhalten und schreibt dann die Implementierung.

Test First for free, Yay!

Als zusätzlichen Bonus denkt man anders über die Funktionalität nach und fügt nach und nach immer weitere Specs hinzu. Diese sind in der Regel nicht gleich vollständig implementiert. Aber wenn einem während des Schreibens von Specs auffällt, dass man einen Fall noch nicht bedacht hat, schreibt man einfach schnell einen it-Aufruf mit nur einer Beschreibung. RSpec markiert diese dann automatisch als pending. Selbst wenn die Beschreibung nicht perfekt ist, vergisst man den Fall nicht und kann sich wieder auf den eigentlichen Spec konzentrieren.

Absolut wichtig für dieses Vorgehen ist, dass man die Ausgabe von Rspec auf das documentation Format (rspec spec –format documentation) einstellt. Zusätzlich bekommt man eine Spezifikation der Funktionalität fast gratis dazu.

Ich hoffe dieser Post hat euch gefallen. Falls ihr was hinzuzufügen habt — Lob, Tadel oder sonstiges Feedback — Ich freue mich darauf.