Einheitentest statisch getippter Funktionscode

15

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 xanstelle von y(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?

back2dos
quelle
4
Übrigens, wenn Sie QuickCheck noch nicht ausprobiert haben , sollten Sie es auf jeden Fall tun .
Jon Purdy
scalacheck.org ist das Scala-Äquivalent
V-Lamp

Antworten:

8

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?

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.

Ein einfacher Fehler wie die Verwendung von x anstelle von y (beides sind Koordinaten) in Ihrem Code kann jedoch nicht behandelt werden. Ein solcher Fehler könnte jedoch auch beim Schreiben des Testcodes auftreten, sodass ich nicht sicher bin, ob sich der Aufwand lohnt.

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.

Unit-Tests führen zu Redundanz, was bedeutet, dass bei Änderungen der Anforderungen sowohl der Code, der sie implementiert, als auch die Tests, die diesen Code abdecken, 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 typisierte funktionale Programme einen Großteil der Grundeinheitentests abdecken, scheint es sich um einen konstanten Overhead zu handeln, den man einfach ohne Abzüge reduzieren kann.

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.

Doc Brown
quelle
5

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?

Es ist sehr unwahrscheinlich, dass Sie Ihre Spezifikationen vollständig als Typeinschränkungen ausdrücken können.

In welchem ​​Umfang verwenden Sie bei einem solchen Programmierstil Komponententests und warum (welche Qualität erhoffen Sie sich für Ihren Code)?

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.

Alexey Romanov
quelle
1
Ein bisschen detaillierter über QuickCheck: Die Grundidee ist, dass Sie "Eigenschaften" (Invarianten in Ihrem Code) ausschreiben und angeben, wie potenzielle Eingaben generiert werden sollen. Quickcheck erzeugt dann eine Menge zufälliger Eingaben und stellt sicher, dass Ihre Invariante in jedem Fall hält. Es ist gründlicher als Unit-Tests.
Tikhon Jelvis
1

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 .

Lunivore
quelle
0

Ja, die Unit-Tests haben bereits Sinn mit statisch typisiertem Funktionscode. Ein einfaches Beispiel:

prop_encode a = (decode . encode $ a) == a

Sie können prop_encodemit statischen Typ erzwingen .

Zhen
quelle