Cucumber-js: Funktionsanfrage: Benutzeroberfläche, die das Schlüsselwort `this` nicht verwendet

Erstellt am 31. Jan. 2017  ·  16Kommentare  ·  Quelle: cucumber/cucumber-js

Angesichts einer Welt wie:

require('cucumber').defineSupportCode( ({setWorldConstructor:world}) => {
    world(class { 
      constructor(p) { ... }
      foo(bar) { return new Promise(.... ) }
    })
})

Ich möchte schreiben können:

  When(/^I do the foo with (.*)$/i, (ctx, bar) => ctx.foo(bar) );

Anstatt von

  When(/^I do the foo with (.*)$/i, function(bar) {
      return this.foo(bar)
  });

Dies wird mich auch davon befreien, die .bind(this) bei allen asynchronen Operationen zu manipulieren, oder, wenn Sie es vorziehen, das Affen mit var that = this loszuwerden.

Ein weiterer Vorteil (IMHO) - es wird Menschen helfen, den Vererbungsbaum für Welten zu verlassen und sie besser auf die besseren Kräfte von JS auszurichten

accepted enhancement

Hilfreichster Kommentar

Okay. Da jedoch nicht jeder ein funktionaler Programmierer ist und um die Einführung eines Breaking Change zu verhindern, was halten Sie von einer Option, die die Welt als ersten Parameter übergeben lässt?

defineSupportCode(({setWorldInjectionStrategy}) => {
  setWorldInjectionStrategy('this') // default
  setWorldInjectionStrategy('argument') // makes world be the first argument to steps and hooks 
})

Alle 16 Kommentare

Ich stimme zu, dass dies bedeutet, dass Sie immer world als Argument akzeptieren, und das ist eine API-Änderung.
Ich denke, dass wie Mokka, der --ui exports und --ui tdd und --ui bdd das Cucue auch kann.
Es ist im Grunde eine Projektvariable, die bestimmt, ob Schritte auf der Welt aufgerufen/angewandt werden oder als erstes Argument akzeptiert werden.

Ich glaube nicht, dass sich das auf Dauer lohnen würde. Ja, es ist schön für diese 1-Zeilen-Schritte, bei denen Sie das => wo Sie es sonst nicht können. Meiner Erfahrung nach hatte ich nur sehr wenige Schritte dieser Art. Ich bin überrascht, dass ES6 keine Pfeilfunktion enthält, die den Kontext nicht beibehält, da dies für mich ideal wäre.

Dies wird mich auch davon befreien, das .bind(this) bei allen asynchronen Operationen zu manipulieren, oder, wenn Sie es vorziehen, das Affen mit var that = this loszuwerden.

Können Sie nicht den dicken Pfeil in den Schrittdefinitionen verwenden, um dies zu vermeiden?

Ein weiterer Vorteil (IMHO) - es wird Menschen helfen, den Vererbungsbaum für Welten zu verlassen und sie besser auf die besseren Kräfte von JS auszurichten

Kannst du das etwas näher erläutern/ein Beispiel geben

In einfachen Worten: Ich gehöre zu einer Schule von Programmierern, die es vermieden haben, einen Kontext als Klasse zu formulieren. Je mehr Menschen der dunklen Seite von OOP ausgesetzt sind, desto größer wird diese Schule – und die Bewegung in der JS-Community, die verhindert, dass

Ohne in eine religiöse Debatte zu geraten - ich sage nur, ich glaube an das Leben und lasse leben, du willst Klassen benutzen? groß. Bitte zwing mich nicht dazu :-)

Mein allgemeines Gefühl ist, dass es eine kleine Änderung sein könnte, und ich werde es gerne tun, wenn Sie mir die allgemeine Richtung geben können, wie es Ihrer Meinung nach am besten wäre ;-)

Wenn wir Schritte definieren, definieren wir Funktionen, keine Klassen, also hat this logischerweise keinen Platz darin.

Ich denke, dies ist eher eine Situation für das FP-Paradigma als für das OOP-Paradigma.

@osher Ich denke, wir können @charlierudolph den Vorteil des Zweifels geben, dass er Sie nicht zwingen möchte, Klassen zu verwenden. Das heißt, er sollte nicht gezwungen werden, (störende) API-Änderungen zu integrieren, wo immer er dieses Paket verwendet, es sei denn, es bietet einen greifbaren Vorteil.

@charlierudolph Ich denke, mit "die besseren Kräfte von JS" bedeutet @osher FP, Functional Programming.

IMHO würde diese Änderung die Schrittdefinitionen ein kleines bisschen einfacher und logischer machen. Es würde im Grunde nur diese Zeile entfernen: const thisWorldNotThisStep = this

Es besteht definitiv keine Notwendigkeit, .bind(this) für alle Ihre asynchronen Funktionen zu verwenden, und solange Sie in der Schrittdefinition Pfeilfunktionen verwenden, müssen Sie das Muster const self = this .

Das Problem (das kein großes Problem ist) besteht darin, dass sich this innerhalb einer Schrittdefinition auf die Welt bezieht, nicht auf den Schritt, was nicht intuitiv ist.

Okay. Da jedoch nicht jeder ein funktionaler Programmierer ist und um die Einführung eines Breaking Change zu verhindern, was halten Sie von einer Option, die die Welt als ersten Parameter übergeben lässt?

defineSupportCode(({setWorldInjectionStrategy}) => {
  setWorldInjectionStrategy('this') // default
  setWorldInjectionStrategy('argument') // makes world be the first argument to steps and hooks 
})

Ich stimme der Idee, die Welt / den Kontext zu injizieren, voll und ganz zu, hat einen Parameter des Schrittes. Beginnend mit diesem Projekt bewegt sich das gesamte JS-Ökosystem, um die neuen Funktionen von ES6 vollständig zu nutzen und das ist eine gute Sache ❤️

Wenn Sie sich andere Tools wie Express oder Koa ansehen, setzen diese den aktuellen Anforderungskontext als this UND als ersten Parameter der Middleware (entspricht dem Gurkenschritt). Diese Lösung ermöglicht die traditionelle Verwendung von Funktionen und die Verwendung der ES6-Pfeilfunktion.
Ein weiterer Vorteil des Kontexts in als erster Parameter ist, dass

Für die von @charlierudolph vorgeschlagene Lösung

Ich bin mir nicht sicher, ob sich die Sorgen um brechende Veränderungen befassen, die v2 ist der perfekte Zeitpunkt, um diese Art von Veränderung einzuführen. Das Warten zwingt dazu, einen weiteren Major zu machen/zu warten.

Wie wäre es, wenn wir Before und After ändern, um world zu injizieren:

defineSupportCode(function({After, Before, Given}) {
  let world;

  // Asynchronous Callback
  Before(function (w, scenarioResult, callback) {
    world = w;
    callback();
  });
  After(function (world, scenarioResult, callback) {
    callback();
  });

  Given('I access the world', () => {
    assert(world); // Yay!
  });
});

Auf diese Weise können Sie world in einer Variablen und einem before Schritt erfassen. Es wird nicht jede Schrittdefinition durcheinander bringen.

Als nächstes behalten wir die alte Arbeitsweise bei, generieren aber eine deprecated Nachricht. Oder tun Sie das auch nicht, lassen Sie die Option einfach dort für Leute, die diesen Ansatz verwenden möchten und wissen, was sie tun.

Ich fürchte, das Argument über die Kompatibilität von online gefundenen Snippets ist ein überzeugendes Argument.

Selbst wenn es --ui Flags oder setWorldInjectionStrategy('argument') - es wird ein Fallstrick, der besser als Breaking Change einer Hauptversion kommuniziert wird, serviert mit all den Online-Diskussionen und Aufruhr, die solchen Änderungen angemessen sind, und hilft, Verwirrung zu beseitigen.

Also stimme ich dafür, es in der nächsten Version von cuke zu tun und ... bis zur Veröffentlichung zu machen.

Eine Kanarienvogel-Version oder eine versteckte Flagge wäre eine tolle Werbeaktion, die ich frühzeitig nutzen und Feedback geben würde

Ich würde gerne eine alternative FP-Schnittstelle bereitstellen.

Was ist mit folgendem? (Verwenden Sie async/await, um zu veranschaulichen, dass es auch versprechen-freundlich ist)

import {initialize, Given, Before} from 'cucumber/fn'

// specify a function that returns the initial context:
initialize(async () => ({ a: 42 }))

Before({ timeout: 10 }, async (ctx) => {
  await doStuff(ctx.a)
})

Given(/^a step passes with {number}$/, async (ctx, number) => {
  const newA = await computeStuff(ctx.a, number)
  // tell cucumber about the new context:
  return Object.assign({}, ctx, { a: newA })
})

Ich habe gurken -fp zusammengehackt , das funktionale Schrittdefinitionen bietet. Ich habe schon ein paar Verbesserungsideen. Rückmeldung willkommen!

Ich schlage vor, wir schließen dieses Thema und lassen die Leute mit dieser kleinen Bibliothek experimentieren. Vielleicht können wir es eines Tages in Cucumber.js einbringen.

@jbpros Ich würde es vorziehen, keine weitere Abhängigkeit installieren zu müssen, nur damit ich

Das sollte eigentlich für jeden ganz einfach selbst umzusetzen sein. Das folgende Code-Snippet funktioniert für mich (funktionell) ziemlich gut, mit einer großen Einschränkung, die es unbrauchbar macht:

// Don't rely on `this` in step definitions. It's 2021 for crying out loud.
const definitionFunctionWrapper = (fn) =>
    function(...args) {
        return fn(...args.slice(0, -1), this);
    }

Dieser Vorbehalt, dass jede einzelne Schrittdefinition besteht, protokolliert jetzt aufgrund des zusätzlichen Parameters den folgenden Fehler:

    Error: function uses multiple asynchronous interfaces: callback and promise
       to use the callback interface: do not return a promise
       to use the promise interface: remove the last argument to the function

Wenn Sie versuchen, zusätzliche Parameter hinzuzufügen, um dies zu umgehen, erhalten Sie

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

Alles, was cucumber-js braucht, ist eine Option zum Deaktivieren von Funktionsargumentüberprüfungen und dieses Problem wird verschwinden. Tatsächlich sieht es auf den zweiten Blick so aus, als würde auch der Test ablaufen, da Gurke davon ausgeht, dass ein Rückruf basierend auf der Anzahl der Argumente in der Schrittdefinitionsfunktion erforderlich ist. Dies könnte jedoch umgangen werden, indem einem zurückgegebenen Versprechen Vorrang eingeräumt wird.

@andyearnshaw danke für deinen Beitrag, ich höre deine Besorgnis über eine Abhängigkeit "nur für Pfeilfunktionen in Stepdefs". Diese Bibliothek ist im Grunde die Lösung, die ich persönlich verwende, um zustandslose Step-Defs zu erhalten. Ich habe sie nur für alle gepackt, die sich dafür interessieren.

Betrachten Sie es als eine vorübergehende Lösung für die anhaltende Debatte um eine so reine Stepdef-API im Kern (es geht seit mehr als 4 Jahren, ob Sie es glauben oder nicht). Wie gesagt, dies ist ein Experiment, das irgendwann im Kern landen könnte, und ich würde mich über Feedback von Leuten freuen, die es tatsächlich verwenden. Wenn es genug Traktion bekommt, wäre das ein besseres Argument für die Integration in Gurken.

Beachten Sie auch, dass es ein paar andere (kleine) nützliche funktionale Werkzeuge bietet: tap() und erzwungene schreibgeschützte Kontexte.

Der Arity-Check von Stepdef-Funktionen ist definitiv ein Problem, das ich in dieser Lib (auf ziemlich hässliche Weise) umgehen musste. Eine Option zum Deaktivieren sowohl auf der CLI als auch programmgesteuert wäre für diese (und möglicherweise andere Anwendungsfälle) sehr nützlich. Das würde ich gerne machen, aber Zeit ist für mich im Moment eine knappe Ressource.

Lassen Sie sich von cucumber-fp inspirieren, um in der Zwischenzeit den Arity-Check zu beheben.

@jbpros , das ist großartig und ich weiß die Mühe, die Sie sich da

@andyearnshaw ha richtig, danke für die Klarstellung. Ich stimme zu, dass wir diese Idee nicht aufgeben sollten und dieses Thema offen zu halten ist wahrscheinlich in der Tat eine gute Möglichkeit, die Dinge transparent zu halten.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen