Ich wollte euch Leute fragen, in welchen Fällen es sinnvoll ist, statisch getippten Funktionscode, wie er in haskell, scala, ocaml, nemerle, f # oder haXe geschrieben ist, zu testen das Wissen der größeren Gemeinschaften nutzen).
Ich frage dies, weil nach meinem Verständnis:
Ein Aspekt von Unit-Tests ist, dass die Spezifikationen in lauffähiger Form vorliegen. Ist es jedoch bei Verwendung eines deklarativen Stils, der die formalisierten Spezifikationen direkt der Sprachsemantik zuordnet, tatsächlich möglich , die Spezifikationen in ausführbarer Form auf eine separate Weise auszudrücken, die einen Mehrwert schafft?
Der offensichtlichere Aspekt von Komponententests besteht darin, Fehler aufzuspüren, die durch statische Analyse nicht aufgedeckt werden können. Angesichts der Tatsache, dass typensicherer Funktionscode ein gutes Werkzeug ist, um sehr genau zu codieren, was Ihr statischer Analysator versteht, scheint es, dass Sie viel Sicherheit in Richtung statischer Analyse verlagern können. Ein einfacher Fehler wie die Verwendung von
x
anstelle vony
(beides sind Koordinaten) in Ihrem Code kann jedoch nicht behandelt werden. OTOH ein solcher Fehler könnte auch beim Schreiben des Testcodes auftreten, daher bin ich mir nicht sicher, ob es den Aufwand wert ist.Unit-Tests führen zu Redundanz, was bedeutet, dass bei Änderungen der Anforderungen sowohl der Code für die Implementierung als auch die Tests für diesen Code geändert werden müssen. Dieser Aufwand ist natürlich ungefähr konstant, so könnte man argumentieren, dass es nicht wirklich wichtig ist. In Sprachen wie Ruby ist dies zwar nicht mit den Vorteilen vergleichbar, aber angesichts der Tatsache, dass statisch getippte Funktionsprogramme viele der Grundeinheitentests abdecken, scheint es sich um einen konstanten Overhead zu handeln, den man einfach ohne Abstriche reduzieren kann.
Daraus würde ich schließen, dass Unit-Tests in diesem Programmierstil etwas veraltet sind. Natürlich kann eine solche Behauptung nur zu Religionskriegen führen. Lassen Sie mich das auf eine einfache Frage reduzieren:
In welchem Umfang verwenden Sie bei einem solchen Programmierstil Komponententests und warum (welche Qualität erhoffen Sie sich für Ihren Code)? Oder umgekehrt: Haben Sie Kriterien, anhand derer Sie eine Einheit des statisch typisierten Funktionscodes als vom statischen Analysegerät abgedeckt und damit ohne die Notwendigkeit einer Abdeckung durch Einheitentests qualifizieren können?
quelle
Antworten:
Wenn Sie Spezifikationen haben, die direkt auf Funktionsdeklarationen abgebildet werden können - in Ordnung. Typischerweise sind dies jedoch zwei völlig unterschiedliche Abstraktionsebenen. Unit-Tests dienen zum Testen einzelner Codeteile, die vom selben Entwickler, der an der Funktion arbeitet, als White-Box-Tests geschrieben wurden. Specs sehen normalerweise so aus: "Wenn ich diesen Wert hier eingebe und auf diese Schaltfläche drücke, sollte dies und das passieren." Typischerweise führt eine solche Spezifikation dazu, dass weit mehr als eine Funktion entwickelt und getestet werden muss.
Sie vermuten hier, dass Unit-Tests tatsächlich dazu dienen, Fehler in Ihrem Code aus erster Hand zu finden - das ist nicht wahr, zumindest ist es nur teilweise wahr. Sie sollen Sie daran hindern, später Fehler einzuführen, wenn sich Ihr Code weiterentwickelt. Wenn Sie also zuerst Ihre Funktion testen ließen und Ihr Komponententest funktionierte (wobei "x" und "y" ordnungsgemäß installiert waren) und dann beim Refactoring "x" anstelle von "y" verwendeten, zeigt Ihnen der Komponententest, dass dies der Fall ist.
In der Technik setzen die meisten Sicherheitssysteme auf Redundanz. Zum Beispiel zwei Pausen in einem Auto, ein überflüssiger Fallschirm für einen Fallschirmspringer usw. Dieselbe Idee gilt für Unit-Tests. Natürlich kann es ein Nachteil sein, mehr Code zu haben, wenn sich die Anforderungen ändern. Daher ist es besonders bei Unit-Tests wichtig, sie trocken zu halten (befolgen Sie das Prinzip "Dont Repeat Yourself"). In einer statisch getippten Sprache müssen Sie möglicherweise weniger Komponententests schreiben als in einer schwach getippten Sprache. Insbesondere "formale" Tests sind möglicherweise nicht erforderlich - was gut ist, da Sie mehr Zeit haben, an den wichtigen Komponententests zu arbeiten, mit denen wesentliche Dinge getestet werden. Und denken Sie nicht nur, weil Sie statische Typen haben, Sie keine Komponententests benötigen, es gibt immer noch viel Raum, um Fehler beim Refactoring einzuführen.
quelle
Es ist sehr unwahrscheinlich, dass Sie Ihre Spezifikationen vollständig als Typeinschränkungen ausdrücken können.
Ein wesentlicher Vorteil dieses Stils besteht darin, dass reine Funktionen einfacher zu testen sind: Es ist nicht erforderlich, einen externen Status einzurichten oder ihn nach der Ausführung zu überprüfen.
Häufig kann die Spezifikation (oder ein Teil davon) einer Funktion als Eigenschaften ausgedrückt werden, die den zurückgegebenen Wert mit Argumenten in Beziehung setzen. In diesem Fall können Sie mit QuickCheck (für Haskell) oder ScalaCheck (für Scala) diese Eigenschaften als Ausdrücke der Sprache aufschreiben und prüfen, ob sie für zufällige Eingaben gültig sind.
quelle
Sie können sich Unit-Tests als Beispiel für die Verwendung des Codes vorstellen, zusammen mit einer Beschreibung, warum er wertvoll ist.
Hier ist ein Beispiel, in dem Onkel Bob so freundlich war, sich mit mir in John Conways "Game of Life" zu paaren . Ich denke, es ist eine hervorragende Übung für diese Art von Dingen. Die meisten Tests sind systemübergreifend und testen das gesamte Spiel, aber der erste Test testet nur eine Funktion - die, die die Nachbarn um eine Zelle berechnet. Sie können sehen, dass alle Tests in deklarativer Form geschrieben sind, wobei das Verhalten, nach dem wir suchen, klar formuliert ist.
Es ist auch möglich, Funktionen zu verspotten, die in Funktionen verwendet werden. Entweder durch Übergabe an die Funktion (das Äquivalent zur Abhängigkeitsinjektion) oder mit Frameworks wie Brian Maricks Midje .
quelle
Ja, die Unit-Tests haben bereits Sinn mit statisch typisiertem Funktionscode. Ein einfaches Beispiel:
Sie können
prop_encode
mit statischen Typ erzwingen .quelle