Warum wird eine Sprache nicht als unterstützte Funktion auf Syntaxebene getestet?

37

Sie finden eine endlose Liste von Blogs, Artikeln und Websites, die die Vorteile des Unit-Tests Ihres Quellcodes fördern . Es ist fast garantiert, dass die Entwickler, die die Compiler für Java, C ++, C # und andere typisierte Sprachen programmiert haben, Unit-Tests verwendet haben, um ihre Arbeit zu verifizieren.

Warum fehlt dann, trotz seiner Popularität, das Testen in der Syntax dieser Sprachen?

Microsoft hat LINQ für C # eingeführt . Warum konnten sie also keine Tests hinzufügen?

Ich möchte nicht vorhersagen, wie sich diese Sprachänderungen auswirken würden, sondern klären, warum sie zunächst fehlen.

Als Beispiel: Wir wissen, dass Sie eine forSchleife ohne die Syntax der forAnweisung schreiben können . Sie könnten whileoder if/ gotoAnweisungen verwenden. Jemand fand eine forAussage effizienter und führte sie in eine Sprache ein.

Warum haben die Tests nicht dieselbe Entwicklung der Programmiersprachen verfolgt?

Reactgular
quelle
24
Ist es wirklich besser, eine Sprachspezifikation durch Hinzufügen von Syntax komplexer zu gestalten, als eine Sprache mit einfachen Regeln zu entwerfen, die auf einfache Weise allgemein erweitert werden können?
KChaloux
6
Was meinen Sie mit "auf Syntaxebene"? Haben Sie ein Detail oder eine Idee, wie es implementiert werden soll?
SJuan76
21
Könnten Sie ein Pseudocode-Beispiel für das Testen als syntaxgestütztes Feature geben? Ich bin gespannt, wie es Ihrer Meinung nach aussehen würde.
FrustratedWithFormsDesigner
12
@FrustratedWithFormsDesigner Ein Beispiel finden Sie in der D-Sprache .
Doval
4
Eine andere Sprache mit integrierten Unit-Testfunktionen ist Rust.
Sebastian Redl

Antworten:

36

Unit-Tests werden, wie so oft, am besten auf Bibliotheksebene und nicht auf Sprachebene unterstützt. Insbesondere verfügt C # über zahlreiche Unit Testing-Bibliotheken sowie über systemeigene Funktionen wie .NET Framework Microsoft.VisualStudio.TestTools.UnitTesting.

Jede Unit Testing-Bibliothek hat eine etwas andere Testphilosophie und -syntax. Wenn alle Dinge gleich sind, sind mehr Entscheidungen besser als weniger. Wenn Komponententests in die Sprache eingebunden wären, wären Sie entweder an die Auswahl des Sprachdesigners gebunden, oder Sie würden ... eine Bibliothek verwenden und die Sprachtestfunktionen vollständig vermeiden.

Beispiele

  • Nunit - Allzweckframework für idiomatische Komponententests , das die Sprachfunktionen von C # voll ausnutzt.

  • Moq - Verspottendes Framework, das Lambda-Ausdrücke und Ausdrucksbäume ohne Aufnahme- / Wiedergabemetapher voll ausnutzt.

Es gibt viele andere Möglichkeiten. Bibliotheken wie Microsoft Fakes können "Shims ..." - Mocks erstellen, bei denen Sie Ihre Klassen nicht mithilfe von Schnittstellen oder virtuellen Methoden schreiben müssen.

Linq ist kein Sprachfeature (trotz seines Namens)

Linq ist eine Bibliotheksfunktion. Wir haben viele neue Funktionen in der C # -Sprache kostenlos, wie Lambda-Ausdrücke und Erweiterungsmethoden, aber die eigentliche Implementierung von Linq ist in .NET Framework.

Es gibt einige syntaktische Zucker, die C # hinzugefügt wurden, um die Linq-Anweisungen sauberer zu machen, aber dieser Zucker ist nicht erforderlich, um Linq zu verwenden.

Robert Harvey
quelle
1
Ich stimme dem Punkt zu, dass LINQ kein Sprachfeature ist, aber um LINQ zu ermöglichen, mussten einige zugrunde liegende Sprachfeatures vorhanden sein - insbesondere generische und dynamische Typen.
Wyatt Barnett
@WyattBarnett: Ja, und das Gleiche gilt für einfache Einheitentests - wie Reflexion und Attribute.
Doc Brown
8
LINQ brauchte Lambdas und Ausdrucksbäume. Generika befanden sich bereits in C # (und .Net im Allgemeinen, sie sind in der CLR enthalten), und die Dynamik hat nichts mit LINQ zu tun. Sie können LINQ ohne dynamische tun.
Arthur van Leeuwen
Perls DBIx :: Class ist ein Beispiel für eine LINQ-ähnliche Funktionalität, die mehr als 10 Jahre nach der Implementierung aller erforderlichen Funktionen in der Sprache implementiert wurde.
Slebetman
2
@ Carson63000 Ich denke du meinst anonyme Typen in Kombination mit dem varSchlüsselwort :)
M.Mimpen
21

Es gibt viele Gründe. Eric Lippert hat viele Male angegeben, dass der Grund feature Xnicht in C # liegt, weil es einfach nicht in ihrem Budget ist. Sprachdesigner haben weder unendlich viel Zeit noch Geld, um Dinge zu implementieren, und mit jeder neuen Funktion sind Wartungskosten verbunden. Die Sprache so klein wie möglich zu halten, ist nicht nur für die Sprachdesigner einfacher - es ist auch für jeden einfacher, alternative Implementierungen und Tools (z. B. IDEs) zu schreiben Portabilität kostenlos. Wenn Unit-Tests als Bibliothek implementiert werden, müssen Sie sie nur einmal schreiben, und sie funktionieren bei jeder konformen Implementierung der Sprache.

Es ist erwähnenswert, dass D Unterstützung auf Syntaxebene für Komponententests bietet . Ich weiß nicht, warum sie sich dazu entschlossen haben, dies zu tun, aber es ist erwähnenswert, dass D eine "Programmiersprache für Systeme auf hohem Niveau" sein soll. Die Designer wollten, dass es für die Art von unsicherem, einfachem Code, für den C ++ traditionell verwendet wurde, brauchbar ist, und ein Fehler in unsicherem Code ist unglaublich kostspielig - undefiniertes Verhalten. Ich nehme an, es hat für sie Sinn gemacht, zusätzliche Anstrengungen zu unternehmen, um zu überprüfen, ob ein unsicherer Code funktioniert. Beispielsweise können Sie erzwingen, dass nur bestimmte vertrauenswürdige Module unsichere Vorgänge wie ungeprüfte Arrayzugriffe oder Zeigerarithmetik ausführen können.

Die schnelle Entwicklung war auch für sie ein wichtiges Anliegen, sodass D-Code so schnell kompiliert werden konnte, dass er als Skriptsprache verwendet werden konnte. Backeinheitentests direkt in die Sprache, sodass Sie Ihre Tests ausführen können, indem Sie einfach ein zusätzliches Flag an den Compiler übergeben.

Ich denke jedoch, dass eine großartige Unit-Testing-Bibliothek viel mehr kann, als nur einige Methoden zu finden und auszuführen. Nehmen Sie zum Beispiel Haskells QuickCheck , mit dem Sie Dinge wie "für alle x und y f (x, y) == f (y, x)" testen können . QuickCheck ist besser als Unit-Test- Generator zu bezeichnen und ermöglicht das Testen von Dingen auf einer höheren Ebene als "Für diesen Eingang erwarte ich diesen Ausgang". QuickCheck und Linq unterscheiden sich nicht sonderlich - sie sind beide domänenspezifische Sprachen. Anstatt die Unterstützung von Unit-Tests auf eine Sprache zu beschränken, sollten Sie die Funktionen hinzufügen, die erforderlich sind, um DSLs praktisch zu machen. Sie werden nicht nur Unit-Tests erhalten, sondern auch eine bessere Sprache.

Doval
quelle
3
Um in der .Net-Welt zu bleiben, gibt es FsCheck, eine Portierung von QuickCheck zu F #, mit einigen Helfern für die Verwendung von C #.
Arthur van Leeuwen
In der .NET-Welt bleiben? Diese Frage hat nichts mit .NET zu tun.
Miles Rout
@MilesRout C # ist ein Teil von .NET. Die Frage und die Antwort sprechen häufig über C #, und die Frage spricht sogar auch über LINQ.
Zachary Dow
Die Frage ist nicht mit .NET oder C # markiert.
Miles Rout
@MilesRout Das mag stimmen, aber man kann nicht sagen, dass es nichts damit zu tun hat, nur weil es das Tag nicht hat. :)
Zachary Dow
13

Denn das Testen und insbesondere das testgetriebene Entwickeln ist ein zutiefst kontraintuitives Phänomen.

Fast jeder Programmierer beginnt seine Karriere mit der Überzeugung, dass er die Komplexität viel besser beherrscht als sie tatsächlich ist. Die Tatsache, dass selbst der größte Programmierer keine großen und komplexen Programme ohne schwerwiegende Fehler schreiben kann, wenn er nicht viele Regressionstests verwendet, ist für viele Praktiker ernsthaft enttäuschend und sogar beschämend. Daher die vorherrschende Abneigung gegen regelmäßige Tests auch bei Fachleuten, die es schon besser wissen sollten.

Ich denke, die Tatsache, dass religiöse Tests sich langsam mehr durchsetzen und erwartet werden, ist größtenteils auf die Tatsache zurückzuführen, dass mit der Explosion der Speicherkapazität und der Rechenleistung größere Systeme als je zuvor gebaut werden - und dass extrem große Systeme besonders anfällig für Komplexität sind kann nicht ohne das Sicherheitsnetz der Regressionstests verwaltet werden. Infolgedessen geben selbst besonders hartnäckige und getäuschte Entwickler jetzt widerwillig zu, dass sie Tests benötigen und dies auch immer tun müssen (wenn dies wie ein Geständnis bei einem AA-Meeting klingt, ist dies durchaus beabsichtigt - ein Sicherheitsnetz zu benötigen, ist für viele psychologisch schwierig zuzugeben Einzelpersonen).

Die meisten heute populären Sprachen stammen aus der Zeit vor dieser Einstellungsänderung, daher haben sie wenig eingebaute Unterstützung für Tests: Sie haben assert, aber keine Verträge. Ich bin mir ziemlich sicher, dass zukünftige Sprachen, wenn der Trend anhält, mehr Unterstützung für die Sprache als für die Bibliotheksebene haben werden.

Kilian Foth
quelle
3
Sie übertreiben Ihren Fall ein bisschen. Während Unit-Tests zweifelsohne von größter Bedeutung sind, ist die ordnungsgemäße Erstellung von Programmen , die die unnötige Komplexität minimiert, ebenso wichtig, wenn nicht sogar noch wichtiger. Sprachen wie Haskell verfügen über Typensysteme, die die Zuverlässigkeit und Überprüfbarkeit der in ihnen geschriebenen Programme verbessern. Best Practices für Codierung und Design vermeiden viele der Fallstricke von ungetestetem Code und machen Komponententests weniger kritisch als sonst.
Robert Harvey
1
@RobertHarve ... und Unit-Tests sind eine sehr gute Möglichkeit, Entwickler indirekt dazu zu bewegen . Wenn Sie beim Erstellen von Tests an der API arbeiten, anstatt sie mit anderem Code zu verwenden, können Sie diese in die richtige Denkweise bringen, um undichte Abstraktionen oder ungerade Methodenaufrufe einzuschränken oder zu entfernen. Ich glaube nicht, dass KillianFoth etwas übertrieben hat.
Izkata
@ Robert: Das einzige , was ich , dass er übertreibt beleive ist „religiöse Tests mehr Mainstream wird langsam“ Wenn langsam in Bezug auf die geografische Zeitrahmen definiert ist , ist er weg weit wahrscheinlich nicht ..... :)
mattnz
+1 In meinem aktuellen Job bin ich beeindruckt von der Veränderung der Entwicklungseinstellungen durch die neueren Bibliotheken und verfügbaren Technologien. Zum ersten Mal in meiner Karriere lehren mich diese Jungs , einen anspruchsvolleren Entwicklungsprozess zu verwenden, anstatt dass ich mich auf einem Hügel fühle, auf dem der Wind weht. Ich bin damit einverstanden, dass es nur eine Frage der Zeit ist, bis sich diese Einstellungen in der Syntax der Muttersprache niederschlagen.
Rob
1
Entschuldigung, noch ein Gedanke zu Robert Harveys Kommentar. Im Moment sind Typensysteme eine nette Prüfung, aber es ist erstaunlich, dass sie keine Einschränkungen für Typen definieren - sie adressieren die Schnittstelle, schränken aber das korrekte Verhalten nicht ein. (dh 1 + 1 == 3 wird kompiliert, kann aber je nach Implementierung von + auch wahr sein oder nicht). Ich frage mich, ob der nächste Trend aussagekräftigere Typsysteme sein wird, die das kombinieren, was wir jetzt als Komponententests betrachten.
Rob
6

Viele Sprachen unterstützen das Testen. Die Aussagen von C sind Tests, bei denen das Programm fehlschlagen kann. Hier hören die meisten Sprachen auf, aber Eiffel und in jüngerer Zeit Ada 2012 haben Vorvarianten (Dinge, die die Argumente für eine Funktion übergeben müssen) und Nachvarianten (Dinge, die die Ausgabe einer Funktion übergeben muss), wobei Ada die Möglichkeit bietet, auf die anfänglichen Argumente in der zu verweisen postinvariant. Ada 2012 bietet auch Typinvarianten. Wenn eine Methode für eine Ada-Klasse aufgerufen wird, wird die Typinvariante vor der Rückgabe überprüft.

Das ist nicht die Art von vollständigen Tests, die ein gutes Test-Framework für Sie bereitstellen kann, aber es ist eine wichtige Art von Tests, die Sprachen am besten unterstützen können.

prosfilaes
quelle
2
Nachdem ich ein wenig in Eiffel gearbeitet habe und viele Behauptungen verwendet habe, habe ich die Erfahrung gemacht, dass alles, was Sie tun können, was vertraglicher Natur ist, dazu beiträgt, viele Fehler zu finden.
Blrfl
2

Einige Befürworter stark typisierter funktionaler Sprachen würden argumentieren, dass diese Sprachmerkmale die Notwendigkeit von Komponententests verringern oder eliminieren.

Zwei, imho, ein gutes Beispiel dafür ist von F # for Fun and Profit hier und hier

Persönlich glaube ich immer noch an den Wert von Komponententests, aber es gibt einige gültige Punkte. Wenn beispielsweise ein illegaler Zustand im Code nicht darstellbar ist, ist es nicht nur unnötig, einen Komponententest für diesen Fall zu schreiben, sondern auch unmöglich.

Pete
quelle
2

Ich würde argumentieren, dass Sie das Hinzufügen einiger der erforderlichen Funktionen verpasst haben, weil sie nicht wie beim Komponententest hervorgehoben wurden .

Zum Beispiel werden Unit-Tests in C # hauptsächlich durch die Verwendung von Attributen gesteuert. Die Funktion " Benutzerdefinierte Attribute" bietet einen umfangreichen Erweiterungsmechanismus, mit dem Frameworks wie NUnit iterieren und konkurrieren können, beispielsweise mit theoretischen und parametrisierten Tests.

Dies bringt mich zu meinem zweiten Hauptpunkt - wir wissen nicht genug darüber, was eine gute Testbarkeit ausmacht, um es in eine Sprache zu backen. Das Innovationstempo beim Testen ist viel schneller als bei anderen Sprachkonstruktionen. Daher benötigen wir flexible Mechanismen in unseren Sprachen, um die Innovationsfreiheit zu gewährleisten.

Ich bin ein Benutzer, aber kein Fan von TDD - es ist in einigen Fällen sehr hilfreich, insbesondere um Ihr Design-Denken zu verbessern. Es ist nicht unbedingt nützlich für größere Legacy-Systeme - Tests auf höherer Ebene mit guten Daten und Automatisierung werden wahrscheinlich zusammen mit einer starken Code-Inspektionskultur mehr Nutzen bringen .

Andere relevante Lesung:

Andy Dent
quelle