Arten oder Tests: Warum Nicht Beide?

0
25

Jedes jetzt und dann, eine Debatte entbrennt über den Wert der eingegebenen JavaScript. “Schreiben Sie einfach mehr tests!” Schreien einige Gegner. “Ersetzen Sie unit-tests mit Typen!” Schreien die anderen. Beide haben Recht, in gewisser Weise, und falsch in anderen. bietet wenig Raum für Nuancen. Aber in dem Raum von diesem Artikel können wir versuchen zu legen, eine mit Gründen versehene argument für, wie beide können und sollten nebeneinander existieren.

Richtigkeit: das, was wir alle wirklich wollen

Es ist am besten am Ende beginnen. Was wir wirklich wollen, aus dem alle diese meta-engineering am Ende ist die Richtigkeit. Ich meine nicht die strenge theoretische informatik definition, sondern eine allgemeinere Einhaltung des Programm-Verhalten zu den Spezifikationen: Wir haben eine Idee, wie unser Programm sollte für die Arbeit in unseren Köpfen, und der Prozess der Programmierung organisiert bits und bytes zu machen, die Idee in die Realität umzusetzen. Denn wir sind nicht immer präzise über das, was wir wollen, und weil wir möchten Vertrauen, dass unser Programm nicht zu brechen, wenn wir eine änderung vorgenommen, wir schreiben, Typen und tests auf der Oberseite des raw-code, den wir bereits haben zu schreiben, nur um Dinge zu machen, die Arbeit in den ersten Platz.

Also, wenn wir akzeptieren, dass die Richtigkeit ist, was wir wollen, und Typen und Prüfungen sind nur automatisierte Weise zu erhalten, es wäre toll, um ein visuelles Modell, wie Typen und tests, die uns helfen, die Richtigkeit, und daher verstehen, wo Sie sich überlappen und wo Sie sich ergänzen.

Ein visuelles Modell der Programm-Korrektheit

Wenn wir uns vorstellen, die gesamte unendliche Turing-kompletten möglichen Platz für alles, was die Programme können je möglicherweise zu tun — inklusive der Fehler — als eine riesige graue Fläche, dann ist das, was wir wollen, unser Programm, unsere Spezifikation, ist ein sehr, sehr, sehr kleine Teilmenge der möglichen Platz (dem grünen Diamanten unten übertrieben in der Größe für den Willen, etwas zu zeigen):

Unsere Aufgabe in der Programmierung ist zu streiten, unser Programm so nah an der Spezifikation, wie möglich (Sie wusste natürlich, wir sind unvollkommen, und unsere Ausstattung ist ständig in Bewegung, z.B. durch menschliche Fehler, neue Funktionen oder unter-spezifizierte Verhalten; so dass wir nie ganz gelingt, den genauen überschneidungen):

Beachten Sie nochmals, dass die Grenzen unseres Verhalten gehören auch geplante und ungeplante Fehler für die Zwecke unserer Diskussion hier. Unsere Bedeutung von “Richtigkeit” umfasst geplante Fehler, aber nicht gehören ungeplante Fehler.

Tests und Richtigkeit

Wir schreiben tests, um sicherzustellen, dass unser Programm passt unsere Erwartungen, haben aber eine Reihe von Möglichkeiten Dinge zu testen:

Der ideale Test sind die orangenen Punkte in der Diagramm — Sie genau zu testen, dass unser Programm keine überschneidung der spec. In dieser Darstellung, die wir nicht wirklich unterscheiden zwischen verschiedenen Arten von tests, aber man könnte sich vorstellen, unit-tests als wirklich kleine Punkte, während integration/end-to-end-tests sind große Punkte. So oder so, Sie sind das Punkte, weil niemand den test vollständig beschreibt jeder Pfad durch ein Programm. (In der Tat, Sie können 100% code coverage und noch nicht testen jeden Weg wegen der kombinatorische explosion!)

Der Blaue Punkt in diesem Diagramm ist ein schlechter test. Sicher, die tests, dass unser Programm funktioniert, aber es hat nicht wirklich pin es auf der zugrunde liegenden Spezifikation (was wir wirklich wollen, aus unserem Programm, am Ende des Tages). Der moment, in dem wir fix unser Programm ausrichten, näher zu spezifizieren, wird dieser test bricht, was uns ein false positive ist.

Der lila Punkt ist ein wertvoller test, weil es tests, wie wir denken unser Programm arbeiten soll, und identifiziert einen Bereich, in dem unsere Programm derzeit nicht. Was mit lila-tests und Festlegung des Programms in der Umsetzung entsprechend ist auch bekannt als Test-Driven Development.

Die rot-test in diesem Diagramm ist eine seltene test. Anstelle der normalen (orange) tests, test – “happy paths” (einschließlich der geplanten error-Staaten), dies ist ein test, der erwartet und überprüft, dass die “unglücklichen ” Pfade” scheitern. Wenn dieser test “bestanden” hat, wo es sollte “scheitern”, das ist ein riesiger frühen Warnzeichen, dass etwas falsch gelaufen ist — aber es ist im Grunde unmöglich zu schreiben genügend tests decken die weite des möglichen unglücklich Pfade, die sich außerhalb der grünen spec Bereich. Menschen nur selten finden, der Wert der Prüfung, die Dinge, die nicht funktionieren sollte, nicht funktioniert, so tun Sie das nicht; aber es kann noch eine hilfreiche Frühwarn-Zeichen, wenn Dinge schief gehen.

Typen und Richtigkeit

Wo tests sind einzelne Punkte auf der Möglichkeit Raum, was unser Programm tun können, Typen repräsentieren Kategorien carving ganze Abschnitte aus dem gesamten Raum möglich. Wir visualisieren diese als Rechtecke:

Wir Holen ein Rechteck, um den Kontrast, die diamond darstellt, das Programm, weil kein Typ-system allein vollständig zu beschreiben, unser Verhalten des Programms anhand der Typen allein. (Wählen Sie ein triviales Beispiel dieser, eine id sollte immer eine positive ganze Zahl ist eine Zahl geben, aber die Anzahl Typ akzeptiert auch Brüche und negative zahlen. Es gibt keine Möglichkeit zu verhindern, dass eine Nummer geben, um einen bestimmten Bereich, über eine sehr einfache union der Anzahl Literale.)

Typen dienen als ein Hindernis auf, wo unser Programm können gehen, wie Sie code. Wenn unser Programm beginnt überschreiten der angegebenen Grenzen Ihrer Programm-Typen, die unserem Typ-checker (wie TypeScript oder Flow) wird Sie weigern sich einfach, um uns zu kompilieren unser Programm. Das ist schön, denn in einer dynamischen Sprache wie JavaScript ist es sehr leicht versehentlich erstellen ein Programm abstürzt, das war sicherlich nicht etwas, was Sie soll. Der einfachste Mehrwert ist die automatisierte null-Prüfung. Wenn foo hat keine Methode namens “bar”, dann ruft foo.bar() bewirken, dass die allzu vertraute undefined ist nicht eine Funktion Laufzeit-exception. Wenn foo eingegeben wurden, an alle, dies könnte gefangen worden, die von der type-checker während des Schreibens, mit bestimmten Zuordnung zu den problematischen code-Zeile (mit autocomplete als begleitende nutzen). Das ist etwas, tests einfach nicht tun.

Wir könnten schreiben wollen, strengen Typen für unser Programm, als wenn wir versuchen, schreiben Sie das kleinstmögliche Rechteck, das passt noch unsere spec. Jedoch, dieser hat eine Lernkurve, weil die volle Nutzung von Systemen des Typs beinhaltet das erlernen einer ganz neuen syntax und Grammatik der Betreiber und der generische Typ Logik, die nötig ist, um das Modell der vollständigen dynamischen Bereich von JavaScript. Handbücher und Cheatsheets helfen, senken Sie diese Lernkurve, und mehr Investitionen hier nötig ist.

Glücklicherweise ist diese Annahme/Lernkurve nicht haben, uns zu stoppen. Da Typ-checking ist eine opt-in-Verfahren mit Durchfluss-und konfigurierbar strenge mit Typoskript (mit der Fähigkeit, selektiv zu ignorieren, lästige code-Zeilen), haben wir unsere Wahl aus einem Spektrum von Typ-Sicherheit. Wir können sogar Modell, das, auch:

Größere Rechtecke, wie der große rote in der obigen Tabelle stellen eine sehr freizügige Annahme einer Typ-system auf der Codebasis, so dass beispielsweise implicitAny und voll Vertrauen auf Typ-Inferenz, bloß um Sie zu beschränken unser Programm aus den schlimmsten unserer Codierung.

Moderate strenge (wie die mittlere grünes Rechteck), könnte ein treuer eingeben, aber mit viel Notausstiege, wie die Verwendung von expliziten Instanzen über die gesamte Codebasis und manuelle Art Behauptungen. Noch ist die mögliche Fläche der gültigen Programme, die nicht mit unserer Skillung ist Massiv reduziert, auch mit diesem Licht Tipparbeit.

Maximale strenge, wie Sie das lila Rechteck, hält Dinge, die so eng an unserer Skillung, dass es manchmal findet, die Teile des Programms, die nicht passen (und diese sind oft ungeplante Fehler in Ihrem Programm zu Verhalten). Finden Fehler in ein vorhandenes Programm wie dieses ist eine sehr verbreitete Geschichte von teams, die Umwandlung Vanille JavaScript codebase. Allerdings wird immer die maximale Sicherheit geben aus unserer Typ-checker wahrscheinlich beinhaltet unter Ausnutzung der generischen Typen und spezielle Operatoren entwickelt, zu verfeinern und eingrenzen der möglichen Raum-Typen für jede variable und Funktion.

Beachten Sie, dass wir nicht technisch schreiben, unser Programm zum ersten mal vor dem schreiben der Arten. Nachdem alle, wir wollen nur unsere Arten zu eng-Modell unserer spec, so wirklich können wir schreiben unsere Arten den ersten und dann den Abgleich der Umsetzung später. In der Theorie wäre dies Typ-Driven Development; in der Praxis werden nur wenige Menschen tatsächlich so entwickeln, da Arten innig durchdringen und verschränken mit unserem eigentlichen Programm-code.

Setzen Sie zusammen

Was wir letztendlich bauen, ist eine intuitive Visualisierung, wie die beiden Typen und tests ergänzen, die unseren Programm – Korrektheit.

Unsere Tests behaupten, dass unser Programm, das speziell führt-wie beabsichtigt-in der select-key-Pfade verwenden (obwohl es bestimmte andere Variationen von tests, wie oben diskutiert, die überwiegende Mehrheit der tests, die dies tun). In der Sprache der Visualisierung, die wir entwickelt haben, Sie “pin”, die dark green diamond in unserem Programm, um das Licht grün Diamant unserer spec. Jede Bewegung, die sich von unserem Programm bricht diese tests, das macht Sie zu krächzen. Das ist ausgezeichnet! Tests sind auch unendlich flexibel und konfigurierbar für die meisten gängigen Anwendungsfälle.

Unsere Typen, die behaupten, dass unser Programm nicht laufen Weg von uns, durch das Verbot einen möglichen Ausfall-Modi, über eine Grenze, die wir ziehen, hoffentlich so fest wie möglich rund um unsere Spez. In der Sprache der Visualisierung, Sie “enthalten” sind mögliche drift von unserem Programm Weg von unserem Skillung (wir sind immer unvollkommen, und jeder Fehler, den wir machen, fügt zusätzliche Ausfall-Verhalten zu unserem Programm). Typen sind auch stumpf, aber mächtig (weil der Typ-Inferenz und-editor-Werkzeug) Werkzeuge, profitieren Sie von einer starken Gemeinschaft liefert Typen, die Sie nicht haben, um zu schreiben, von Grund auf.

In kurz:

  • Tests sind am besten zu gewährleisten, glücklich Pfaden arbeiten.
  • Typen sind am besten bei der Verhinderung unglücklich Pfade aus den bestehenden.

Verwenden Sie Sie zusammen, basierend auf Ihren stärken, für beste Ergebnisse!

Wenn Sie möchten, um mehr darüber zu Lesen, wie Typen und Tests überschneiden, Gary Bernhardt hervorragenden Vortrag über Grenzen und Kent C. Dodds’ Testen Trophy wurden signifikante Einflüsse in meinem denken für diesen Artikel.