Cucumber-js: Eine Option zum Lockern der Argumentprüfung für die Schrittdefinitionen

Erstellt am 12. Feb. 2016  ·  14Kommentare  ·  Quelle: cucumber/cucumber-js

Hallo, Obwohl ich den Wert der strengen Überprüfung, die für die Schrittargumente durchgeführt wird, verstehe, bin ich auch der Meinung, dass diese Kontrolle an die Benutzer zurückgegeben werden muss.

In meinem Fall möchte ich beispielsweise generische Wrapperfunktionen um die Schrittimplementierungsfunktionen herum implementieren, wobei ich vor und nach dem Schrittaufruf eine generische Verarbeitungslogik hinzufügen möchte. Für diesen generischen Wrapper muss ich auf alle Argumente zugreifen, die übergeben wurden, und muss daher auf das arguments-Array zugreifen, anstatt explizite Parameter zu deklarieren. Derzeit würde mich die Gurke nur davon abhalten, dies zu tun, da sie eine strenge Überprüfung der Parameter durchführt.

Ich möchte vorschlagen, dem Optionsobjekt einen weiteren Konfigurationsparameter mit dem Namen skipStrictParameterCheck hinzuzufügen, der, wenn er nicht gesetzt ist, als falsch angenommen würde. Auf diese Weise wäre das Standardverhalten für die gängigste Verwendung strenge Prüfungen, aber für andere, die das Framework verwenden möchten, um etwas mehr darum herum zu bauen, gibt es ihnen die Flexibilität, einige der dynamischen Fähigkeiten von JavaScript zu nutzen.

Alle 14 Kommentare

Ich habe die gleiche Anfrage gestellt, siehe #445 :)

@riaan53 , ja, ich habe mir das

Was ist ein Teil dieser generischen Verarbeitungslogik, über die Sie sprechen?

Sicher. In einem Anwendungsfall möchte ich, dass alle Entwickler Ausdrucksreferenzen in ihren Schrittdefinitionen verwenden können. zB 'Wenn sich der Benutzer ${admin} am System anmeldet'.

In diesem Fall würde ${admin} möglicherweise aus einer JSON-Datei aufgelöst und die Verantwortung für die Auflösung liegt beim Entwickler, während die Schrittdefinition implementiert wird. Wenn Sie sich jedoch eine solche Eigenschaftsauflösung wirklich ansehen, kann dies durch generischen Code erfolgen, ohne dass sich der Entwickler dessen bewusst ist.

Um dies zu ermöglichen, kann ich leicht einen generischen Funktionswrapper um die Entwicklerschrittimplementierungen erstellen, der die von Cucumber injizierten Rohargumente akzeptiert, sie auflöst und dann die aufgelösten Werte in die eigentliche Schrittimplementierung einfügt.

Derzeit wäre ich nicht in der Lage, eine so generische Funktion zu schreiben, da die Validierungen von Cucumber sicherstellen würden, dass die Funktion die richtige Anzahl von Parametern hat, was in meinem Fall nicht der Fall ist, da meine generische Wrapper-Funktion keine benannten Argumente akzeptiert und ich die Objekt "Argumente".

Hoffe, ich konnte Sinn machen. Es gibt auch andere Anwendungsfälle in unserem aktuellen Projekt, bei denen eine generische Wrapper-Funktion um die Schrittimplementierungen benötigt würde

Wenn wir Step-Argument-Transformationen implementieren, würde dies Ihr Problem lösen: https://github.com/cucumber/cucumber/wiki/Step-Argument-Transforms?

Ah ja, könnte möglicherweise das Problem der Referenzauflösung beheben. Ich muss jedoch aus anderen Gründen immer noch eine generische Wrapper-Funktion um die Schrittimplementierungen legen.

Ein Grund dafür ist beispielsweise, dass ich Protractor mit Cucumber als Framework verwende und in Protractor benutzerdefinierte Zusagen im WebDriver-Kontrollfluss registrieren muss, damit es sorgfältig darauf wartet, dass diese benutzerdefinierten Zusagen aufgelöst werden, bevor es mit dem nächsten Schritt fortfährt.

Während Cucumber auf die Auflösung eines Promises wartet (wenn es von der Schrittimplementierung zurückgegeben wird), ist es WebDriver offensichtlich nicht bekannt und meistens müssen wir jeder Schrittimplementierung zusätzlichen Code hinzufügen, um das zurückgegebene Promise bei WebDriver zu registrieren.

Dies ist wiederum sehr einfach durch einen generischen Funktionswrapper zu beheben, für den die Parameterprüfung von Cucumber gelockert werden muss.

Ich bin jetzt ein paar Mal auf dieses Problem gestoßen.

Die bekannteste ist die Implementierung eines retry Helfers für jeden Then Schritt:

cucumber.Then = function(match, callback) {
  const retryingCallback = (...args) => retry(async () => await callback(...args));
  cucumber.Then(match, retryingCallback);
};

Ich verwende diesen Helfer, um Timing-Probleme in einem schließlich konsistenten Backend zu lösen. Im Wesentlichen führt es den Rückruf alle x Sekunden aus, bis entweder y Sekunden verstrichen sind oder der Rückruf verstrichen ist.

Dies verursacht leider

function has 0 arguments, should have 1 (if synchronous or returning a promise) or 2 (if accepting a callback)

Die Alternative, die ich jetzt verwende, besteht darin, den Helfer in jedem Then Schritt aufzurufen, was eine Menge Codeduplizierung verursacht.

this.Then(/^I see that "([^"]*)" does not have a destination$/, async clientName => {
  return retry(async () => {
    const client = await homeView.clientByName(clientName);
    expect(client.destinationName).to.not.exist;
  });
});

Ein anderer Fall ist ein einfacherer Fall, in dem ich eine Hilfsfunktion für die Anmeldung möchte.

function loggedIn(username, func) {
  return (...args) => {
    await accounts.login(username);
    return func(...args)
  };
}

this.Then(/^someone logged in as "([^"]*)" sees a destination named "([^"]*)"$/, loggedIn(assert.destinationExists));

Auch dieser würde mir eine Menge Code-Duplizierung ersparen.

Schließlich erwarte ich, dass ich irgendwann eine Suite hinzufügen möchte, die alle meine Akzeptanztests durchführt, aber den Server vor jedem Then Rückruf neu startet (um sicherzustellen, dass Server-Neustarts die Dinge nicht durcheinander bringen). Wieder viele Duplikate.

PS Der Wiederholungshelfer:

const patience = 250;
const interval = 5;

function delay(time) {
  return new Promise(function (fulfill) {
    setTimeout(fulfill, time);
  });
}

async function attempt(start, func) {
  const attemptDate = new Date();
  try {
    return await func();
  } catch (errr) {
    const timeElapsed = attemptDate.getTime() - start.getTime();
    if (timeElapsed < patience) {
      await delay(interval);
      return await attempt(start, func);
    } else {
      throw errr;
    }
  }
}

export async function retry(func) {
  const start = new Date();
  return await attempt(start, func);
}

_Bearbeiten_

Ich habe versucht, mich daran herumzuhacken:

function splat(func) {
  return (one, two, three, four, five, six, seven, eight, nine, ten) => {
    if (typeof ten !== 'undefined') {
      return func(one, two, three, four, five, six, seven, eight, nine, ten);
    } else if (typeof nine !== 'undefined') {
      return func(one, two, three, four, five, six, seven, eight, nine);
    } else if (typeof eight !== 'undefined') {
      return func(one, two, three, four, five, six, seven, eight);
    } else if (typeof seven !== 'undefined') {
      return func(one, two, three, four, five, six, seven);
    } else if (typeof six !== 'undefined') {
      return func(one, two, three, four, five, six);
    } else if (typeof five !== 'undefined') {
      return func(one, two, three, four, five);
    } else if (typeof four !== 'undefined') {
      return func(one, two, three, four);
    } else if (typeof three !== 'undefined') {
      return func(one, two, three);
    } else if (typeof two !== 'undefined') {
      return func(one, two);
    } else if (typeof one !== 'undefined') {
      return func(one);
    } else {
      return func();
    }
  };
}

cucumber.Then = function(match, callback) {
  const retryingCallback = splat((...args) => retry(async () => await callback(...args)));
  cucumber.Then(match, retryingCallback);
};

aber

function has 10 arguments, should have 1 (if synchronous or returning a promise) or 2 (if accepting a callback)

macht mich zu einem traurigen Panda

Hier ist ein Beispiel für das Umschließen einer Funktion, um die Länge beizubehalten. Wäre schön, wenn es nur ein kleines Knotenmodul gäbe, das dies für Sie erledigt.

Danke für den Vorschlag! Das funktioniert, wenn Sie die Anzahl der Argumente pro Schrittdefinition angeben, oder?

z.B.

this.Then(/^someone logged in as "([^"]*)" sees a destination named "([^"]*)"$/, createProxy(loggedIn(assert.destinationExists), 2));

Das dringendste Problem für mich ist, dass ich keine generische Middleware für Definitionen mit mehreren Schritten hinzufügen kann. Etwas wie createProxy könnte vielleicht funktionieren, wenn ich es zu einem Objekt mache, das eine Middleware-Registrierung ermöglicht, und ihm dann die Anzahl der Argumente bei jeder einzelnen Schrittdefinition mitteile. (Schauen Sie sich mein erstes Beispiel genau an und Sie werden sehen, dass ich createProxy nicht direkt verwenden kann, da die Funktion retry es umschließt. Es sollte umgekehrt sein, aber dann createProxy kennt nicht die Anzahl der Argumente für jeden Rückruf)

Fühlt sich immer noch sehr umständlich an, verglichen mit der Möglichkeit, den Fehler auszuschalten. :unschuldig:

Ich denke, Sie können eine Variante dieser Funktion verwenden, bei der Sie, anstatt proxyLength einzugeben, einfach die Funktion, die Sie umschließen, übergeben und function.length .

Toller Vorschlag, danke!

Ich habe so etwas wie Folgendes zu tun:

cucumber.Then = function(match, callback) {
  cucumber.Then(match, retryProxy(callback));
};

function retryProxy(func) {
  const numberOfArgs = func.length;
  switch (numberOfArgs) {
    case 0: return () => retry(func);
    case 1: return (a) => retry(func, a);
    case 2: return (a, b) => retry(func, a, b);
    case 3: return (a, b, c) => retry(func, a, b, c);
    case 4: return (a, b, c, d) => retry(func, a, b, c, d);
    case 5: return (a, b, c, d, e) => retry(func, a, b, c, d, e);
  }
}

Zwei Dinge, die es nicht löst, sind der Login-Helper-Fall und das Zulassen von Standardparametern, aber ich kann diese beiden umgehen.

Ich bin froh, dass ich jetzt Middleware ohne Anpassungen an meinen Schrittdefinitionen hinzufügen kann!

@thomasvanlankveld Nur damit Sie es wissen, habe ich diese Bibliothek gefunden, die eine Funktion https://github.com/blakeembrey/arity

@sushil-rxr könnten Sie in Ihrem generischen Funktionswrapper so etwas wie die ursprüngliche Funktionslänge beibehalten?

In 2.0.0-rc.1 Sie jetzt einen generischen Funktionswrapper hinzufügen. Es hat auch die eingebaute Funktionalität, die ursprüngliche Funktionslänge beizubehalten.

Dieser Thread wurde automatisch gesperrt, da nach dem Schließen in letzter Zeit keine Aktivität stattgefunden hat. Bitte öffnen Sie eine neue Ausgabe für verwandte Fehler.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

NoNameProvided picture NoNameProvided  ·  5Kommentare

jan-molak picture jan-molak  ·  4Kommentare

edwinwright picture edwinwright  ·  3Kommentare

stefdelec picture stefdelec  ·  6Kommentare

bmsoko picture bmsoko  ·  7Kommentare