Sind magische Zahlen in Einheitentests akzeptabel, wenn die Zahlen nichts bedeuten?


59

In meinen Unit-Tests gebe ich oft beliebige Werte in meinen Code ein, um zu sehen, was er tut. Wenn ich beispielsweise weiß, dass foo(1, 2, 3)17 zurückgegeben werden soll, könnte ich Folgendes schreiben:

assertEqual(foo(1, 2, 3), 17)

Diese Zahlen sind rein willkürlich und haben keine weiter gefasste Bedeutung (es handelt sich beispielsweise nicht um Randbedingungen, obwohl ich diese auch teste). Ich würde mich bemühen, gute Namen für diese Nummern zu finden, und so etwas zu schreiben const int TWO = 2;ist offensichtlich nicht hilfreich. Ist es in Ordnung, die Tests so zu schreiben, oder sollte ich die Zahlen in Konstanten zerlegen?

In sind alle magischen Zahlen die gleiche erstellt? Wir haben gelernt, dass magische Zahlen in Ordnung sind, wenn die Bedeutung aus dem Kontext ersichtlich ist, aber in diesem Fall haben die Zahlen tatsächlich überhaupt keine Bedeutung.


9
Wenn Sie Werte eingeben und erwarten, dass Sie dieselben Werte wieder lesen können, würde ich sagen, dass magische Zahlen in Ordnung sind. Wenn 1, 2, 3es sich beispielsweise um 3D-Array-Indizes handelt, in denen Sie den Wert zuvor gespeichert haben 17, ist dieser Test meiner Meinung nach fehlerhaft ( vorausgesetzt , Sie haben auch einige negative Tests). Wenn es sich jedoch um das Ergebnis einer Berechnung handelt, sollten Sie sicherstellen, dass jeder, der diesen Test liest, versteht, warum dies foo(1, 2, 3)so ist 17, und magische Zahlen erreichen dieses Ziel wahrscheinlich nicht.
Joe White

24
const int TWO = 2;ist noch schlimmer als nur mit 2. Es entspricht dem Wortlaut der Regel mit der Absicht, ihren Geist zu verletzen.
Agent_L

4
Was ist eine Zahl, die "nichts bedeutet"? Warum sollte es in Ihrem Code sein, wenn es nichts bedeutete?
Tim Grant

6
Sicher. Hinterlassen Sie einen Kommentar vor einer Reihe solcher Tests, z. B. "eine kleine Auswahl von manuell bestimmten Beispielen". Dies ist in Bezug auf Ihre anderen Tests, bei denen es sich eindeutig um Testgrenzen und Ausnahmen handelt, klar.
Davidbak

5
Ihr Beispiel ist irreführend - wenn Ihr Funktionsname wirklich wäre foo, würde es nichts bedeuten, und so die Parameter. Aber in Wirklichkeit bin ich mir ziemlich sicher, dass die Funktion diesen Namen nicht hat und die Parameter keine Namen bar1haben bar2, und bar3. Machen Sie ein realistischeres Beispiel , in dem die Namen haben eine Bedeutung, dann macht es viel mehr Sinn zu diskutieren , wenn die Testdatenwerte einen Namen brauchen, auch.
Doc Brown

Antworten:


81

Wann haben Sie wirklich Zahlen, die überhaupt keine Bedeutung haben?

Wenn die Zahlen eine Bedeutung haben, sollten Sie sie normalerweise lokalen Variablen der Testmethode zuweisen, um den Code lesbarer und selbsterklärender zu machen. Die Namen der Variablen sollten mindestens die Bedeutung der Variablen widerspiegeln, nicht unbedingt ihren Wert.

Beispiel:

const int startBalance = 10000;
const float interestRate = 0.05f;
const int years = 5;

const int expectedEndBalance = 12840;

assertEqual(calculateCompoundInterest(startBalance, interestRate, years),
            expectedEndBalance);

Beachten Sie, dass die erste Variable nicht benannt ist HUNDRED_DOLLARS_ZERO_CENT, sondern startBalanceum die Bedeutung der Variablen zu kennzeichnen, aber nicht, dass ihr Wert in irgendeiner Weise speziell ist.


3
@ Kevin - in welcher Sprache testen Sie? In einigen
Testframeworks können

10
Obwohl ich der Idee zustimme, ist zu beachten, dass diese Vorgehensweise auch neue Fehler verursachen kann, z. B. wenn Sie versehentlich einen Wert wie 0.05feinen extrahieren int. :)
Jeff Bowman

5
+1 - großartiges Zeug. Nur weil es Ihnen egal ist, was ein bestimmter Wert ist, heißt das nicht, dass es nicht immer noch eine magische Zahl ist ...
Robbie Dee

2
@PieterB: AFAIK, das ist die Schuld von C und C ++, die den Begriff einer constVariablen formalisierten .
Steve Jessop

2
Haben Sie Ihre Variablen so benannt wie die benannten Parameter von calculateCompoundInterest? In diesem Fall ist die zusätzliche Eingabe ein Beweis dafür, dass Sie die Dokumentation für die zu testende Funktion gelesen oder zumindest die von Ihrer IDE angegebenen Namen kopiert haben. Ich bin nicht sicher, wie viel dies dem Leser über die Absicht des Codes sagt, aber wenn Sie die Parameter in der falschen Reihenfolge übergeben, können sie zumindest sagen, was beabsichtigt war.
Steve Jessop

20

Wenn Sie willkürliche Zahlen verwenden, nur um zu sehen, was sie tun, suchen Sie wahrscheinlich nach zufällig generierten Testdaten oder eigenschaftsbasierten Tests.

Beispielsweise ist Hypothesis eine coole Python-Bibliothek für diese Art von Tests und basiert auf QuickCheck .

Stellen Sie sich einen normalen Komponententest wie folgt vor:

  1. Richten Sie einige Daten ein.
  2. Führen Sie einige Operationen an den Daten durch.
  3. Behaupten Sie etwas über das Ergebnis.

Mit der Hypothese können Sie Tests schreiben, die stattdessen so aussehen:

  1. Für alle Daten, die einer Spezifikation entsprechen.
  2. Führen Sie einige Operationen an den Daten durch.
  3. Behaupten Sie etwas über das Ergebnis.

Die Idee ist, sich nicht auf Ihre eigenen Werte zu beschränken, sondern zufällige Werte auszuwählen, mit denen überprüft werden kann, ob Ihre Funktion (en) ihren Spezifikationen entsprechen. Ein wichtiger Hinweis ist, dass sich diese Systeme im Allgemeinen alle fehlgeschlagenen Eingaben merken und sicherstellen, dass diese Eingaben in Zukunft immer getestet werden.

Punkt 3 kann für manche Leute verwirrend sein. Dies bedeutet nicht, dass Sie die genaue Antwort angeben - dies ist offensichtlich nicht für willkürliche Eingaben möglich. Stattdessen behaupten Sie etwas über eine Eigenschaft des Ergebnisses. Sie könnten beispielsweise behaupten, dass etwas nach dem Anhängen an eine Liste nicht mehr leer ist oder dass ein selbstausgleichender binärer Suchbaum tatsächlich ausgeglichen ist (unter Verwendung aller Kriterien, die für eine bestimmte Datenstruktur gelten).

Insgesamt ist es wahrscheinlich ziemlich schlecht, selbst willkürliche Zahlen zu wählen - das erhöht nicht wirklich den Wert und verwirrt jeden, der es liest. Es ist gut, eine Menge zufälliger Testdaten automatisch zu generieren und diese effektiv zu nutzen. Die Suche nach einer Hypothese oder einer QuickCheck-ähnlichen Bibliothek für die Sprache Ihrer Wahl ist wahrscheinlich eine bessere Möglichkeit, Ihre Ziele zu erreichen und gleichzeitig für andere verständlich zu bleiben.


11
Bei zufälligen Tests werden möglicherweise schwer reproduzierbare Fehler gefunden, bei zufälligen Tests werden jedoch kaum reproduzierbare Fehler gefunden. Stellen Sie sicher, dass alle Testfehler mit einem bestimmten reproduzierbaren Testfall erfasst werden.
JBRWilkinson

5
Und woher weißt du, dass dein Komponententest nicht fehlerhaft ist, wenn du "etwas über das Ergebnis aussagst" (in diesem Fall berechne neu, was fooberechnet wird) ...? Wenn Sie zu 100% sicher wären, dass Ihr Code die richtige Antwort liefert, würden Sie diesen Code einfach in das Programm einfügen und nicht testen. Wenn nicht, müssen Sie den Test testen, und ich denke, jeder sieht, wohin das führt.

2
Ja, wenn Sie zufällige Eingaben an eine Funktion übergeben, müssen Sie wissen, wie die Ausgabe aussehen würde, um zu behaupten, dass sie ordnungsgemäß funktioniert. Mit festgelegten / ausgewählten Testwerten können Sie diese natürlich von Hand usw. ausarbeiten. Jede automatisierte Methode zur Feststellung, ob das Ergebnis korrekt ist, unterliegt jedoch genau den gleichen Problemen wie die zu testende Funktion. Sie verwenden entweder die Implementierung, die Sie haben (was Sie nicht können, weil Sie testen, ob sie funktioniert), oder Sie schreiben eine neue Implementierung, bei der es genauso wahrscheinlich ist, dass sie fehlerhaft ist ).
Chris

7
@NajibIdrissi - nicht unbedingt. Sie können beispielsweise testen, ob die Anwendung der Umkehrung der zu testenden Operation auf das Ergebnis den Anfangswert ergibt, mit dem Sie begonnen haben. Oder Sie könnten erwartete Invarianten testen (z. B. für alle Zinsberechnungen an dTagen sollte die Berechnung an dTagen + 1 Monat einen bekannten höheren monatlichen Prozentsatz haben) usw.
Jules

12
@Chris - In vielen Fällen ist es einfacher, die Richtigkeit der Prüfergebnisse zu überprüfen, als die Ergebnisse zu generieren. Obwohl dies nicht unter allen Umständen zutrifft , gibt es viele Bereiche, in denen dies der Fall ist. Beispiel: Das Hinzufügen eines Eintrags zu einem ausgeglichenen Binärbaum sollte zu einem neuen Baum führen, der ebenfalls ausgeglichen ist ... einfach zu testen, in der Praxis recht schwierig zu implementieren.
Jules

11

Der Name Ihres Komponententests sollte den größten Teil des Kontexts enthalten. Nicht aus den Werten der Konstanten. Der Name / die Dokumentation für einen Test sollte den entsprechenden Kontext und die Erklärung der im Test enthaltenen magischen Zahlen enthalten.

Wenn dies nicht ausreicht, sollte ein kleiner Teil der Dokumentation in der Lage sein, dies bereitzustellen (ob über den Variablennamen oder eine Dokumentzeichenfolge). Beachten Sie, dass die Funktion selbst Parameter enthält, die hoffentlich aussagekräftige Namen haben. Diese in Ihren Test zu kopieren, um die Argumente zu benennen, ist ziemlich sinnlos.

Und zuletzt, wenn Ihre Unittests so kompliziert sind, dass dies schwierig / unpraktisch ist, haben Sie wahrscheinlich zu komplizierte Funktionen und könnten überlegen, warum dies der Fall ist.

Je schlampiger Sie Tests schreiben, desto schlechter wird Ihr tatsächlicher Code. Wenn Sie der Meinung sind, dass Sie Ihre Testwerte benennen müssen, um den Test klarer zu machen, wird dringend empfohlen, dass Ihre tatsächliche Methode eine bessere Benennung und / oder Dokumentation benötigt. Wenn Sie die Notwendigkeit finden, Konstanten in Tests zu benennen, würde ich untersuchen, warum Sie dies benötigen - wahrscheinlich ist das Problem nicht der Test selbst, sondern die Implementierung


Diese Antwort scheint sich auf die Schwierigkeit zu beziehen, den Zweck eines Tests abzuleiten, wohingegen sich die eigentliche Frage auf magische Zahlen in Methodenparametern bezieht ...
Robbie Dee,

@RobbieDee Der Name / die Dokumentation für einen Test sollte den entsprechenden Kontext und die Erklärung der im Test enthaltenen magischen Zahlen enthalten. Wenn nicht, fügen Sie entweder die Dokumentation hinzu oder benennen Sie den Test um, um die Übersichtlichkeit zu verbessern.
Enderland

Es wäre immer noch besser, den magischen Zahlen Namen zu geben. Sollte sich die Anzahl der Parameter ändern, besteht die Gefahr, dass die Dokumentation nicht mehr aktuell ist.
Robbie Dee

1
@RobbieDee bedenke, dass die Funktion selbst Parameter hat, die hoffentlich aussagekräftige Namen haben. Diese in Ihren Test zu kopieren, um die Argumente zu benennen, ist ziemlich sinnlos.
Enderland

"Hoffentlich" nicht wahr? Warum nicht einfach das Ding richtig codieren und die scheinbar magische Zahl
Robbie Dee

9

Dies hängt stark von der Funktion ab, die Sie testen. Ich kenne viele Fälle, in denen die einzelnen Zahlen für sich genommen keine besondere Bedeutung haben, der Testfall aber als Ganzes durchdacht aufgebaut ist und daher eine besondere Bedeutung hat. Das sollte man irgendwie dokumentieren. Wenn es sich beispielsweise footatsächlich um eine Methode handelt, testForTriangledie entscheidet, ob die drei Zahlen gültige Längen der Kanten eines Dreiecks sind, sehen Ihre Tests möglicherweise folgendermaßen aus:

// standard triangle with area >0
assertEqual(testForTriangle(2, 3, 4), true);

// degenerated triangle, length of two edges match the length of the third
assertEqual(testForTriangle(1, 2, 3), true);  

// no triangle
assertEqual(testForTriangle(1, 2, 4), false); 

// two sides equal
assertEqual(testForTriangle(2, 2, 3), true);

// all three sides equal
assertEqual(testForTriangle(4, 4, 4), true);

// degenerated triangle / point
assertEqual(testForTriangle(0, 0, 0), true);  

und so weiter. Sie können dies verbessern und die Kommentare in einen Nachrichtenparameter umwandeln, der assertEqualangezeigt wird, wenn der Test fehlschlägt. Sie können dies dann weiter verbessern und in einen datengesteuerten Test umgestalten (sofern Ihr Test-Framework dies unterstützt). Trotzdem tun Sie sich selbst einen Gefallen, wenn Sie dem Code einen Vermerk hinzufügen, warum Sie diese Zahlen gewählt haben und welches der verschiedenen Verhaltensweisen Sie im Einzelfall testen.

Natürlich könnten für andere Funktionen die einzelnen Werte für die Parameter von größerer Bedeutung sein. fooDaher ist es wahrscheinlich nicht die beste Idee , einen bedeutungslosen Funktionsnamen zu verwenden, wenn Sie nach dem Umgang mit der Bedeutung von Parametern fragen.


Sinnvolle Lösung.
user1725145

6

Warum wollen wir benannte Konstanten anstelle von Zahlen verwenden?

  1. TROCKEN - Wenn ich den Wert an 3 Stellen benötige, möchte ich ihn nur einmal definieren, damit ich ihn an einer Stelle ändern kann, falls er sich ändert.
  2. Geben Sie Zahlen eine Bedeutung.

Wenn Sie mehrere Komponententests mit jeweils 3 Zahlen (startBalance, interest, years) schreiben, packe ich die Werte einfach als lokale Variablen in den Komponententest. Der kleinste Bereich, in den sie gehören.

testBigInterest()
  var startBalance = 10;
  var interestInPercent = 100
  var years = 2
  assert( calcCreditSum( startBalance, interestInPercent, years ) == 40 )

testSmallInterest()
  var startBalance = 50;
  var interestInPercent = .5
  var years = 1
  assert( calcCreditSum( startBalance, interestInPercent, years ) == 50.25 )

Wenn Sie eine Sprache verwenden, die benannte Parameter erlaubt, ist dies natürlich überflüssig. Dort würde ich einfach die Rohwerte in den Methodenaufruf packen. Ich kann mir kein Refactoring vorstellen, das diese Aussage prägnanter macht:

testBigInterest()
  assert( calcCreditSum( startBalance:       10
                        ,interestInPercent: 100
                        ,years:               2 ) = 40 )

Oder verwenden Sie ein Test-Framework, mit dem Sie die Testfälle in einem Array- oder Map-Format definieren können:

testcases = { {
                Name: "BigInterest"
               ,StartBalance:       10
               ,InterestInPercent: 100
               ,Years:               2
              }
             ,{ 
                Name: "SmallInterest"
               ,StartBalance:       50
               ,InterestInPercent:  .5
               ,Years:               1
              }
            }

3

... aber in diesem Fall haben die Zahlen eigentlich gar keine Bedeutung

Die Zahlen werden verwendet, um eine Methode aufzurufen, so dass die obige Prämisse sicherlich falsch ist. Es ist dir vielleicht egal, wie die Zahlen lauten, aber das ist nicht der springende Punkt. Ja, Sie könnten ableiten, wofür die Zahlen von einem IDE-Assistenten verwendet werden, aber es wäre weitaus besser, wenn Sie den Werten nur Namen geben würden - selbst wenn sie nur mit den Parametern übereinstimmen.


1
Dies muss jedoch nicht unbedingt zutreffen - wie im Beispiel des letzten Komponententests, den ich geschrieben habe ( assertEqual "Returned value" (makeKindInt 42) (runTest "lvalue_operators")). In diesem Beispiel 42handelt es sich nur um einen Platzhalterwert, der vom Code im Testskript mit dem Namen erstellt lvalue_operatorsund dann überprüft wird, wenn er vom Skript zurückgegeben wird. Es hat überhaupt keine Bedeutung, außer dass derselbe Wert an zwei verschiedenen Stellen vorkommt. Was wäre hier ein passender Name, der tatsächlich eine sinnvolle Bedeutung hat?
Jules

3

Wenn Sie eine reine Funktion an einem Satz von Eingängen testen möchten, bei denen es sich nicht um Randbedingungen handelt, möchten Sie sie mit ziemlicher Sicherheit an einer ganzen Reihe von Eingängen testen, bei denen es sich nicht um Randbedingungen handelt (und handelt). Und für mich bedeutet das, dass es eine Wertetabelle geben sollte, mit der die Funktion aufgerufen werden kann, und eine Schleife:

struct test_foo_values {
    int bar;
    int baz;
    int blurf;
    int expected;
};
const struct test_foo_values test_foo_with[] = {
   { 1, 2, 3, 17 },
   { 2, 4, 9, 34 },
   // ... many more here ...
};

for (size_t i = 0; i < ARRAY_SIZE(test_foo_with); i++) {
    const struct test_foo_values *c = test_foo_with[i];
    assertEqual(foo(c->bar, c->baz, c->blurf), c->expected);
}

Mit Tools wie den in Dannnnos Antwort vorgeschlagenen können Sie die zu testende Wertetabelle erstellen . bar,, bazund blurfsollten durch aussagekräftige Namen ersetzt werden, wie in Philipps Antwort beschrieben .

(Allgemeines Prinzip: Zahlen sind nicht immer "magische Zahlen", die Namen benötigen, sondern möglicherweise Daten . Wenn es sinnvoll wäre, Ihre Zahlen in ein Array, vielleicht ein Array von Datensätzen, zu schreiben, dann sind sie wahrscheinlich Daten Wenn Sie dagegen den Verdacht haben, Daten in Ihren Händen zu haben, ziehen Sie in Betracht, diese in ein Array aufzunehmen und weitere Daten zu erfassen.)


1

Tests unterscheiden sich vom Seriencode, und zumindest bei in Spock geschriebenen Komponententests, die kurz und sachlich sind, habe ich keine Probleme mit der Verwendung magischer Konstanten.

Wenn ein Test 5 Zeilen lang ist und dem vorgegebenen / when / then-Schema folgt, würde das Extrahieren solcher Werte in Konstanten den Code nur länger und schwerer lesbar machen. Wenn die Logik lautet "Wenn ich einen Benutzer namens Smith hinzufüge, wird der Benutzer Smith in der Benutzerliste angezeigt", hat es keinen Sinn, "Smith" in eine Konstante zu extrahieren.

Dies gilt natürlich, wenn Sie die Werte, die im Block "given" (setup) verwendet werden, problemlos mit denen in den Blöcken "when" und "then" abgleichen können. Wenn Ihr Testaufbau (im Code) von dem Ort getrennt ist, an dem die Daten verwendet werden, ist es möglicherweise besser, Konstanten zu verwenden. Da die Tests jedoch am besten in sich abgeschlossen sind, befindet sich das Setup in der Regel in der Nähe des Verwendungsorts und der erste Fall trifft zu, was bedeutet, dass magische Konstanten in diesem Fall durchaus akzeptabel sind.


1

Lassen Sie uns zunächst zustimmen, dass „Komponententest“ häufig verwendet wird, um alle automatisierten Tests abzudecken, die ein Programmierer schreibt, und dass es sinnlos ist zu diskutieren, wie jeder Test genannt werden sollte.

Ich habe an einem System gearbeitet, bei dem die Software viele Eingaben machte und eine „Lösung“ ausarbeitete, die einige Einschränkungen erfüllen und gleichzeitig andere Zahlen optimieren musste. Es gab keine richtigen Antworten, daher musste die Software nur eine vernünftige Antwort geben.

Dazu wurden viele Zufallszahlen verwendet, um einen Startpunkt zu erhalten, und anschließend ein „Bergsteiger“, um das Ergebnis zu verbessern. Dies wurde viele Male durchgeführt, um das beste Ergebnis zu erzielen. Ein Zufallszahlengenerator kann gesetzt werden, sodass er immer die gleichen Zahlen in der gleichen Reihenfolge ausgibt. Wenn der Test also einen Startwert festlegt, wissen wir, dass das Ergebnis bei jedem Lauf das gleiche ist.

Wir hatten viele Tests, die das oben Genannte ausführten, und prüften, ob die Ergebnisse gleich waren. Dies zeigte uns, dass wir nicht geändert hatten, was dieser Teil des Systems versehentlich beim Refactoring usw. tat. Es sagte uns nichts über die Richtigkeit von was dieser Teil des Systems tat.

Die Wartung dieser Tests war kostspielig, da jede Änderung des Optimierungscodes die Tests zum Erliegen bringen würde. Sie fanden jedoch auch einige Fehler im viel umfangreicheren Code, der die Daten vorverarbeitet und die Ergebnisse nachverarbeitet hat.

Als wir die Datenbank „verspotteten“, konnten Sie diese Tests als „Unit-Tests“ bezeichnen, aber die „Unit“ war ziemlich groß.

Wenn Sie an einem System ohne Tests arbeiten, führen Sie häufig die oben genannten Schritte aus, damit Sie bestätigen können, dass Ihr Refactoring die Ausgabe nicht ändert. Hoffentlich werden bessere Tests für neuen Code geschrieben!


1

Ich denke, in diesem Fall sollten die Zahlen als willkürliche Zahlen und nicht als magische Zahlen bezeichnet werden. Kommentieren Sie die Zeile einfach als "willkürlichen Testfall".

Sicher, einige magische Zahlen können auch willkürlich sein, wie für eindeutige "Handle" -Werte (die natürlich durch benannte Konstanten ersetzt werden sollten), aber auch vorberechnete Konstanten wie "Fluggeschwindigkeit eines unbeladenen europäischen Sperlings in Furlong pro vierzehn Tage". wobei der numerische Wert ohne Kommentare oder hilfreichen Kontext eingefügt wird.


0

Ich werde es nicht wagen, ein definitives Ja / Nein zu sagen, aber hier sind einige Fragen, die Sie sich stellen sollten, wenn Sie entscheiden, ob es in Ordnung ist oder nicht.

  1. Wenn die Zahlen nichts bedeuten, warum gibt es sie überhaupt? Können sie durch etwas anderes ersetzt werden? Können Sie die Überprüfung auf der Grundlage von Methodenaufrufen und Flows anstelle von Wertbehauptungen durchführen? Stellen Sie sich so etwas wie Mockitos verify()Methode vor, die prüft, ob bestimmte Methodenaufrufe zum Verspotten von Objekten durchgeführt wurden, anstatt tatsächlich einen Wert zu bestätigen.

  2. Wenn die Zahlen haben , sollten sie Variablen zugewiesen werden, die entsprechend benannt sind.

  3. Schreiben Sie die Nummer 2 wie TWOes in bestimmten Kontexten hilfreich sein könnte, und nicht so sehr in anderen Kontexten.

    • Zum Beispiel: assertEquals(TWO, half_of(FOUR))Sinnvoll für jemanden, der den Code liest. Es ist sofort klar was Sie testen.
    • Wenn jedoch Ihr Test ist assertEquals(numCustomersInBank(BANK_1), TWO), dann bedeutet dies nicht machen , dass viel Sinn. Warum nicht BANK_1enthalten zwei Kunden? Wofür testen wir?
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.