Cucumber-js: Una opción para relajar la verificación de argumentos para las definiciones de los pasos.

Creado en 12 feb. 2016  ·  14Comentarios  ·  Fuente: cucumber/cucumber-js

Hola, aunque comprendo el valor de la comprobación estricta que se está haciendo para los argumentos de los pasos, también creo que es necesario devolver este control a los usuarios.

Por ejemplo, en mi caso, quiero implementar funciones de envoltura genéricas alrededor de las funciones de implementación de pasos donde quiero agregar alguna lógica de procesamiento genérica antes y después de la invocación del paso. Para esta envoltura genérica, necesito acceder a los argumentos que se pasaron y, por lo tanto, necesito acceso a la matriz de argumentos en lugar de declarar parámetros explícitos. Actualmente, el pepino simplemente me impediría hacer esto, ya que realiza una verificación estricta de los parámetros.

Me gustaría proponer agregar otro parámetro de configuración al objeto de opciones llamado tal vez algo como skipStrictParameterCheck que si no se establece se asumirá como falso. De esa manera, para el uso más común, el comportamiento predeterminado sería comprobaciones estrictas, pero para otros que quieran usar el marco para construir algo más a su alrededor, les da la flexibilidad para aprovechar algunas de las capacidades dinámicas de JavaScript.

Todos 14 comentarios

Hice la misma solicitud, consulte el n. ° 445 :)

@ riaan53 , sí, eché un vistazo a eso y, desafortunadamente, la solicitud fue rechazada debido quizás a algunas de las razones correctas. Si bien no estoy en desacuerdo con el razonamiento, sí creo que, como parte de la construcción de un marco más grande, esta característica es importante siempre que los usuarios no abusen de ella, de ahí mi recomendación de hacer de esto una configuración estrictamente opcional.

¿Cuál es parte de esta lógica de procesamiento genérica de la que estás hablando?

Seguro. En un caso de uso, quiero que todos los desarrolladores puedan usar referencias de expresión en sus definiciones de pasos. por ejemplo, 'Cuando el usuario $ {admin} inicia sesión en el sistema'.

Ahora, en este caso, $ {admin} se resolvería tal vez desde un archivo JSON y la responsabilidad de la resolución sería del desarrollador al implementar la definición del paso. Sin embargo, si realmente observa este tipo de resolución de propiedad, esto se puede hacer mediante código genérico sin que el desarrollador lo sepa en absoluto.

Para permitir esto, puedo crear fácilmente un envoltorio de función genérico alrededor de las implementaciones del paso del desarrollador que aceptaría los argumentos sin procesar inyectados por Cucumber, los resolvió y luego inyectaría los valores resueltos en la implementación del paso real.

Actualmente, no podría escribir una función tan genérica ya que las validaciones de Cucumber se asegurarían de que la función tenga el número correcto de parámetros que, en mi caso, no será ya que mi función de envoltura genérica no aceptaría argumentos con nombre y estaría usando el objeto 'argumentos'.

Espero poder tener sentido. También hay otros casos de uso en nuestro proyecto actual en los que se necesitaría una función de envoltura genérica alrededor de las implementaciones de pasos

Si implementamos transformaciones de argumentos de paso, ¿eso resolvería su problema: https://github.com/cucumber/cucumber/wiki/Step-Argument-Transforms?

Ah, sí, potencialmente podría abordar el problema de resolución de referencia. Sin embargo, todavía tengo la necesidad de poner una función de envoltura genérica alrededor de las implementaciones de pasos por otras razones.

Por ejemplo, una razón es que estoy usando Transportador con Cucumber como marco y en Transportador, uno tiene que registrar promesas personalizadas en el flujo de control de WebDriver para que espere diligentemente a que estas promesas personalizadas se resuelvan antes de continuar con el siguiente paso.

Si bien Cucumber espera a que se resuelva una promesa (si se devuelve de la implementación del paso), obviamente no es compatible con WebDriver y, por lo general, necesitamos agregar código adicional a la implementación de cada paso para registrar la promesa devuelta con WebDriver.

De nuevo, esto es algo muy fácil de abordar a través de una envoltura de función genérica para la cual Cucumber debe relajar la verificación de parámetros.

Me encontré con este problema un par de veces.

El más destacado es implementar un retry helper en cada Then paso:

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

Utilizo este ayudante para lidiar con problemas de sincronización en un backend eventualmente consistente. Esencialmente, ejecuta la devolución de llamada cada x segundos hasta que hayan pasado y segundos o haya pasado la devolución de llamada.

Lamentablemente, esto causa

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

La alternativa que estoy usando ahora es llamar al ayudante en cada Then paso, lo que provoca MUCHA duplicación de código.

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

Otro caso es uno más simple en el que quiero una función auxiliar para iniciar sesión.

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

Además, este me ahorraría mucha duplicación de código.

Finalmente, espero que en algún momento desee agregar una suite que ejecute todas mis pruebas de aceptación, pero reinicie el servidor antes de cada devolución Then llamada

PD El ayudante de reintento:

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

_Editar_

Traté de hackear mi camino a mi alrededor:

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

pero

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

haciéndome un panda triste

A continuación, se muestra un ejemplo de cómo ajustar una función para conservar la longitud. Sería bueno si solo hubiera un pequeño módulo de nodo que hiciera esto por usted.

¡Gracias por la sugerencia! Eso funciona si especifica el número de argumentos por definición de paso, ¿verdad?

p.ej.

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

El problema más urgente para mí es no poder agregar middleware genérico para definiciones de varios pasos. Algo como createProxy tal vez podría funcionar si lo convirtiera en un objeto que permita el registro de middleware y luego le diga el número de argumentos en cada definición de paso. (Mire de cerca mi primer ejemplo y verá que no puedo usar directamente createProxy , porque la función retry ajustará. Debería ser al revés, pero luego createProxy no sabrá el número de argumentos para cada devolución de llamada)

Sin embargo, todavía se siente realmente incómodo, en comparación con poder desactivar el error. :inocente:

Creo que puede usar una variante de esa función en la que, en lugar de pasar proxyLength , simplemente pasa la función que está ajustando y usa function.length .

¡Gran sugerencia, gracias!

Tengo algo como lo siguiente para funcionar:

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

Dos cosas que no resuelve son el caso del asistente de inicio de sesión y permitir los parámetros predeterminados, pero puedo solucionar esos dos.

¡Me alegro de que ahora puedo agregar middleware sin ajustes a mis definiciones de pasos!

@thomasvanlankveld Para que lo sepas, encontré esta biblioteca que envuelve una función para darle una longitud de función específica: https://github.com/blakeembrey/arity

@ sushil-rxr ¿podría algo como en su envoltorio de función genérica para retener la longitud de la función original?

En 2.0.0-rc.1 ahora puede agregar un contenedor de función genérico. También tiene la funcionalidad incorporada de conservar la longitud de la función original.

Este hilo se ha bloqueado automáticamente ya que no ha habido ninguna actividad reciente después de que se cerró. Abra un nuevo problema para errores relacionados.

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

Temas relacionados

igniteram picture igniteram  ·  7Comentarios

nicojs picture nicojs  ·  3Comentarios

lamartire picture lamartire  ·  6Comentarios

dblooman picture dblooman  ·  7Comentarios

jfstephe picture jfstephe  ·  4Comentarios