Cucumber-js: Solicitud de función: interfaz de usuario que no utiliza la palabra clave `this`

Creado en 31 ene. 2017  ·  16Comentarios  ·  Fuente: cucumber/cucumber-js

Dado un mundo como:

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

Quiero poder escribir:

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

en lugar de

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

Esto también me librará de manipular los .bind(this) en todas las operaciones asíncronas, o si lo prefiere, deshacerme de los monos con var that = this .

Un beneficio más (en mi humilde opinión): ayudará a las personas a bajar del árbol de herencia de los mundos y a orientarlos mejor hacia los mejores poderes de JS.

accepted enhancement

Comentario más útil

Bueno. Sin embargo, como no todo el mundo es un programador funcional y para evitar la introducción de un cambio importante, ¿qué piensan ustedes de hacer una opción que haga que el mundo se pase como el primer parámetro?

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

Todos 16 comentarios

Estoy de acuerdo en que significará que siempre acepta world como argumento, y eso es un cambio de API.
Creo que al igual que el moca que tiene --ui exports y --ui tdd y --ui bdd , la cucue también puede.
Básicamente es una variable de proyecto que determina si los pasos se llaman / aplican en el mundo, o lo aceptan como primer argumento.

Realmente no creo que esto valga la pena a largo plazo. Sí, es bueno para esos pasos de 1 línea en los que puede usar => donde de otra manera no podría hacerlo. En mi experiencia, he tenido muy pocos pasos como este. Me sorprende que ES6 no incluyera una función de flecha que no retiene el contexto, ya que sería ideal para mí.

Esto también me librará de manipular los .bind(this) en todas las operaciones asincrónicas, o si lo prefiere, deshacerme de los monos con var that = this .

¿No puedes usar la flecha gruesa dentro de las definiciones de los pasos para evitar esto?

Un beneficio más (en mi humilde opinión): ayudará a las personas a bajar del árbol de herencia de los mundos y a orientarlos mejor hacia los mejores poderes de JS.

¿Puedes explicar esto un poco más / dar un ejemplo?

En palabras simples: pertenezco a una escuela de programadores que han evitado la forma de frasear un contexto como clase. Cuanto más personas se exponen al lado oscuro de la programación orientada a objetos, esta escuela se hace más grande, y el movimiento en la comunidad JS que impidió que la función de flecha retenga el contexto es un ejemplo de la fuerza de este movimiento.

Sin entrar en un debate religioso, solo diré que creo en vivir y dejar vivir, ¿quieres usar clases? estupendo. Por favor , no me fuerces a :-)

Mi sensación general es que podría ser un pequeño cambio, y estaré feliz de hacerlo si me puedes dar la dirección general de cómo crees que se haría mejor ;-)

Cuando definimos pasos, estamos definiendo funciones, no clases, por lo que, lógicamente, this no tiene lugar en él.

Creo que esta es una situación para el paradigma FP más que el paradigma OOP.

@osher Creo que podemos darle a @charlierudolph el beneficio de la duda de que no quiere obligarte a usar clases. Dicho esto, no debería verse obligado a integrar (rompedores) cambios de API donde sea que esté usando este paquete, a menos que ofrezca alguna ventaja tangible.

@charlierudolph Creo que por "los mejores poderes de JS" @osher significa FP, Programación funcional.

En mi humilde opinión, este cambio haría que las definiciones de pasos fueran un poco más simples y lógicas. Básicamente, simplemente eliminaría esta línea: const thisWorldNotThisStep = this

Definitivamente no hay necesidad de simular .bind(this) a todas sus funciones asíncronas, y siempre que dentro de la definición de paso esté usando funciones de flecha, no es necesario usar el patrón const self = this .

El problema (que no es un gran problema) es que dentro de una definición de paso, this refiere al mundo, no al paso, lo cual es contrario a la intuición.

Bueno. Sin embargo, como no todo el mundo es un programador funcional y para evitar la introducción de un cambio importante, ¿qué piensan ustedes de hacer una opción que haga que el mundo se pase como el primer parámetro?

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

Estoy totalmente de acuerdo con la idea de inyectar el mundo / contexto tiene un parámetro del paso. A partir de este proyecto, todo el ecosistema JS se está moviendo para adoptar por completo las nuevas funcionalidades permitidas por ES6 y eso es algo bueno ❤️

Si observa otras herramientas como Express o Koa, establecen el contexto de solicitud actual como this Y como primer parámetro del middleware (es el equivalente al paso de pepino). Esta solución permite el uso tradicional de la función y el uso de la función de flecha ES6.
Otra ventaja del contexto como primer parámetro es que

Para la solución propuesta por @charlierudolph , no creo que esto funcione a largo plazo: esto dividirá a la comunidad en dos. Todos los ejemplos de pepino no funcionarán en todas las instalaciones y los documentos se duplicarán en dos. No es elegante.

No estoy seguro de las preocupaciones sobre los cambios importantes, la v2 es el momento perfecto para introducir este tipo de cambio. Esperar obligará a hacer / esperar otra especialización.

¿Qué tal si cambiamos Before y After para inyectar world :

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!
  });
});

De esa manera, puede capturar world en una variable y un paso de before . No estropeará la definición de cada paso.

A continuación, mantenemos la forma antigua de trabajar pero generamos un mensaje deprecated . O incluso no hagas eso, simplemente deja la opción ahí para las personas que quieran usar ese enfoque y sepan lo que están haciendo.

Me temo que el argumento sobre la compatibilidad de los fragmentos que se encuentran en línea es un argumento convincente.

Incluso si habrá --ui banderas, o setWorldInjectionStrategy('argument') , se convierte en un problema que se comunica mejor como un cambio importante de una versión principal, acompañado de todas las discusiones en línea y el alboroto que corresponde a tales cambios. y ayudando a eliminar la confusión.

Así que voto por hacerlo en la próxima versión de cuke, y ... makedoing hasta que se lance.

Una versión canaria o una bandera oculta sería una promoción increíble que usaría y recibiría comentarios desde el principio.

Me encantaría exponer una interfaz FP alternativa.

¿Qué pasa con lo siguiente? (usando async / await para ilustrar que también es compatible con las promesas)

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 })
})

He pirateado cucumber-fp que ofrece definiciones de pasos funcionales. Ya tengo algunas ideas de mejora. ¡Comentarios bienvenidos!

Sugiero que cerremos este problema y dejemos que la gente experimente con esa pequeña biblioteca. Quizás algún día podamos traerlo a Cucumber.js.

@jbpros Preferiría no tener que instalar otra dependencia solo para poder usar las funciones de flecha en mis pruebas.

Esto debería ser realmente sencillo de implementar para todos. El siguiente fragmento de código funciona bastante bien (funcionalmente) para mí, con una gran advertencia que lo hace inutilizable:

// 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);
    }

Esa advertencia es que cada definición de paso ahora registra el siguiente error debido al parámetro adicional:

    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

Si intenta agregar parámetros adicionales para solucionarlo, obtendrá

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

Todo lo que cucumber-js necesita es una opción para deshabilitar las comprobaciones de argumentos de función y este problema desaparecerá. En realidad, pensándolo bien, parece que la prueba también expirará, ya que pepino asume que se requiere una devolución de llamada en función del número de argumentos en la función de definición de paso. Sin embargo, esto podría solucionarse dando prioridad a una promesa devuelta.

@andyearnshaw gracias por su aporte, escuché su preocupación acerca de una dependencia "solo para funciones de flecha en stepdefs". Esta biblioteca es básicamente la solución que uso personalmente para obtener definiciones de pasos sin estado, simplemente la empaqueté para cualquier persona interesada.

Considérelo como una solución temporal en torno al debate en curso para una API stepdef tan pura en el núcleo (ha estado sucediendo durante más de 4 años, lo crea o no). Como dije, este es un experimento que podría aterrizar en el núcleo en algún momento y realmente agradecería los comentarios de las personas que realmente lo usan. Si obtiene suficiente tracción, sería un mejor argumento para la integración en pepino.

Además, tenga en cuenta que ofrece un par de otras (pequeñas) herramientas funcionales útiles: tap() y contextos de solo lectura forzados.

La verificación de arity en las funciones stepdef es definitivamente un problema que tuve que sortear en esta biblioteca (de una manera bastante fea). Una opción para desactivarlo tanto en la CLI como programáticamente sería muy útil para este (y potencialmente otros casos de uso). Me encantaría hacer eso, pero el tiempo es un recurso escaso para mí en este momento.

Siéntase libre de inspirarse en pepino-fp para arreglar el control de arity mientras tanto.

@jbpros eso es genial, y realmente aprecio el esfuerzo que han puesto allí. Estaba más en desacuerdo con la opinión de que este tema debería cerrarse. Echaré un vistazo a su biblioteca y veré si me ayuda a solucionar ese molesto cheque. 🙂

@andyearnshaw tiene razón, gracias por la aclaración. Estoy de acuerdo en que no deberíamos deshacernos de esta idea y mantener este tema abierto es probablemente una buena manera de mantener las cosas transparentes, de hecho.

¿Fue útil esta página
0 / 5 - 0 calificaciones