Jest: Für Menschen lesbarer Kontext für Erwartungen

Erstellt am 21. Okt. 2016  ·  76Kommentare  ·  Quelle: facebook/jest

Wenn in einem einzigen it mehrere Erwartungen vorhanden sind, scheint es derzeit unmöglich zu sein, herauszufinden, welche Erwartung tatsächlich fehlgeschlagen ist, ohne den Fehler mit Zeilennummern in Ihrem Code zu vergleichen.

test('api works', () => {
    expect(api()).toEqual([]) // api without magic provides no items
    expect(api(0)).toEqual([]) // api with zero magic also provides no items
    expect(api(true)).toEqual([1,2,3]) // api with magic enabled provides all items
})

Welche Erwartung ist gescheitert? Das erste oder das zweite?

image

Es wäre schön, wenn es einen für Menschen lesbaren Kontext gäbe, der sofort klar macht, welche Erwartung fehlgeschlagen ist und was die Erwartungsausgabe tatsächlich in menschlicher Hinsicht bedeutet, ohne die Zeilennummer am Anfang des Stack-Trace finden und diese zurück auf die zuordnen zu müssen Code.


Vergleichen Sie das tape -Äquivalent unten. Ignorieren Sie, dass das Band nach dem ersten Behauptungsfehler nicht aussteigt. tape gibt über jedem Erwartungsfehler eine für Menschen lesbare Nachricht aus, sodass Sie genau wissen, welcher Test fehlgeschlagen ist, ohne zur Testdatei zurückkehren zu müssen.

Beachten Sie, dass dies auch das für Menschen lesbare Rauschen an das Zeilenende in der Testquelle verschiebt, wo Sie sowieso einen Kommentar schreiben könnten.

test('api works', t => {
  t.deepEquals(api(), [], 'api without magic provides no items')
  t.deepEquals(api(0), [], 'api with zero magic also provides no items')
  t.deepEquals(api(true), [1,2,3], 'api with magic enabled provides all items')
})

image


Es scheint, dass die einzige Möglichkeit, menschenlesbare Informationen an Fehler mit jest anzuhängen, darin besteht, alles in ein zusätzliches it zu packen, was meiner Meinung nach unnötig ausführlich ist.

describe('api works', () => {
  test('api without magic provides no items', () => {
    expect(api()).toEqual([])
  })
  test('api with zero magic also provides no items', () => {
    expect(api(0)).toEqual([])
  })
  test('api with magic enabled provides all items', () => {
    expect(api(true)).toEqual([1,2,3])
  })
})

Idealerweise könnte man irgendwie einen menschenlesbaren Kontext an das Ende von expect anhängen.

z.B

Kontextnachricht als zusätzlicher optionaler Parameter für Assertion-Methoden:

test('api works', () => {
    expect(api()).toEqual([], 'api without magic provides no items')
    expect(api(0)).toEqual([], 'api with zero magic provides no items')
    expect(api(true)).toEqual([1,2,3], 'api with magic enabled provides all items')
})


Oder Kontextnachricht als verkettete .because oder .why oder .comment oder .t oder so etwas:

test('api works', () => {
    expect(api()).toEqual([]).because('api without magic provides no items')
    expect(api(0)).toEqual([]).because('api with zero magic provides no items')
    expect(api(true)).toEqual([1,2,3]).because('api with magic enabled provides all items')
})

Alternativ wäre es vielleicht noch besser, wenn jest einfach die Datei lesen und die eigentliche Quellcodezeile drucken könnte, auf der die Erwartung selbst steht.

Hilfreichster Kommentar

Aus dieser Diskussion und diesem Repository denke ich, wäre eine nette und semantische:

it('has all the methods', () => {
  since('cookie is a method').expect(reply.cookie).toBeDefined();
  since('download is a method').expect(reply.download).toBeDefined();
  since('end is a method').expect(reply.end).toBeDefined();
  // ...
});

Die Verwendung ist ähnlich wie bei because , macht aber semantisch mehr Sinn.

Wenn Ihnen das gefällt, kann ich vielleicht eine PR ausarbeiten, die die since -Funktionalität hinzufügt.

Alle 76 Kommentare

Hallo! Wir hatten dies also tatsächlich in Jasmine, stellten aber fest, dass bei Tausenden von Testdateien bei FB niemand es verwendet hat. Also drucken wir vorerst eine nette Fehlermeldung mit ungefähren Informationen und einem Stack-Trace, der zur Erwartung führt (genau wie in Ihrem Screenshot). Ich stimme zu, dass wir die Zeile drucken könnten, die auslöst, aber ziemlich oft ist die Behauptung mehrere Zeilen lang:

expect(a).toEqual({
  …
});

Das würde also nicht wirklich gut aussehen und wir müssten einen Parser verwenden, um das JS zu parsen und die relevanten Informationen zu extrahieren (und lange Zeilen zu reduzieren) oder etwas Ähnliches, um es hübsch zu machen.

Persönlich denke ich, dass wir vorerst genug Informationen zeigen, aber gerne noch einmal darüber nachdenken. Wenn Sie Ideen für etwas haben, das nicht sehr komplex ist, aber mehr Kontext hinzufügt, wodurch Probleme schneller gelöst werden können, lassen Sie es mich wissen.

Wir hatten dies tatsächlich früher in Jasmine, fanden aber heraus, dass über Tausende von Testdateien bei FB niemand es benutzte

@cpojer , also besteht das Muster darin, jede Behauptung in ein it einzuschließen? und/oder einfach auf die Zeilennummern vertrauen?

Ist es möglich, dass dieses Muster weniger übernommen wurde, weil es besser oder schlechter ist, sondern mehr nur aus Gründen der Übereinstimmung mit den bestehenden Tests? oder vielleicht nicht wissend, dass die Funktion existiert? Ich wusste nicht, dass das in Jasmin ist.

Ich stimme zu, dass wir die Zeile drucken könnten, die wirft, aber ziemlich oft ist die Behauptung mehrere Zeilen lang

Das Umgestalten auf eine einzelne Zeile könnte mehr semantische Informationen in der Behauptung fördern? Womöglich?

const adminUser = {
  …
}
expect(a).toEqual(adminUser);

Persönlich denke ich, dass wir vorerst genug Informationen zeigen, aber gerne noch einmal darüber nachdenken

Das obige Beispiel zeigt, dass es schwierig ist, genau herauszufinden, welche Assertion fehlgeschlagen ist, es sei denn, Sie fügen verbose (IMO) Wrapper um alles hinzu. Dies gilt insbesondere in einer transpilierten Umgebung, in der Quellzuordnungszeilennummern nicht immer genau sind. Ich glaube, dass schnelles und genaues Verstehen brach und wo wichtig ist, ebenso wie prägnante Tests.

Wenn Sie Ideen für etwas haben, das nicht sehr komplex ist, aber mehr Kontext hinzufügt, wodurch Probleme schneller gelöst werden können, lassen Sie es mich wissen.

Ich habe oben ein paar Vorschläge gemacht:

Suchen Sie etwas Einfacheres oder Anderes?

Hallo @timoxley! Wir haben bereits darüber nachgedacht, so etwas hinzuzufügen.

Das Problem bei der ersten Option ist also, dass einige Matcher optionale Argumente haben, und das macht die Sache komplizierter.

zB hier im zweiten Fall werden wir nicht wissen, ob das Argument eine Näherung oder eine Fehlermeldung ist

expect(555).toBeCloseTo(111, 2, 'reason why');
expect(555).toBeCloseTo(111, 'reason why');

Der zweite Vorschlag funktioniert nicht, weil der Matcher wirft, sobald etwas nicht den Erwartungen entspricht

expect(1).toBe(2)/* will throw here */.because('reason');

Wir könnten den Grund anhängen, bevor der Matcher ausgeführt wird, wie folgt:

expect(1).because('reason').toBe(2);
// or 
because('reason').expect(1).toBe(2);

aber diese API sieht nicht wirklich gut aus.

Eine andere Möglichkeit wäre, expect ein zweites Argument hinzuzufügen

expect(1, 'just because').toBe(2);

aber es ist so ziemlich das gleiche wie die vorherige Option.

Der Grund, warum ich denke, dass dies nicht sehr nützlich ist, liegt darin, dass Ingenieure keine Zeit mit dem Schreiben von Tests verschwenden wollen. Alles, was wir tun, um es ihnen schwerer zu machen, führt nur zu schlechteren Tests.

In der Vergangenheit bestand die beste Lösung tatsächlich darin, benutzerdefinierte Matcher zu erstellen. Wir werden expect.extend in der nächsten Version von Jest einführen und es wird Ihnen ermöglichen, auf einfache Weise Matcher zu erstellen wie:

expect(a).toEqualMySpecificThing(…)

was es Ihnen ermöglichen sollte, aussagekräftigere Fehlermeldungen zu schreiben. Ich habe gesehen, dass dies häufig in Projekten wie Relay verwendet wird. Sehen Sie sich alle Matcher an: https://github.com/facebook/relay/blob/master/src/tools/__mocks__/RelayTestUtils.js#L281

Wegen Inaktivität geschlossen, aber gerne wieder geöffnet, wenn es gute Ideen gibt.

@cpojer @dmitriiabramov entschuldigt die Verzögerung.

Ein zweites Argument zu expect oder das Verketten eines Grundes mit .because wäre großartig. Was muss getan werden, damit dies geschieht oder nicht?

Ingenieure wollen keine Zeit mit dem Schreiben von Tests verschwenden

@cpojer Einverstanden! Abgesehen davon, dass ich keine Zeit mit dem Debuggen von Tests verschwenden möchte, glaube ich genau aus diesem Grund, dass eine weniger ausführliche API mit mehr Fehlerkontext vorzuziehen wäre.

Für einige konkrete Zahlen, die das einfache Beispiel aus meinem obigen Kommentar verwenden, um äquivalente Behauptungen + Kontext mit Band zu erhalten, verlangt Jest, dass der Programmierer fast die doppelte Menge an zeremoniellem Boilerplate schreibt:

  • 1,8x die Zeilen (6 vs. 11)
  • 2x die Einkerbung (1 vs 2)
  • 2x die Parens/Curlies (24 vs 48) !

Dies könnte verbessert werden!

// tape
test('api works', t => {
  t.deepEquals(api(), [], 'api without magic provides no items')
  t.deepEquals(api(0), [], 'api with zero magic also provides no items')
  t.deepEquals(api(true), [1,2,3], 'api with magic enabled provides all items')
  t.end()
})

// jest
describe('api works', () => {
  test('api without magic provides no items', () => {
    expect(api()).toEqual([])
  })
  test('api with zero magic also provides no items', () => {
    expect(api(0)).toEqual([])
  })
  test('api with magic enabled provides all items', () => {
    expect(api(true)).toEqual([1,2,3])
  })
})

Update : Ich nehme an, Sie könnten die Scherztests in einer einzigen Zeile mit Pfeilen schreiben:

// jest
describe('api works', () => {
  test('api without magic provides no items', () => expect(api()).toEqual([]))
  test('api with zero magic also provides no items', () => expect(api(0)).toEqual([]))
  test('api with magic enabled provides all items', () => expect(api(true)).toEqual([1,2,3]))
})

Dies führt zu einigen längeren Zeilen, verbessert aber die Statistiken, die wir zuvor verglichen haben, etwas:

  • 0,8x die Linien (6 vs 5)
  • 1x die Einkerbung (1 vs 1)
  • 1,75x die Klammern/Curlies (24 vs. 42)

Ich denke jedoch, dass die Testbeschreibung am Anfang der Zeile ohne Zeilenumbruch die visuelle Analyse der Logik erschwert, da das "Fleisch" des Tests, dh die tatsächlichen Behauptungen, an einer beliebigen Spaltenposition platziert wird.

Das Parsen des Codes ist wichtiger als das Lesen der Testbeschreibung, die im Grunde nur ein verherrlichter Kommentar ist. Deshalb schreibt niemand Kommentare an den Anfang der Zeile .zB wäre dies sadomasochistischer Wahnsinn:

/* api without magic provides no items */ expect(api()).toEqual([])
/* api with zero magic also provides no items */ expect(api(0)).toEqual([])
/* api with magic enabled provides all items */ expect(api(true)).toEqual([1,2,3])

Im Idealfall würde der gesamte Assertion-Code sauber in derselben Spalte angeordnet sein, sodass er von einem Menschen leicht analysiert werden kann. Basierend auf dieser Überlegung würde ich mich nachdrücklich für die nachgestellte .because -Form entscheiden und nicht für den alternativen Vorschlag eines zweiten Arguments für expect .

Danke, dass du das Gespräch am Laufen hältst. Beachten Sie, dass Sie auch den Beschreibungsblock nicht benötigen, was die Dinge weiter verkleinert. .because funktioniert leider nicht, denn wenn der Matcher wirft (was passiert, bevor .because aufgerufen wird), haben wir keine Möglichkeit, den Namen zu extrahieren.

Verwenden Sie dann das letzte Argument jeder Matcher-Funktion?

Es würde nur funktionieren, wenn wir es als zweites Argument von expect hinzufügen.
Wir können es aufgrund von Mehrdeutigkeiten nicht als letztes Argument für jede Matcher-Funktion hinzufügen
z.B

expect(obj).toHaveProperty('a.b.c', 'is that a reason or a value of the property?');

Wegen Inaktivität geschlossen, aber gerne wieder geöffnet, wenn es gute Ideen gibt.

@cpojer es ist unklar, ob der Weg von Jasmin (und anderen) von expect(value).toBe(something, 'because message') ausgeschlossen wurde?

Ein Beispiel ist das Testen von Redux-Saga/Redux-Observable-Sachen, bei denen Sie eine Zustandsmaschine testen . Es ist sehr hilfreich, eine beschreibende Meldung darüber zu haben, in welchem ​​​​Zustand es fehlgeschlagen ist. Dieses Beispiel ist erfunden, die Beschreibungen sind es jedoch auch.

@jayphelps Wir verwenden den Jasmin-Weg nicht mehr, seit wir alle Jasmin-Matcher neu geschrieben haben

@dmitriiabramov Entschuldigung, meine Frage war nicht klar. Wurde der Jasmin _Weg_, es zu tun, entschieden, dass es wieder hinzugefügt werden soll? Das Gleiche tun, was sie erlauben.

@jayphelps Wie ich bereits sagte, funktioniert es aufgrund der Mehrdeutigkeit nicht für alle Matcher.

expect(obj).toHaveProperty('a.b.c', 'is that a reason or a value of the property?');

und Sing Jest Matchers können mit Paketen von Drittanbietern erweitert werden. Ich denke nicht, dass es eine gute Idee ist, mit der Argumentliste herumzuspielen

Die sauberste Option ist wahrscheinlich, es als zweites Argument von expect zu haben, da es immer genau ein Argument benötigt.

expect(123, 'jest because').toEqual(123);

Ich bin mir nicht sicher, ob wir die API überladen wollen. Wir haben es fast nie in Facebook-Testsuiten verwendet, und für spezielle Fälle denke ich, dass es einfacher ist, einfach einen neuen Test zu definieren:

beforeEach(someSharedSetup);
test('reason or description', () => expect(1).toBe(1));

es sind nur ein paar zeilen mehr :)

Oder Sie können es sogar in einen anderen describe() Anruf stecken.

@dmitriiabramov Die ärgerlichen Fälle sind, wenn Sie einen Zustand aufbauen, wie in einer Zustandsmaschine für Sagen, Epen usw. Jeder Test erfordert die vorherigen Zustandsänderungen, ihre Isolierung erfordert eine Menge Duplizierung ohne Gewinn AFAIK.

it('stuff', () => {
  const generator = incrementAsync();

  expect(generator.next().value).toBe(
    call(delay, 1000)
  );

  expect(generator.next().value).toBe(
    put({ type: 'INCREMENT' })
  );

  expect(generator.next()).toBe(
    { done: true, value: undefined }
  );
});

Oder Sie können es sogar in einen anderen describe()-Aufruf packen.

Können Sie das näher ausführen? Das Verschachteln von Beschreibungsaufrufen war AFAIK nur zum Teilen von Abschnittstiteln, Tests werden immer noch gleichzeitig ausgeführt, oder?

Testsuiten (Dateien) werden gleichzeitig ausgeführt, test() -Aufrufe nicht.

Ich fange an zu denken, dass so etwas wie

test('111' () => {
  jest.debug('write something only if it fails');
  expect(1).toBe(2);
});

kann was werden

Aus dieser Diskussion und diesem Repository denke ich, wäre eine nette und semantische:

it('has all the methods', () => {
  since('cookie is a method').expect(reply.cookie).toBeDefined();
  since('download is a method').expect(reply.download).toBeDefined();
  since('end is a method').expect(reply.end).toBeDefined();
  // ...
});

Die Verwendung ist ähnlich wie bei because , macht aber semantisch mehr Sinn.

Wenn Ihnen das gefällt, kann ich vielleicht eine PR ausarbeiten, die die since -Funktionalität hinzufügt.

Bitte implementieren Sie eine einfache Möglichkeit, dies zu tun. Ich benutze es nicht sehr oft, aber gerade bei komplizierteren Tests ist es hilfreich, genau zu wissen, woran es liegt, ohne graben zu müssen.

Bitte sagen Sie nicht "Schreiben Sie Ihre Testsuiten neu, um sie einfacher zu machen". Das Einzige, was Ingenieure mehr hassen als das _Schreiben_ von Testsuiten, ist das _Umschreiben_ von Testsuiten.

Ein weiterer Vorschlag, den ich irgendwo gesehen habe, ich habe vergessen, wo er in derselben Ausgabe war, mit einer Erklärung, warum er nicht funktionieren würde. Ich sollte wohl etwas schlafen :)

Ich habe eine einfache "Prototyp" -Demo zum Laufen gebracht, ich müsste die Rekursion jetzt implementieren. Es ist ein dünner Wrapper, der Proxys um die globalen Variablen und dann über jede Methode verwendet. Proxys werden jedoch von älteren Browsern nicht unterstützt und können nicht mehrfach gefüllt werden, sodass dies für Jest möglicherweise nicht akzeptabel ist. Dies ist die allgemeine Struktur für den Wrapper:

const since = (text) => {
  return new Proxy(global, {
    get: (orig, key) => {
      return (...args) => {
        try {
          const stack = orig[key](...args);
          return new Proxy(stack, {
            get: (orig, key) => {
              return (...args) => {
                try {
                  const ret = orig[key](...args);

                  // ... implement recursion here

                } catch (err) {
                  console.log('2', key, text, err);
                  throw err;
                }
              }
            }
          });
        } catch (err) {
          console.log('1', key, text, err);
          throw err;
        }
      };
    }
  });
};

Es gibt drei realistische Optionen:

  • Dieser Weg ist akzeptabel, daher sollte er der Hauptbibliothek von Jest hinzugefügt werden. Ich räume es auf und erstelle eine PR.
  • Tauchen Sie tiefer in Jest ein und ändern Sie die Kernbibliothek. Eine Menge Arbeit, also würde ich nichts tun, bis einige Mitglieder etwas halboffiziell zu diesem Thema / dieser Richtung sagen.
  • Beenden Sie es auf diese Weise und veröffentlichen Sie es als Paket. Unerwünscht, da es nicht leicht zu entdecken ist.

Bearbeiten: Sehen Sie es in Aktion:

describe('Test', () => {
  it('works', () => {
    since('It fails!').expect('a').toEqual('b');
  });
});

Sie benötigen einen Erwartungskontext, um Testergebnisse vernünftig zu machen, wenn Sie nicht-triviale Tests haben. Tests in der realen Welt wären nicht immer so einfach.

Denken Sie an benutzerdefinierte Matcher - sie verbergen mathematische Komplexität. Aber wenn der Test fehlschlägt, ist das Verbergen dieser Komplexität nicht das, was Sie wollen, denn Sie wollen maximale Informationen über das Scheitern. Der Erwartungskontext ermöglicht es Ihnen, diesen Kontext manuell bereitzustellen. Nicht ideal, denke ich, eine Art automatischer Kontext wäre besser, aber es ist die einzige Möglichkeit, die ich bisher gesehen habe.

Wenn ich etwas kaputt gemacht habe und es fehlschlägt, muss ich es mit Jest manuell debuggen oder Protokollierung oder was auch immer _modifikationen_ hinzufügen, was viel weniger bequem ist, als nur die Ergebnisse von Testläufen anzusehen.
In Jasmine zum Beispiel haben wir die Möglichkeit, einen Kontext zu drucken, um Fehler sinnvoller zu machen.
In Javas beliebtestem Testframework JUnit haben wir genau dieselbe Funktion.

Tut mir leid, wenn ich mich irre, aber ich sehe hier keine _technologischen_ Gegenargumente zu diesem Feature. Und Dinge wie "das sollte nicht hinzugefügt werden, weil es nicht gut aussieht" sind einfach lächerlich.

Können wir wieder öffnen? Sogar jest.debug() , wie oben von @aaronabramov vorgeschlagen, wäre für mich hilfreich.

This:

it('has all the methods', () => {
    since('cookie is a method', () => expect(reply.cookie).toBeDefined());
});

can be supported by adding this:


// setupTestFrameworkScriptFile.js
// http://facebook.github.io/jest/docs/configuration.html#setuptestframeworkscriptfile-string
global.since = (explanation, fn) => {
    try {
        fn();
    } catch(e) {
        e.message = explanation + '\n' + e.message;
        throw e;
    }
};

Außerdem sieht jasmine-custom-message ähnlich aus wie angefordert:

describe('test', function() {
  it('should be ok', function() {
    since(function() {
      return {'tiger': 'kitty'};
    }).
    expect(3).toEqual(4); // => '{"tiger":"kitty"}'
  });
});

Gibt es Pläne, das wieder zu eröffnen? Scheint, als ob es in letzter Zeit Duplikate dieses Problems gegeben hat. Ich möchte auch eine benutzerdefinierte Nachricht anzeigen, wenn der Test fehlschlägt.

Hmm.. Das ist auch etwas, das ich auf meiner Wunschliste habe. Nachdem ich diesen Thread gelesen habe, kann ich die Reaktion verstehen, die sich auf die Verwendung von Jest bei Facebook bezieht und Ihren eigenen Arbeitsablauf nicht beeinflussen möchte, aber es gab einige Vorschläge, die bestehende Tests nicht beeinträchtigen und die Funktionalität hinzufügen würden, die mehrere andere gerne hätten haben (mich eingeschlossen).

Was wäre nötig, damit entweder das 2. Argument für das Erwarten()- oder das Seit()-Format als PR akzeptiert wird? Ich bin bereit, etwas Zeit zu spenden, um dabei zu helfen.

Ich habe mir gerade den Thread durchgelesen und sehe gute Argumente auf beiden Seiten. Ich möchte auf jeden Fall einen Mechanismus, um eine benutzerdefinierte Fehlermeldung aus dem gleichen Grund bereitzustellen, aus dem @timoxley ursprünglich gepostet hat. Im Moment mache ich so etwas:

import assert from 'assert'
import chalk from 'chalk'

test('api works', () => {
  assert.deepEqual(
    api(),
    [],
    chalk.red('api without magic provides no items')
  )
  assert.deepEqual(
    api(0),
    [],
    chalk.red('api with zero magic also provides no items')
  )
  assert.deepEqual(
    api(true),
    [1, 2, 3],
    chalk.red('api with magic enabled provides all items')
  )
})

Das funktioniert tatsächlich bemerkenswert gut, aber ich möchte vermeiden, Kreide verwenden zu müssen, um die rote Farbe zu erhalten (druckt sonst ohne Farbe), und ich würde es vorziehen, wenn dies von expect unterstützt wird.

Ehrlich gesagt ist es mir egal, wie es umgesetzt wird. Aber hier ist eine Alternative zu since , nur um etwas anderes rauszuschmeißen, falls andere es bevorzugen:

const expectWithMessage = expect.withMessage(
  'api with magic enabled provides all items'
)
expectWithMessage(api(true)).toEqual([1, 2, 3])

// could be rewritten like
expect
  .withMessage('api with magic enabled provides all items')(api(true))
  .toEqual([1, 2, 3])

Ich bin mir nicht sicher, ob mir das besser gefällt als since . Ich bin gut mit allem, ich würde das wirklich gerne haben :)

Oh, und um den Kommentar anzusprechen:

Der Grund, warum ich denke, dass dies nicht sehr nützlich ist, liegt darin, dass Ingenieure keine Zeit mit dem Schreiben von Tests verschwenden wollen. Alles, was wir tun, um es ihnen schwerer zu machen, führt nur zu schlechteren Tests.

Ich stimme zu, dass wir es nicht schwieriger machen wollen, Tests zu schreiben. Deshalb wäre dies eine additive Änderung. Leute, die keine "Zeit verschwenden" wollen, um ihre Tests einfacher zu debuggen, können die hilfreichen Nachrichten einfach überspringen, aber dann kommen sie in eine Codebasis, die hilfreiche Nachrichten wie diese enthält, und dann werden sie sich bedanken Ingenieur, der sich die Zeit genommen hat, die Behauptung ein wenig zu erklären :wink:

Hallo @cpojer , irgendwelche Neuigkeiten dazu?

Jest funktioniert großartig für mich, abgesehen von diesem Problem ... Im Moment kämpfe ich damit, eine fehlgeschlagene Assertion innerhalb einer for-Schleife wie zu beheben
expectationsArray.forEach(expectation => expect(...))

Es ist schwer herauszufinden, welche Erwartungen ohne eine benutzerdefinierte Fehlermeldung fehlschlagen (es sei denn, ich mache es falsch ...?)

Danke schön

@mj-airwallex Sie sind gut darin, Erwartungen mit einem test in eine for-Schleife zu packen, zum Beispiel:

const expectationsArray = [[0, 'a'], [1, 'b']];

expectationsArray.forEach(([expectation, desc]) => {
  test(`test ${desc}`, () => {
    expect(expectation).toBeGreaterThanOrEqual(2);
  });
});

Ich habe auch ein Problem mit Jest, weil ich während der Erwartungen benutzerdefinierte Nachrichten bereitstellen muss. Das Verpacken von Erwartungen unter test scheint für Fälle zu funktionieren, in denen keine asynchronen Aufrufe erforderlich sind. Aber da Jest nicht damit umgehen kann, dass describe (#2235 ) ein Versprechen zurückgibt, können wir keine Tests mit asynchronen Aufrufen erstellen und sie mit test umhüllen. Und wir können nicht mehrere test verschachteln.

Hier ein Beispiel zur Veranschaulichung des Problems:

async function getArray() {
  return [0,0,0,0,0,0]
}

describe('Custom messages with async', async () => {
  const array = await getArray();
  array.forEach((item) => {
    test(`test${item}`, () => {
      expect(item).toBe(0)
    });
  });
})

Irgendwelche Ideen, wie man damit umgeht?

Wenn ich mir das Problem im OP ansehe ("Es wäre schön, wenn es einen für Menschen lesbaren Kontext gäbe, der sofort klar macht, welche Erwartung fehlgeschlagen ist"), denke ich, dass es jetzt gelöst ist. Ab Jest 22 drucken wir den Kontext der fehlgeschlagenen Behauptung. Wird die zusätzliche Nachricht noch benötigt? Wenn es _ist_, kann es sich um einen Codekommentar über oder neben der Assertion handeln

image

Asynchrone Beschreibung ist ein weiteres Problem (das durch den hinzugefügten Codeframe nicht unterstützt wird).

Ich denke, ich würde keine asynchrone Beschreibung verwenden und stattdessen beforeEach oder beforeAll verwenden

@kentcdodds könnten Sie ein Beispiel geben, wie Sie dies mit beforeEach oder beforeAll handhaben können? Wenn Sie versuchen, alle erforderlichen asynchronen Aufrufergebnisse in beforeEach und beforeAll zu erstellen, werden Sie am Ende gezwungen, verschachtelte test zu erstellen, was nicht zulässig ist.

Ah, ich habe verpasst, was Sie mit diesem asynchronen Anruf gemacht haben. Tut mir leid 😅 Ja, dafür konntest du nicht beforeEach oder beforeAll tun.

@SimenB , das Drucken des Kontexts hilft bereits sehr und löst die meisten Probleme mit benutzerdefinierten Nachrichten. Danke dafür! Es wäre jedoch schön, die Möglichkeit für benutzerdefinierte Nachrichten explizit als Argument zu haben, da dies in Situationen wie der Verwendung von Expects in Schleifen hilfreich ist.

Wenn ich mir das Problem im OP ansehe ("Es wäre schön, wenn es einen für Menschen lesbaren Kontext gäbe, der sofort klar macht, welche Erwartung fehlgeschlagen ist"), denke ich, dass es jetzt gelöst ist.

Ja, dies löst das ursprüngliche Problem, solange Sie vor der Transpilation Zugriff auf die Originalquelle haben, was die meisten Jest-Benutzer haben werden. IMO ist es ein bisschen schwerfällig im Vergleich dazu, Benutzern zu erlauben, einfach eine vom Benutzer bereitgestellte Nachricht mit dem Fehler zu drucken, aber gut genug, denke ich.

Ich habe gerade angefangen, Jest zu verwenden, und mir fehlt eine Funktion wie diese. Mein Anwendungsfall:
Ein Test schlägt fehl, wenn ich behaupte, dass eine Eigenschaft eines Objekts wahr ist. Es würde mir helfen, den Fehler schneller zu verstehen, wenn ich das Objekt protokollieren könnte, falls die Assertion fehlschlägt.

Sie können dafür toHaveProperty verwenden.

test('property', () => {
  expect({foo: 'bar'}).toHaveProperty('baz', 'foobar');
});

image

Wenn Sie nur überprüfen möchten, ob es vorhanden ist, lassen Sie das zweite Argument weg. Wenn Sie nur behaupten wollen, dass es _einen_ Wert hat, können Sie expect.anything() verwenden.
toMatchObject ist eine weitere Alternative.

Sie können auch assert verwenden, wenn Sie möchten.

test('property', () => {
  const obj = {foo: 'bar'};
  assert.equal(obj.baz, 'foobar', JSON.stringify(obj));
});

image

Danke für den Tipp. assert.equal(obj.baz, 'foobar', JSON.stringify(obj)); würde in meinem speziellen Fall die Arbeit erledigen.

@SimenB @mpseidel was ist behaupten? ist es eine Bibliothek eines Drittanbieters? Ich kann nichts in Jest Docs finden.

@sharikovvladislav assert ist ein Knotenkernmodul https://nodejs.org/api/assert.html

@mpseidel hoppla ! Ich wusste es nicht. Danke schön. Es klappt.

Ich verwende das folgende Codefragment, um diese Einschränkung des Frameworks zu umgehen (in TypeScript, aber entferne einfach die Typanmerkungen für JS).
export const explain = (expectation: () => void, explanation: string) => { try { expectation(); } catch(e) { console.log(explanation) throw e; } }

Hallo,
Ich bin überrascht, dass noch niemand Schleifen erwähnt hat. Die Nachricht wäre nicht nur ein String, sondern abhängig von der Schleifeniteration ein dynamischer String.
jest-plugin-context ist nett, danke für diese Arbeit, aber es ist ein bisschen schwer und das ursprüngliche Problem ist imo immer noch relevant.
Sehen Sie sich diesen Test an

describe('MyStuff', () => {
    it('should render and contain relevant inputs', () => {
      const wrapper = shallowWrapped(<MyStuff />);
      const expectedKeys = ['love','jest','but','need','details','for','expect'];
      expectedKeys.forEach((key) => {
        expect(wrapper.find({ id: key }).length).toEqual(1);
      });
    });
  });

Viel Glück bei der Suche nach Ihrem Schuldigen. Im Moment muss ich stattdessen eine Zeile hinzufügen oder ein Objekt wie {len:.., key:..} testen, das ist nicht sauber und nicht benutzerfreundlich.
Ich denke, dieser Anwendungsfall ist relevant, zum Beispiel für Formulare und Item-Rendering-Check.
Die Syntax könnte so einfach sein wie toEqual(1).context("my message") oder toEqual(1, "my message") (obwohl ich natürlich weiß, dass die Implementierung immer schwieriger ist, und ich Ihre großartige Arbeit mit Jest respektiere).

Verwenden Sie vielleicht das gleiche Format wie chai - fügen Sie die Nachricht also als zweites Argument zum Erwartungsaufruf hinzu:

expect(foo, 'this detail').toEqual(2)

T

Ich habe zuvor Jasmin verwendet, bin also hierher gekommen, um festzustellen, dass es nicht unterstützt wird.
Allerdings sind diese Dinge alle Funktionen. Können wir nicht einfach so etwas tun:

describe('MyStuff', () => {
    describe('should render and contain relevant inputs', () => {
      const wrapper = shallowWrapped(<MyStuff />);
      const expectedKeys = ['love','jest','but','need','details','for','expect'];

      expectedKeys.forEach((key) => {
        it(`contains key "${key}"`, () =>
          expect(wrapper.find({ id: key }).length).toEqual(1)
        )
      })
  });
});

2018-04-18-222246_646x390_scrot

@akkerman Schöne Lösung. Da "describe" und "it" magische Globals sind, die von Scherzen bereitgestellt werden, muss ich zugeben, dass sie sich obskur anfühlen können, war ich mir nicht sicher, ob das Schreiben von "t" in einer Schleife funktionieren könnte.

Was ist mit dem Verketten eines anderen Modifikators?

expect(foo).toEqual(bar).because('reason with %s placeholders')

Oder vielleicht eine Funktion

expect(foo).toEqual(bar).explainedBy((result) => `Lorem ipsum ${result}`)

Ich denke, ein anderer Modifikator wird schnell unlesbar.
T

2018-04-19 13:47 GMT+02:00 λ • Geovani de Souza [email protected] :

Was ist mit dem Verketten eines anderen Modifikators?

Expect(foo).toEqual(bar).because('Grund mit %s Platzhaltern')

Oder vielleicht eine Funktion

erwartet(foo).toEqual(bar).explainedBy((Ergebnis) => Lorem ipsum ${result} )


Sie erhalten dies, weil Sie kommentiert haben.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/facebook/jest/issues/1965#issuecomment-382705387 oder stumm
der Faden
https://github.com/notifications/unsubscribe-auth/AAM5PwBCvET1KdEDeDEF7gGo708Naj8oks5tqHlSgaJpZM4Kc6Uu
.

--


Tarjei Huse
Mobil: 920 63 413

Die Art und Weise, wie expect funktioniert, ist das Werfen, also würde das sowieso nicht funktionieren.

Ich denke, entweder expect(something, 'some helpful text on failure').toEqual(somethingElse) oder expect.context(something, 'some helpful text on).toEqual(somethingElse) sind die besten Alternativen, aber ich mag keine von beiden wirklich

Kann man das wieder öffnen? Es scheint, dass Jest immer noch keine gute Lösung zum Testen hat, wie sich der Status über mehrere Interaktionen hinweg ändert, zum Beispiel:

  • Testen, wie sich ein zustandsbehafteter React-Container als Reaktion auf eine Reihe von Ereignissen ändert
  • Testen, wie sich eine Webseite während mehrerer Interaktionen mit Puppeteer ändert

In beiden Fällen ist es erforderlich, eine Reihe von Aktionen auszuführen und zu bestätigen, wie sich der Zustand nach jeder Aktion geändert (oder nicht geändert) hat, sodass manchmal Tests mit mehreren Behauptungen erforderlich sind. Es ist nicht immer möglich, diese Art von Problem mit beforeEach zu lösen.

Ich finde immer wieder Situationen, in denen das wirklich nützlich wäre. Insbesondere wenn ich mehrere Interaktionen und Behauptungen ausführe, wie @callumlocke erklärt.

Wenn wir eine API entwickeln, die die Leute nicht hassen, wären Sie dann bereit, diese weiterzuverfolgen? Ich denke wirklich, dass dies ein wertvolles und viel genutztes Feature wäre.

Hier ist eine Zusammenfassung der vorgeschlagenen Lösungen:

expect(api()).toEqual([]) // api without magic provides no items
it('api without magic provides no items', () => expect(api()).toEqual([]))
test('api without magic provides no items', () => expect(api()).toEqual([]))
expect(api()).toHaveNoItems()

expect(api(), 'api without magic provides no items').toEqual([])
expect(api()).because('api without magic provides no items').toEqual([])
since('api without magic provides no items').expect(api()).toEqual([]))
because('api without magic provides no items').expect(api()).toEqual([]))
jest.debug('api without magic provides no items'); expect(api()).toEqual([]))

Beachten Sie, dass ein abschließendes .because() nicht möglich ist und daher nicht als Option enthalten ist.

Alle vier Optionen in der ersten Gruppe werden heute unterstützt. Ich persönlich finde, dass die erste Option (ein Coderahmen mit einem Kommentar) großartig funktioniert. Und noch besser ist die Verwendung eines benutzerdefinierten Matchers (Option 4).

Ich denke, was wir verstehen müssen, um hier etwas bewegen zu können, ist: Was ist attraktiver an den Optionen in der zweiten Gruppe als an den Optionen in der ersten? Was fügt die zweite Gruppe hinzu, was die Grundwartung für alle von uns bereitgestellten Matcher rechtfertigen kann (über asynchrone Matcher, asymmetrische Matcher, Spionage-Matcher, Throw-Matcher, Promise-Matcher und benutzerdefinierte Matcher hinweg)?

Hallo,
Sie haben im Grunde ein paar Anwendungsfälle:

  • Mehrere Behauptungstests (Sie müssen mehrere Dinge in einem Test behaupten)
  • Dynamisch generierter Behauptungskontext (Sie möchten eine Variable in Ihrer Fehlermeldung, um sie klarer zu machen, z. B. um ein bestimmtes Feld Ihres fehlerhaften Objekts zu drucken, weil Sie viele Tests erhalten haben)
  • Dynamisch generierte Zusicherungen (Sie erstellen eine Schleife, die Zusicherungen generiert)

Optionen der ersten Gruppe sind hauptsächlich für den ersten Anwendungsfall gedacht. Wenn Sie wie vorgeschlagen dynamisch generierte Assertionen benötigen, können Sie Aufrufe von it und test verschachteln, sodass ein Test neue Tests in einer Schleife generieren kann. Das Problem ist, dass Sie Tests und keine Behauptungen generieren. Stellen Sie sich vor, ich möchte für jedes Element eines Arrays mit 1000 Elementen etwas behaupten, dies würde die Testzusammenfassungen aufblähen.

Da diese dynamischen Anwendungsfälle jedoch immer noch selten sind, sollten wir uns imo an die Lösung halten, die minimalen Aufwand für die Betreuer erfordert. Ich persönlich mag die because/since -Lösung, weil sie recht einfach klingt. Ich denke, die Implementierung würde hauptsächlich expect in ein try/catch einwickeln, das die Nachricht druckt und zurückgibt?
jest.debug klingt komisch, für mich ist Debuggen das Drucken einer Nachricht, selbst wenn die Tests tatsächlich bestanden werden
Die Option "letztes Argument" ist auch gut, aber ich bin mir nicht sicher, ob sie machbar ist, da expect eine variable Anzahl von Argumenten akzeptieren kann?

Ich bin mit jeder Option einverstanden. Ich will nur die Funktion. Ich bin auch kein großer Fan der jest.debug -API, aber wenn es die einzige ist, die Sinn macht, bin ich damit einverstanden, weil ich nur diese Funktion haben möchte.

@kentcdodds was ist mit den vier bestehenden Optionen?

@eric-burel hast du gesehen, dass test.each und describe.each in Jest 23 hinzugefügt wurden (als eigenständige Version für Jest <23 verfügbar)?

Wie gesagt, es ist mir egal, welche Option wir wählen. Ich möchte nur, dass die Funktion existiert. Ich denke, wenn ich sie nach Präferenz sortieren müsste, wäre es:

  1. expect(api(), 'api without magic provides no items').toEqual([])
  2. because('api without magic provides no items').expect(api()).toEqual([]))
  3. since('api without magic provides no items').expect(api()).toEqual([]))
  4. expect(api()).because('api without magic provides no items').toEqual([])
  5. jest.debug('api without magic provides no items'); expect(api()).toEqual([]))

(test|describe).each ist großartig, löst aber nicht das Problem, dass Sie mehrere Aktionen/Assertionen in einem einzigen Test haben möchten.

Die Funktion existiert heute mit vier Optionen:

expect(api()).toEqual([]) // api without magic provides no items
it('api without magic provides no items', () => expect(api()).toEqual([]))
test('api without magic provides no items', () => expect(api()).toEqual([]))
expect(api()).toHaveNoItems()

Was ist falsch an diesen? Die vorgeschlagenen _neuen_ Lösungen scheinen nur geringfügig besser zu sein als diese bestehenden Lösungen. Welche Vorteile bringen sie gegenüber dem, was wir haben, die die Wartungskosten rechtfertigen?

@rickhanlonii Schön, dass ich test.each nicht kannte, das ist wirklich eine großartige Funktion, danke für den Hinweis. Ich denke, es löst das Problem für meinen 3. Anwendungsfall, den dynamisch generierten Test aus einem Array.

Also blieb die zweite, die ich aufgelistet habe: Eine dynamisch generierte Fehlermeldung, die das Debuggen beschleunigen würde. Ich habe im Moment nicht viele Anwendungsfälle, vielleicht möchten Sie beim Testen eines Objektfeldwerts das gesamte Objekt bei einem Fehler drucken. Das ist meiner Meinung nach legitim, da alles, was das Schreiben von Tests erleichtert, auch wenn es marginal oder ein bisschen überflüssig ist. Schließlich können wir beide it und test schreiben, es sei denn, es gibt einen Unterschied, von dem ich nicht weiß, dass dies hauptsächlich der Bequemlichkeit dient.
Es ist marginal, aber wirklich etwas, das von Benutzern erwartet wird (kein Wortspiel), wie dieser Thread zeigt.

Bearbeiten: Das Erstellen eines Tests in einem anderen Test mit it oder test und einem dynamisch generierten Namen für den Test ist eine gültige Lösung, aber ich mag es wirklich nicht, einen Test zu erstellen, wenn ich meine, eine Behauptung zu erstellen . Ich hätte nie gedacht, dass es möglich ist, wenn die Lösung nicht in diesem Thread gegeben wurde.

Das ist verrückt. Fügen Sie einfach einen zweiten, optionalen Parameter zu Expect() hinzu. Diejenigen von uns, die es verwenden wollen, werden es (selektiv) tun, und diejenigen, die es nicht tun, werden es nicht tun.

Mocha macht das schon seit Ewigkeiten ... es ist einer der Gründe, warum ich Jasmine vor Jahren aufgegeben habe (der andere ist viel besserer Timer-Spott.) Wenn ich mich nicht dem React-Zug anschließen müsste, würde ich Jest oder so etwas nicht benutzen andere Jasminderivate.

Das Drucken einer Fehlermeldung ist eine Konvention in so vielen anderen Test-Frameworks, und ich war überrascht, es in Jest nicht zu sehen. Ich habe in diesem Thread viele hilfreiche Beispiele gefunden (danke dafür), aber das Hinzufügen einer expliziten Möglichkeit zum Drucken eines benutzerdefinierten Fehlers bei einem Testfehler wäre eine nette Ergänzung der Benutzerfreundlichkeit von Jest. Dies würde Entwicklern, die an andere Test-Frameworks (einschließlich Nicht-JS-Frameworks) gewöhnt sind, den Einstieg in Jest erleichtern.

@mattphillips Glaubst du, es ist möglich, hier etwas Ähnliches wie Jest-Chain zu tun, damit eine Lösung im Userland existiert? ZB zweites Argument zu expect

Ehrlich gesagt ist dies etwas sehr Standard in den meisten JS-Test-Frameworks. Sehr enttäuscht, es nicht in Jest zu finden, da wir alle unsere Tests mit einer benutzerdefinierten Fehlermeldung schreiben.

@SimenB Entschuldigung, ich habe deine Nachricht erst heute Morgen bemerkt!

Ja, das ist im Userland machbar, ich habe es gerade hochgezogen und als jest-expect-message https://github.com/mattphillips/jest-expect-message veröffentlicht

Feedback erwünscht :smile:

Super, danke dafür!

@cpojer

Der Grund, warum ich denke, dass dies nicht sehr nützlich ist, liegt darin, dass Ingenieure keine Zeit mit dem Schreiben von Tests verschwenden wollen. Alles, was wir tun, um es ihnen schwerer zu machen, führt nur zu schlechteren Tests.

Zwei Dinge:

  1. Das Hinzufügen eines optionalen zweiten Arguments zu Expect() macht Entwicklern kaum etwas schwerer.
  2. Das Letzte, was Entwickler tun wollen, ist, Zeit mit dem Debuggen zu verschwenden, was dazu geführt hat, dass der Test fehlgeschlagen ist. Erwartet vs. erhalten ist oft eine gute Möglichkeit, um zu überprüfen, ob eine Bedingung erfüllt wurde, aber oft nicht genug Kontext, um zu wissen, was dazu geführt hat, dass sie fehlgeschlagen ist.

Ich habe Mocha/Chai sowie Klebeband verwendet, bevor ich zu Jest kam, und das ist wirklich ein Deal Breaker. Was müssen wir tun, um eine Unterstützung für benutzerdefinierte Nachrichten zu erhalten?

Uns zu sagen, dass wir Expect.extend benötigen, um einen benutzerdefinierten Matcher zu erstellen, klingt genau so, wie Sie es in Ihrem ersten Argument vermeiden wollten: „Ingenieure wollen keine Zeit mit dem Schreiben von Tests verschwenden.“

Ich finde es einfach, den Komponententest zu öffnen und mir die Zeilennummer anzusehen, sodass mich der Anwendungsfall im OP nicht stört.

Der Anwendungsfall, der mich stört, ist, wenn ich eine Schleife in einem Test habe, z. B. um jeden Wert einer Aufzählung zu testen, zum Beispiel so:

it("Should contain at least one word of every wordType", () => {
  for (const wordType of wordTypes) {
    expect(words.find((word) => word.wordType === wordType)).toBeTruthy();
  }
});

Wenn das fehlschlägt, weiß ich nicht, welcher wordType-Wert fehlgeschlagen ist.

Meine Problemumgehung bestand darin, dies durch eine Nachricht zu ersetzen, die das Testergebnis enthält, und zu erwarten, dass die Nachricht das erwartete Testergebnis enthält (dh wahr). Wenn dies fehlschlägt, druckt Jest die Nachricht mit den zusätzlichen Informationen.

    expect(`${wordType} ${!!words.find((word) => word.wordType === wordType)}`).toEqual(`${wordType} ${true}`);

Jest druckt das ...

expect(received).toEqual(expected)

Difference:

- Expected
+ Received

- 6 true
+ 6 false

... was mir sagt, dass der Worttyp 6 war, als es fehlschlug.

Oder besser lesbar so etwas wie ...

    if (!words.find((word) => word.wordType === wordType)) {
      expect(`Didn't find a word with wordType '${wordType}'`).toEqual(null);
    }

@cwellsx check parametrized tests with test.each diese Weise wird jeder Worttyp ein unabhängiger Test mit einem beschreibenden Titel (basierend auf dem Wert)

Dies wäre unglaublich nützlich in Tests wie diesen:

test("compare ArrayBufferCursors", () => {
    const orig: ArrayBufferCursor;
    const test: ArrayBufferCursor;

    expect(test.size).toBe(orig.size);

    while (orig.bytes_left) {
        expect(test.u8()).toBe(orig.u8());
    }
});

Im Moment weiß ich nur, dass ein Byte im ArrayBufferCursor falsch ist, aber ich habe keine Ahnung, welches. In der Lage zu sein, einen Index als Kontext hinzuzufügen, würde das Debuggen viel einfacher machen. Zum Glück haben einige Leute hier Problemumgehungen vorgestellt, aber sie sind alle hässlich und langsam.

@rickhanlonii , ich verstehe, dass Sie die Wartungskosten von Scherz reduzieren möchten, aber Optionen, die Sie in Ihrem Kommentar anbieten, erhöhen die Wartung von Komponententests aller anderen Projekte. Wenn ich eine Behauptung mit toEqual erklären möchte, was ansonsten vollkommen ausreicht, schlagen Sie mir wirklich vor, einen benutzerdefinierten Matcher einzuführen?!

Während es Fälle mit sich wiederholenden Tests gibt, in denen it.each nützlich sind, erschwert das Hinzufügen eines unnötigen Codes für einen einfachen Kommentar zu einer Behauptung dieses Testframework.

Diese Diskussion und das Auftauchen von jest-expect-message erinnert mich an das beforeAll -Problem in Jasmine...

Möglicherweise verstehe ich die Anfrage des OP hier falsch, aber das Problem, das ich zu lösen versuchte und mich zu diesem Problemthread brachte, wurde nur mit einem einfachen try / catch und einem Wrapper um jest.expect gelöst. Alles, was ich tun wollte, war, die Gesamtheit der Objekte, die ich erwartet hatte, im Vergleich zu denen, die ich erhielt, zu protokollieren + einige grundlegende Protokolle, die die Bedeutung erklären. Natürlich könnte dies erweitert werden, um so ziemlich alles zu tun, was Sie wollen.

Die allgemeinste Version davon könnte so aussehen:

in some test utility file...

const myExpect = (expectFn, errorCallback) => {
  try {
    expectFn();
  } catch (err) {
    errorCallback();
    throw new Error(err);
  }
};

// in the actual test suite...

const context = { hello: "world", results, expected };
myExpect(
    () => expect(results).toEqual(expected),
    () => console.error("[Error] -- context:", JSON.stringify(context))
);

+1
Hallo, diese Funktion würde mich auch sehr interessieren. Es würde mein Leben so viel einfacher machen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen