The Blog

Assert(Lesbarkeit und Verständlichkeit)

Was ist besser, “Assert.That(foo.Bar, Is.EqualTo(„bla“)“ oder Foo.Bar.Should.Be.EqualTo(„bla“)? Handelt es sich um eine Geschmacksfrage oder gibt es eine herleitbare Antwort? Was bedeutet besser überhaupt? Schneller zu lesen? Schneller zu schreiben? Hier zumindest hoffe ich auf Konsens: Lesbarkeit und Verständlichkeit von Software sind, wenn es um Syntax geht, entscheidend. Unverständliche Software steht für teure Wartung und schlechte Evolvierbarkeit. Die Frage nach dem Besser ist also in diesem Fall die Frage nach dem schneller-zu-erfassen.

Leider ist hier eine logische Argumentation de facto am Ende: Denn was schneller zu erfassen ist, ist ohne Studie eine subjektive Einschätzung, die beeinflusst ist von Prägung und ästhetischem Empfinden. Nun fehlen subjektiven Einschätzungen nicht die Argumente, zählen wir sie auf:

Pro Assert.That():

  • Assert.That stellt das wichtigste voran u. ist damit schneller zu erfassen. Das “wichtigste zuerst” ist in jedem beschreibenden und auf Verständlichkeit ausgelegten Schreibstil eine Grundregel.
  • Assert.That ist ein visueller Anker.
  • Bei Verwendung von Extensions Methods muss erst „weit“ gelesen werden bis klar wird, dass es sich um eine Assertion handelt.

Con Assert.That():

  • Assert.That erzeugt visuelles Rauschen und bindet Aufmerksamkeit
  • Schon aus der Struktur des Quelltexts sollte klar ersichtlich sein, dass es sich um eine Assertion handelt.
  • Assert.That verlangsamt die Lesegeschwindigkeit, da es immer erst weggefiltert werden muss.

Wichtig zu bemerken: Verwendet man Mspec, verliert Assert.That seinen (behaupteten) Mehrwert, da die Assertion deutlich durch die Struktur angezeigt wird: Aber nicht jeder verwendet Mspec und wer als Mspec Nutzer argumentiert, sollte sich dessen bewusst sein.

Selbst wenn die Wissenschaft (vermutlich die Wahrnehmungspsychologie) herausfände, das eine sei besser als das andere, müssten wir dann gegen unsere Geschmack entscheiden? Nein! Denn Wissenschaft ist erschreckend fehlbar und relativ, und seinem Geschmack zu folgen, ist wichtiger als Mikrooptimierung. Ob Assert.That oder ExtensionMethod kann selbst im Team ausnahmsweise eine Konvention sein, die jeder für sich individuell entscheiden kann, denn nicht einmal hier lohnt die Konsistenz.

P.s: Ein dritte alternative sind Extension Methods in Verwendung mit Assert.That, beispielsweise: Assert.That(foo.Bar.IsEqualTo(„bla“)).


Kick It auf dotnet-kicks.de

11 Awesome Comments So Far

Don't be a stranger, join the discussion by leaving your own comment
  1. Rainer Hilmer
    März 28, 2011 at 10:56 #

    Kann ich so unterschreiben. 🙂
    Assert.That… aus NUnit entspricht auch schon einer fluent Syntax. Daher empfinde ich den Unterschied zu x.Should… auch nur minimal, ja nicht erwähnenswert.

    Unglücklicher sieht dagegen schon die Syntax von MSTest aus. Assert.AreEqual(expected, actual) – oder warte mal – war es nicht Assert.AreEqual(actual, expected)?

    Gerade wenn ich mir das Gesamtbild meiner Tests anschaue, bin ich der Meinung dass ich die fluent Syntax durchziehen möchte. Denn für Mocks setze ich FakeItEasy ein und für Test-Daten AutoFixture. Ein Test liest sich damit von vorne bis hinten ganz flüssig.

    Dummerweise kann ich nicht NUnit benutzen, denn damit müßte ich zusätzliches Geld für NDepend ausgeben um die Testabdeckung zu erfahren. Ein weiteres Schmankerl, nämlich den Test impact müßte ich auch aufgeben. SharpTestsEx oder auch FluentAssertions sind für mich ideale Alternativen. Sie bieten ein fluent interface, benutzen aber unter der Haube MSTest (wahlweise), und somit habe ich das beste aus zwei Welten.

  2. Steffen Forkmann
    März 28, 2011 at 11:07 #

    Hi,

    danke für den Post. Deine Vorschläge “Assert.That(foo.Bar, Is.EqualTo(“bla”)“ oder Foo.Bar.Should.Be.EqualTo(“bla”) sind meiner Meinung nach beide viel zu wordy. Was spricht gegen Foo.Bar.ShouldEqual(„bla“)? Im Kontext eines strukturierten Test-Frameworks (und da bin ich der festen Meinung das man das nutzen sollte) würde ich sogar gern das = verwenden und das Überprüfen durch Foo.Bar = „bar“ antriggern.

    It should_equal_the_reference_text = () => Foo.Bar = „bar“ (= wird nach Assert.AreEqual aufgelöst)

    Aber auf keinen Fall würde ich diese Assert.That-Syntax verwenden. Die vielen Klammern, Punkte und Kommas sind doch echt schrecklich.

    Ich denke wenn man ein strukturiertes Framework verwendet, dann kann man mit wesentlich weniger auskommen, da dann viel über den Kontext geregelt wird.

    Grüße Steffen

  3. Robert
    März 28, 2011 at 13:30 #

    @Steffen, Du hast konkret MSpec im Sinn. Ich finde das sprachlich aber ziemlich verdreht. Because Of => .. und It Should_bla_be passt nur selten wirklich und ist meist so holprig, dass ich es gar nicht verwenden mag. Yodaesk ist da im Vergleich auf der guten Seite der Macht!

    Klassiche UnitTests bleiben aus meiner Sicht eine gute Wahl und für Specs (auch technische) habe ich früher StoryQ verwendet und heute BDDish. Hier stehen aber der lesbare Output und die Dokumentation für das ganze Team im Vordergrund (Teamorientiert), weswegen aus praktischen Gründen Assertions meist in einer eigenen Methode durchgeführt werden. (Das muss nicht so sein.)

    Das was Du strukturiertes Framework bezeichnest, also ein Framework, das es erlaubt UnitTest in einer expliziteren Syntax zu formulieren(?), gibt es leider noch nicht in einer Ausprägung die mich überzeugt.

  4. Mike Bild
    März 28, 2011 at 13:38 #

    Hallo Robert,

    vielen Dank für den Post. Schneller erfassen!? Richtig – das finde ich sehr wichtig. Kein Hokuspokus – schnell lesen und übersetzen können. Was dem im Wege ist besonders. Gut oder schlecht ist abhängig vom Übersetzer (Code lesen/schreiben)

    @Steffen: „Was spricht gegen Foo.Bar.ShouldEqual(“bla”)?“ Ja genau, dass hab ich mich auch gefragt. Ergebnis:

    Ich setze seit einiger Zeit Should und ShouldFluent von Tim Scott ein. Aus meiner Sicht eine einfache und übersichtliche vorallem aber optionale TDD, BDD Syntax.

    http://lunaverse.wordpress.com/category/unit-testing/
    oder NuGet
    Should / ShouldFluent

    Klar, schlussendlich ist Syntax wie so vieles ein großer Teil persönliche Vorliebe. IMHO, wichtig ist – geeignete Übersetzungen bieten zu können.

    Bleibt weiterhin die Frage, Was ist gut übersetzbar und bleibt ohne für Worte einer umfangreichen wordy API flexibel genug? Ein Wiederspruch wie ich finde. Flexibilität braucht meist mehr und komplizierte Worte.

    Wer hohe Testanforderung stellt, muss meiner Meinung nach eine Wordy Test-API ala MSpec nehmen. Wer Tests auf reiner Integrations- oder Akzeptanzebene schreibt kommt sicher auch mit etwas weniger zurecht und hilft seinen Lesern schneller zu übersetzen.

    -Cheers Mike

  5. Rüdiger Plantiko
    März 28, 2011 at 14:30 #

    Hallo Robert,

    ich stimme für die Variante mit Assert am Anfang.

    Das von Dir herangezogene Prinzip „Das Wichtigste zuerst“ überzeugt mich.

    In Testcode soll die Maschine etwas machen, etwas sicherstellen. Wir geben ihr die Anweisung „Stelle sicher, dass…“, und dieser Satzteil kommt als erstes.

    Anmerkung am Rande: Das Ideal „natürlichsprachlich“ müssen wir nicht so weit treiben, dass wir die natürliche Sprache mit all ihren grammatischen Schwächen imitieren. Es reicht ja die Lesbarkeit in natürlicher Sprache. Dass wir kein „richtiges“ Englisch oder gar Deutsch erreichen, ist klar und sollte eben auch gar nicht das Ziel sein.

    Gruss,
    Rüdiger

  6. Steffen Forkmann
    März 28, 2011 at 14:58 #

    Moin nochmal,

    also „das Wichtigste zuerst“ ist für mich eher ein Argument gegen die Assert-Syntax. Ich sehe doch hoffentlich das es ein Test ist, also wird da ja wohl auch irgendwo eine Assertion drin sein. Also wenn ich das nicht sehe, dann hat der Test eindeutig sein Ziel verfehlt.

    Tools wie MSpec und Xunit.BDDExtensions haben auch nicht das Ziel natürliche Sprache abzubilden (jedenfalls nicht primär). Wenn du sowas suchst dann schau mal in NaturalSpec (https://github.com/forki/NaturalSpec) rein. Björn und Alex haben mir begreiflich machen können, dass es in den Frameworks eher um die explizite Strukturierung der AAA-Phasen geht. Was schreibe ich in welche Phase und wie wirkt sich das auf mein wording und insbesondere auch das Fehlerfall-Reporting aus.

    Grüße Steffen

  7. Mike Bild
    März 28, 2011 at 16:01 #

    @Steffen: Mit anderen Worten, die Standard (NUnit) Assert Syntax ist dir nicht expliziet genug?

    Es reicht nicht, sich auf Konventionen festzulegen?

    [Test]…
    //arrange
    var sut = new Foo()
    //act
    var actual = sut.Bar();
    //assert
    actual.Should().True;
    ——————————————-
    oder auch für Kontext-Szenarios:
    [Setup]…
    _sut = new Foo()
    _actual = sut.Bar();

    [Test] …
    _actual.Should().True;

    Ist mehr nicht doch fluent syntactic sugar und daraus generierte Testreporte?

    Anderer Meinung, will heißen gegen oben genannte Konvention, bin ich allerdings bei speziellen Implementierung ala EBC, Async,UI Tests, etc.. Das ist speziell, hier würde für eine „spezielle“ Test DSL plädieren.

    -Cheers Mike

  8. Mike Bild
    März 28, 2011 at 16:13 #

    Sorry, explizit ist natürlich gemeint 😉 Warum kann ich meine Kommentare eigentlicht nicht bearbeiten? 😉

  9. Steffen Forkmann
    März 29, 2011 at 9:00 #

    Hi Mike,

    ja gegen Variante 1 spricht:
    – Trennung der Phasen erscheint nicht im Reporting (z.B. Fehler in Act vs. Fehler in Assert)
    – Wenn du mehrere Assertions hast (und das hat man fast immer) – dann wird beim ersten Fehler abgebrochen. Dir fehlt dann Information welche Assertion das war und wie sich die anderen Verhalten hätten (das hilft sehr oft). Um zu ermitteln welche Assertion es war musst du jetzt also den Assertions Bemerkungen mitgeben. Genau hier helfen MSpec und xbbdx.

    Als Lösung kannst du nun also deine Variante 2 benutzen. Die würde die Assertions getrennt reporten (Vorrausetzung: 1 Assertion pro Test-Methode). Jetzt musst du jedoch aufpassen, dass du dein Setup nicht zu kompliziert machst. Unterschiedliche Test-Methoden könnten unterschiedliche Setups benötigen. Also trennst du die Test-Klassen nach Setup. Genau dazu zwingen dich die Tools auch nur.

    Es geht (meiner Meinung) nach eben nicht primär nur um das wording oder eine Fluent-Syntax. Sondern es geht darum das Setup sauber von anderen Setups zu trennen, Fehler richtig zu reporten usw. Das muss man nicht immer aus einem BDD-Kontext sehen. Wenn Struktur im Test ist, dann werden solche Keywords wie Assert als visuelle Anker völlig unnötig. Das wording ist dann das Sahnehäubchen, aber das muss man trotzdem erst lernen. Das ist im Allgemeinen nicht trivial.

    Ich habe ja schon 2 umfangreiche UnitTest-Frameworks gebaut (NaturalSpec und eins für Dynamics NAV) und kann mit ruhigem Gewissen sagen, dass beide trotz allem Ehrgeiz nicht an MSpec rankommen. Früher als ich mir nur die Syntax angeschaut habe, da habe ich das so noch nicht eingesehen. Aber es passt halt vieles zusammen und nötigt dich das Richtige zu tun. Auch in Hinblick auf Stabilität der Tests und schnelle Handlungsfähigkeit im Fehlerfall.

    Mir fehlen natürlich auch noch ein paar Dinge im Framework (z.B. parametrisierte Tests). Und wenn mir das „Because of“ nicht mehr gefällt, werde ich für Given/When/Then Aliase erzeugen.
    Das Schöne ist es ist erfolgreiches OSS-Projekt. Da helfe ich lieber dort mit als nochmal das Rad zu erfinden.

    Grüße Steffen

  10. Robert
    März 29, 2011 at 10:29 #

    @Steffen, zu erkennen ob ein Fehler im Act oder im Assert auftritt, hier sehe ich bei keinem Test-Framework eine Schwierigkeit und auch nicht so recht die Notwendigkeit. Ist der Test Rot, muss er behoben werden. Eine einfache Fehlerlokalisierung und ein besseres Reporting sind sicher immer positiv, aber dann doch eher untergeordnete Aspekte, da alle Alternativen – bis hin zum einfachen Stacktrace – gut genug sind. (Reports für Kunden ausgenommen, aber das ist ja nicht der Fokus über den wir reden und auch nicht der Fokus von MSpec(?))

    Schwer wiegen für mich jedoch die Strukturvorgaben in MSpec oder auch StoryQ, also das enge Korsett aus „Because Of, In Order to , It Should“ oder analog „Given, When, Then”, denn die reduzierten Ausdrucksmöglichkeiten machen die Verwendung beider Frameworks schwer! Hier ist viel Übung erforderlich, um produktiv zu werden. Und das Problem lässt sich auch nicht durch Aliase beheben. Aus meiner Sicht sind das Nachteile, die einfache UnitTest nicht – und andere Frameworks weniger haben.

    Zur Trennung von Setup/Context: Das ist für mich eher eine Frage eines gute Programmierstils, als das eines Frameworks. Auch in MSpec kann ich ein großes unhandliches Setup aufbauen, da bewahrt mich das Framework nicht vor. Dass MSpec mit einem guten Programmierstil am besten aussieht, ist nur ein geringer Schutz! Vor meinem geistigen Auge sehe ich ein paar üble, lange Tests, die von klassischen NUnit Tests zu MSpec Tests gewandelt wurden und die auch noch in MSPec übel sind.

    Insgesamt stehe ich MSpec positiv gegenüber. Es zeigt in die richtige Richtung und es ist vermutlich das Beste Framework, wenn es um entwicklerorientierte Tests geht. Anderseits muss der Entwickler sich schon die Aufwand vs. Mehrwert Frage stellen. Mir scheint ein gut strukturierter UnitTest immer noch eine starke Konkurrenz und auch eine gute Wahl zu sein.

  11. Mike Bild
    März 29, 2011 at 9:59 #

    Hi Steffen,

    „Früher als ich mir nur die Syntax angeschaut habe, da habe ich das so noch nicht eingesehen. Aber es passt halt vieles zusammen und nötigt dich das Richtige zu tun. Auch in Hinblick auf Stabilität der Tests und schnelle Handlungsfähigkeit im Fehlerfall.“

    „Mir fehlen natürlich auch noch ein paar Dinge im Framework (z.B. parametrisierte Tests). Und wenn mir das “Because of” nicht mehr gefällt, werde ich für Given/When/Then Aliase erzeugen.“

    OK das passt sehr gut denke ich. Damit verstehe ich die Motivation und die Vorteile auch besser. Danke!

    -Cheers Mike