Warum sind assertEquals () -Parameter in der Reihenfolge (erwartet, tatsächlich)?

76

Warum nehmen so viele assertEquals()oder ähnliche Funktionen den erwarteten Wert als ersten Parameter und den tatsächlichen als zweiten?
Dies scheint mir nicht intuitiv zu sein. Gibt es also einen besonderen Grund für diese ungewöhnliche Reihenfolge?

jor
quelle
8
Aus diesem Grund verwende ich normalerweise Matcher wie assertThat (tatsächlich, ist (erwartet)). Ich finde es so viel einfacher zu lesen
JonathanC
2
Sind Sie sicher, dass das wirklich die Reihenfolge ist? Die Dokumente geben keinen Standard für sich assertEqualselbst an docs.python.org/2/library/… und das Durchsuchen dieser Seite zeigt, dass die Reihenfolge innerhalb des unittest-Moduls selbst inkonsistent ist. Aber dieses Python-Problem impliziert (tatsächlich, erwartet), dass es tatsächlich der Standard ist: bugs.python.org/issue10573
Michael Scheper
1
Beachten Sie auch, dass dies assertEqualsveraltet ist - verwenden Sie assertEqual(obwohl zumindest in 2.7 nicht angegeben wird, welcher Parameter erwartet wird und welcher tatsächlich)
Michael Scheper
2
@warvariuc: Sieht so aus, als wären sie von "erwartet" und "tatsächlich" zu "zuerst" und "zweitens" übergegangen. Ich denke, dies macht Unit-Tests mehrdeutiger und die Testausgabe schwieriger zu verstehen. Irgendeine Idee, warum sie es so geändert haben?
Michael Scheper
4
@warvariuc: Ich habe keine starken Gefühle bezüglich der Reihenfolge von "tatsächlich" und "erwartet", solange es konsistent ist und vor allem die Parameter klar benannt sind. In dem von Ihnen verknüpften Diff scheinen die Parameter jedoch in "first" und "second" umbenannt worden zu sein. Diese sind nicht nur unklar, sondern praktisch bedeutungslos! Mit diesen Namen kann ich nicht sagen, ob in der Fehlermeldung für self.assertEqual(ltuae, 42)42 42 oder 54 erwartet wurde. Wenn ein Test fehlschlägt, soll die Meldung hilfreich und genau sein, damit der Fehler so schnell wie möglich behoben werden kann. Die neuen Parameternamen machen das schwieriger.
Michael Scheper

Antworten:

29

Weil die Autoren eine 50% ige Chance hatten, Ihrer Intuition zu entsprechen.

Wegen der anderen Überlastung

assertWhatever(explanation, expected, actual)

Und die Erklärung, die Teil dessen ist, was Sie wissen, passt zum Erwarteten, was Sie wissen, im Gegensatz zu der tatsächlichen, die Sie zum Zeitpunkt des Schreibens des Codes nicht kennen.

bmargulies
quelle
14
In diesem Fall wird es jedoch leicht inkonsistent, da in den meisten Sprachen die erforderlichen Parameter zuerst und die obligatorischen zuletzt angezeigt werden. Aus diesem Grunde ist natürlich für assertWhatever gehen würde (tatsächlich, erwartete [Erläuterung])
jor
3
Genau, jor! Darüber hinaus ist es in Bedingungen üblicher, if (s == "a") als if ("a" == s) zu schreiben (obwohl es fraglich ist, ob es umgekehrt besser wäre - in Bezug auf die Gemeinsamkeit das erste man gewinnt aber).
Benroth
3
Ich denke nicht, dass es für 50% der Bevölkerung intuitiv ist ... Sie werden nicht sagen, dass die assertGreater(a, b)Behauptung b > aintuitiv ist ... (und ich denke, deshalb haben sie die nicht kommutativen Methoden entfernt)
Boumbh
2
Wenn Sie die Konstante an die erste Stelle setzen, wird ein Fehler angezeigt, wenn Sie versehentlich eine Zuweisung =anstelle einer Äquivalenzprüfung durchgeführt haben, ==da dies zu einem Fehler führen würde, wenn Sie versuchen, die Konstante neu zuzuweisen. (Ich glaube, ich habe es zum ersten Mal beim Schreiben von festem Code beworben gesehen.) Es erstellt (IMHO) weniger lesbaren Code (ich bevorzuge die Grammatik im Stil "Ergebnis sollte wie erwartet sein"), bringt den Compiler jedoch dazu, Ihre Chops zu sprengen, wenn Sie ein = weglassen Zeichen
Chuck van der Linden
13

Die Antwort von Kent Beck , dem Erfinder von SUnit und JUnit (wo möglicherweise diese Konvention ihren Ursprung hat), lautet:

Zeichnen Sie eine Reihe von assertEquals hintereinander. Wenn sie zuerst erwartet haben, lesen sie besser.

Dies ist jedoch so entgegengesetzt zu meiner eigenen Erfahrung, dass ich mich fragen muss, ob ich es falsch verstehe. Folgendes sehe ich oft in Tests:

assertEquals(12345, user.getId());
assertEquals("kent", user.getUsername());
assertEquals("Kent Beck", user.getName());

Ich würde denken, dass dies mit dem tatsächlichen Wert zuerst besser lesen würde . Dadurch wird mehr von der sich wiederholenden Boilerplate zusammengefügt und die Methodenaufrufe ausgerichtet, deren Werte wir testen:

assertEquals(user.getId(), 12345);
assertEquals(user.getUsername(), "kent");
assertEquals(user.getName(), "Kent Beck");

(Und es gibt andere Gründe, warum ich diese Reihenfolge bevorzuge, aber für den Zweck dieser Frage, warum es anders ist, scheint Kents Argumentation die Antwort zu sein, auch wenn ich sie nicht verstanden habe.)

Chris Povirk
quelle
7

Ich stimme dem Konsens zu, dass Konsistenz die Nummer 1 ist, aber das Verhalten beim Vergleichen von Wörterbüchern kann ein hilfreicher Datenpunkt sein, wenn Sie diese Frage bewerten.

Wenn ich ein "+" auf einem Diff sehe, lese ich dies als "das getestete Verfahren hat dies hinzugefügt". Auch hier gelten persönliche Vorlieben.

Hinweis: Ich habe alphabetische Tasten verwendet und das Wörterbuch verlängert, sodass sich zur Verdeutlichung des Beispiels nur eine mittlere Taste ändert. Andere Szenarien zeigen mehr verschleierte Unterschiede. Bemerkenswert ist auch, dass assertEqual assertDictEqual in> = 2.7 und> = 3.1 verwendet

exl.py.

from unittest import TestCase


class DictionaryTest(TestCase):

    def test_assert_order(self):
        self.assertEqual(
            {
                'a_first_key': 'value',
                'key_number_2': 'value',
                'z_last_key': 'value',
                'first_not_second': 'value',
            },
            {
                'a_first_key': 'value',
                'key_number_2': 'value',
                'z_last_key': 'value',
                'second_not_first': 'value',
            }
        )

Ausgabe:

$ python -m unittest exl
F
======================================================================
FAIL: test_assert_order (exl.DictionaryTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "exl.py", line 18, in test_assert_order
    'second_not_first': 'value',
AssertionError: {'a_first_key': 'value', 'z_last_key': 'value', 'key_number_2': 'value', 'first_ [truncated]... != {'a_first_key': 'value', 'z_last_key': 'value', 'key_number_2': 'value', 'second [truncated]...
  {'a_first_key': 'value',
-  'first_not_second': 'value',
   'key_number_2': 'value',
+  'second_not_first': 'value',
   'z_last_key': 'value'}

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)
whp
quelle
6

Dies ist ein sehr spannendes Thema und auch hier viele sehr lehrreiche Antworten! Folgendes lerne ich von ihnen:

  1. Intuitiv / kontraintuitiv kann als subjektiv angesehen werden. Unabhängig von der ursprünglich definierten Reihenfolge wären vielleicht 50% von uns nicht glücklich .

  2. Persönlich hätte ich es vorgezogen, wenn es so konzipiert wäre assertEqual(actual, expected), weil ich angesichts der konzeptionellen Ähnlichkeit zwischen assertund ifwünschte, es würde zum Beispiel der Norm vonif actual == expectif a == 1 folgen .

    (PS: Es ist wahr, dass es unterschiedliche Meinungen gibt, die dazu auffordern, eine if-Anweisung in "umgekehrter Reihenfolge" zu schreiben, dhif(1==a) {...} um Sie davor zu schützen, versehentlich eine zu verpassen =. Aber dieser Stil war selbst im C / weit von der Norm entfernt C ++ Welt. Und wenn Sie zufällig Python-Code schreiben, sind Sie in erster Linie nicht anfällig für diesen bösen Tippfehler, da er if a = 1in Python nicht gültig ist.)

  3. Der praktisch überzeugende Grund dafür assertEqual(expect, actual)ist, dass die unittest Bibliothek in Ihrer Sprache wahrscheinlich bereits dieser Reihenfolge folgt, um eine lesbare Fehlermeldung zu generieren. Zum Beispiel in Python, wenn Sie das tun assertEqual(expected_dictionary, actual_dictionary), wird Unittest mit dem Präfix fehlende Schlüssel in der tatsächlichen Anzeige -und zusätzliche Tasten mit Präfix+ , wie wenn Sie ein tun git diff old_branch new_branch.

    Intuitiv oder nicht, dies ist der überzeugendste Grund, sich an die assertEqual(expected, actual)Bestellung zu halten. Wenn Sie es nicht mögen, akzeptieren Sie es besser noch, denn "Praktikabilität schlägt Reinheit" .

  4. Wenn Sie eine Möglichkeit benötigen, sich an die Bestellung zu erinnern, wird diese AntwortassertEqual(expected_result, actual_calculation) mit der Reihenfolge der Zuweisungsanweisungen verglichen result = calculate(...). Es kann ein guter Weg sein, sich das De-facto-Verhalten zu merken, aber meiner Meinung nach ist es nicht die unbestreitbare Begründung dieser Reihenfolge, die intuitiver ist.

Also los geht's. Glücklich assertEqual(expect, actual)!

RayLuo
quelle
1
Für Punkt 4) macht es genug Spaß, dass die Parameterreihenfolge für die Zuweisung in den frühen Jahren des Rechnens tatsächlich viel diskutiert wurde. Einige CS-Experten waren der Meinung, dass unter Verwendung der Reihenfolge "Wert wird in Wert gespeichert" gelesen werden könnte, was viel weniger Verwirrung mit der mathematischen Gleichheit aufkommen ließ (und natürlich etwas anderes als '=' für die Zuweisung wie eine Art Pfeil wie verwenden '->').
Kriss
5

Ein hintergründiger Zweck von assertEqual()ist es, Code für menschliche Leser zu demonstrieren. Da der einfachste Funktionsaufruf ist result = function(parameters), gewöhnt man sich daran, an den Rückgabewert links und den Aufruf rechts zu denken .

Ein Test, der eine Funktion dokumentiert, zeigt also links ein Literal und rechts einen Aufruf.

assertEqual(15, sum((1,2,3,4,5)))

Das heißt (erwartet, tatsächlich). Ähnliches gilt für einen Ausdruck.

assertEqual(4, 2 + 2)

Wenn Sie gerne aneinanderreihen ( trotz PEP8 ), hilft der erwartete Parameter, links kürzer zu sein:

assertEqual(42, 2 * 3 * 7)
assertEqual(42, (1 << 1) + (1 << 3) + (1 << 5))
assertEqual(42, int('110', int('110', 2)))

Vielen Dank an Andrew Weimholt und Ganesh Parameswaran für diese Formeln.

Bob Stein
quelle
2

Die xUnit-Testkonvention wird erwartet / aktuell. Für viele ist das die natürliche Ordnung, denn das haben sie gelernt.

Interessanterweise gilt in einer Abweichung von der Konvention für ein xUnit-Framework qunit für tatsächlich / erwartet. Zumindest mit Javascript können Sie einfach eine neue Funktion erstellen, die die alte kapselt und ihr die ursprüngliche Variable zuweist:

var qunitEquals = equals;
equals = function(expected, actual, message) {
    qunitEquals(actual, expected, message);
};
Ed Sykes
quelle
-2

Die Erklärung, die ich gehört habe, ist, dass es von TDD kommt.

In Test Driven Development beginnen Sie mit dem Test und schreiben dann den Code.

Das Starten von Behauptungen durch Schreiben der Erwartung und anschließendes Aufrufen des Codes, der sie erzeugen soll, ist eine Mini-Version dieser Denkweise.

Natürlich kann dies nur eine Geschichte sein, die die Leute erzählen. Ich weiß nicht, dass es ein bewusster Grund war.

Lars P.
quelle