Frage

Als Programmierer habe ich mich von ganzem Herzen in die TDD-Philosophie gekauft und mich bemüht, umfangreiche Unit-Tests für jeden nicht trivialen Code zu machen, den ich schreibe. Manchmal kann diese Straße schmerzhaft sein (Verhaltensänderungen, die kaskadierende Änderungen mehrerer Unit -Tests; hohe Mengen an Gerüsten erforderlich), aber im Großen und Ganzen lehne ich es ab, ohne Tests zu programmieren, die ich nach jeder Änderung durchführen kann, und mein Code ist viel weniger fehler Ergebnis.

Vor kurzem habe ich mit Haskell gespielt und die Resident Testing Library, QuickCheck. In einer Weise unterscheidet sich QuickCheck auf die Prüfung von Invarianten des Codes, dh bestimmte Eigenschaften, die alle (oder inhaltlichen Teilmengen) von Eingaben halten. Ein kurzes Beispiel: Ein stabiler Sortieralgorithmus sollte die gleiche Antwort geben, wenn wir ihn zweimal ausführen, eine zunehmende Ausgabe haben, sollte eine Permutation der Eingabe usw. sein. Dann generiert QuickCheck eine Vielzahl von Zufallsdaten, um diese Invarianten zu testen.

Es scheint mir, zumindest für reine Funktionen (dh Funktionen ohne Nebenwirkungen-und wenn Sie sich richtig verspotteten . Jeder Unit -Test besteht aus einem Eingang und einer Ausgabe (in imperativen Programmiersprachen ist die "Ausgabe" nicht nur die Rückkehr der Funktion, sondern auch jeden geänderten Zustand, sondern dies kann eingekapselt werden). Man könnte möglicherweise einen zufälligen Eingangsgenerator erstellen, der gut genug ist, um alle Eingänge der Unit -Tests abzudecken, die Sie manuell erstellt hätten (und noch einige, weil es Fälle erzeugen würde, an die Sie nicht gedacht hätten). Wenn Sie aufgrund einer Randbedingung einen Fehler in Ihrem Programm finden, verbessern Sie Ihren zufälligen Eingabergenerator, damit er auch diesen Fall generiert.

Die Herausforderung besteht also darin, ob es möglich ist, nützliche Invarianten für jedes Problem zu formulieren. Ich würde sagen, es ist: Es ist viel einfacher, wenn Sie eine Antwort haben, um zu sehen, ob es richtig ist, als die Antwort überhaupt zu berechnen. Das Nachdenken über Invarianten hilft auch dabei, die Spezifikation eines komplexen Algorithmus viel besser zu klären als Ad-hoc-Testfälle, die eine Art von Fall zu Fall fördern, wenn das Problem an das Problem nachdenkt. Sie können eine frühere Version Ihres Programms als Modellimplementierung oder eine Version eines Programms in einer anderen Sprache verwenden. Usw. Schließlich können Sie alle Ihre früheren Testbewohner abdecken, ohne eine Eingabe oder eine Ausgabe explizit codieren zu müssen.

Habe ich verrückt geworden oder bin ich auf etwas?

War es hilfreich?

Lösung

Ein Jahr später denke ich jetzt, dass ich eine Antwort auf diese Frage habe: Nein! Insbesondere sind Unit -Tests für Regressionstests immer notwendig und nützlich, bei denen ein Test an einen Fehlerbericht beigefügt ist und in der Codebasis weiterlebt, um zu verhindern, dass dieser Fehler jemals zurückkommt.

Ich vermute jedoch, dass jeder Unit -Test durch einen Test ersetzt werden kann, dessen Eingaben zufällig generiert werden. Selbst bei imperativen Code ist die „Eingabe“ die Reihenfolge der imperativen Aussagen, die Sie machen müssen. Natürlich ist es eine andere Frage, ob es sich lohnt, den Zufallsdatengenerator zu erstellen, und ob Sie den Zufallsdatengenerator die richtige Verteilung haben können oder nicht. Unit -Tests sind einfach ein degenerierter Fall, in dem der Zufallsgenerator immer dasselbe Ergebnis ergibt.

Andere Tipps

Was Sie aufgebracht haben, ist ein sehr guter Punkt - wenn Sie nur auf funktionale Programmierung angewendet werden. Sie haben ein Mittel ausgegeben, um dies mit imperativen Code zu erreichen, aber Sie haben auch angesprochen, warum es nicht getan ist - es ist nicht besonders einfach.

Ich denke, das ist genau der Grund, warum es die Unit -Tests nicht ersetzt: Es passt nicht so einfach für den imperativen Code.

Doubtful

I've only heard of (not used) these kinds of tests, but I see two potential issues. I would love to have comments about each.

Misleading results

I've heard of tests like:

  • reverse(reverse(list)) should equal list
  • unzip(zip(data)) should equal data

It would be great to know that these hold true for a wide range of inputs. But both these tests would pass if the functions just return their input.

It seems to me that you'd want to verify that, eg, reverse([1 2 3]) equals [3 2 1] to prove correct behavior in at least one case, then add some testing with random data.

Test complexity

An invariant test that fully describes the relationship between the input and output might be more complex than the function itself. If it's complex, it could be buggy, but you don't have tests for your tests.

A good unit test, by contrast, is too simple to screw up or misunderstand as a reader. Only a typo could create a bug in "expect reverse([1 2 3]) to equal [3 2 1]".

What you wrote in your original post, reminded me of this problem, which is an open question as to what the loop invariant is to prove the loop correct...

anyways, i am not sure how much you have read in formal spec, but you are heading down that line of thought. david gries's book is one the classics on the subject, I still haven't mastered the concept well enough to use it rapidly in my day to day programming. the usual response to formal spec is, its hard and complicated, and only worth the effort if you are working on safety critical systems. but i think there are back of envelope techniques similar to what quickcheck exposes that can be used.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top