Cucumber-js: Une option pour assouplir la vérification des arguments pour les définitions d'étape

Créé le 12 févr. 2016  ·  14Commentaires  ·  Source: cucumber/cucumber-js

Bonjour, Bien que je comprenne la valeur de la vérification stricte effectuée pour les arguments d'étape, je pense également qu'il est nécessaire de retransmettre ce contrôle aux utilisateurs.

Par exemple, dans mon cas, je souhaite implémenter des fonctions d'encapsulation génériques autour des fonctions d'implémentation d'étape où je souhaite ajouter une logique de traitement générique avant et après l'invocation de l'étape. Pour ce wrapper générique, j'ai besoin d'accéder à tous les arguments passés et j'ai donc besoin d'accéder au tableau des arguments plutôt que de déclarer des paramètres explicites. Actuellement, le concombre m'empêcherait de le faire car il vérifie strictement les paramètres.

Je voudrais proposer d'ajouter un autre paramètre de configuration à l'objet d'options appelé peut-être quelque chose comme skipStrictParameterCheck qui, s'il n'est pas défini, serait considéré comme faux. De cette façon, pour l'utilisation la plus courante, le comportement par défaut serait des vérifications strictes, mais pour les autres qui souhaitent utiliser le framework pour construire quelque chose de plus autour de celui-ci, cela leur donne la possibilité de tirer parti de certaines des capacités dynamiques de JavaScript.

Tous les 14 commentaires

J'ai fait la même demande, voir #445 :)

@ riaan53 , oui, j'y ai jeté un coup d'œil et malheureusement, la demande a été rejetée pour peut-être certaines des bonnes raisons. Bien que je ne sois pas en désaccord avec le raisonnement, je pense que dans le cadre de la construction d'un cadre plus large, cette fonctionnalité est importante tant que les utilisateurs n'en abusent pas, d'où ma recommandation d'en faire une configuration strictement facultative.

De quelle logique de traitement générique parlez-vous ?

Sûr. Dans un cas d'utilisation, je souhaite que tous les développeurs puissent utiliser des références d'expression dans leurs définitions d'étape. par exemple 'Lorsque l'utilisateur ${admin} se connecte au système'.

Maintenant, dans ce cas, ${admin} serait peut-être résolu à partir d'un fichier JSON et la responsabilité de la résolution incomberait au développeur lors de l'implémentation de la définition de l'étape. si vous regardez vraiment ce type de résolution de propriété, cela peut être fait par un code générique sans que le développeur en soit conscient.

Pour permettre cela, je peux facilement créer un wrapper de fonction générique autour des implémentations de l'étape du développeur qui accepterait les arguments bruts injectés par Cucumber, les résoudrait, puis injecterait les valeurs résolues dans l'implémentation réelle de l'étape.

Actuellement, je ne serais pas en mesure d'écrire une fonction aussi générique que les validations de Cucumber s'assureraient que la fonction a le bon nombre de paramètres, ce qui dans mon cas ne sera pas car ma fonction wrapper générique n'accepterait aucun argument nommé et j'utiliserais le objet 'arguments'.

J'espère que je pourrais avoir du sens. Il existe également d'autres cas d'utilisation dans notre projet actuel où une fonction wrapper générique autour des implémentations d'étape serait nécessaire

Si nous implémentions des transformations d'argument étape, cela résoudrait-il votre problème : https://github.com/cucumber/cucumber/wiki/Step-Argument-Transforms ?

Ah oui, pourrait potentiellement résoudre le problème de résolution de référence. Cependant, j'ai toujours besoin de mettre une fonction wrapper générique autour des implémentations d'étape pour d'autres raisons.

Par exemple, une des raisons est que j'utilise Protractor avec Cucumber comme framework et dans Protractor, il faut enregistrer des promesses personnalisées dans le flux de contrôle WebDriver afin qu'il attende avec diligence que ces promesses personnalisées soient résolues avant de passer à l'étape suivante.

Alors que Cucumber attend qu'une promesse soit résolue (si elle est renvoyée par l'implémentation d'étape), il n'est évidemment pas conscient de WebDriver et le plus souvent, nous devons ajouter du code supplémentaire à chaque implémentation d'étape pour enregistrer la promesse renvoyée avec WebDriver.

C'est encore une fois une chose très facile à résoudre via un wrapper de fonction générique pour lequel la vérification des paramètres doit être assouplie par Cucumber.

J'ai rencontré ce problème plusieurs fois maintenant.

La plus importante consiste à implémenter un assistant retry à chaque étape Then :

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

J'utilise cette aide pour traiter les problèmes de synchronisation sur un backend finalement cohérent. Essentiellement, il exécute le rappel toutes les x secondes jusqu'à ce que y secondes se soient écoulées ou que le rappel soit passé.

Malheureusement, cela provoque

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

L'alternative que j'utilise maintenant est d'appeler l'assistant à chaque étape Then , ce qui provoque BEAUCOUP de duplication de code.

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

Un autre cas est plus simple où je veux une fonction d'assistance pour la connexion.

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

De plus, celui-ci me permettrait d'économiser beaucoup de duplication de code.

Enfin, je m'attends à vouloir ajouter une suite qui exécute tous mes tests d'acceptation, mais redémarre le serveur avant chaque rappel Then (pour être sûr que les redémarrages du serveur ne gâchent pas les choses). Encore une fois, BEAUCOUP de doublons.

PS L'assistant de nouvelle tentative :

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

_Éditer_

J'ai essayé de pirater mon chemin:

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

mais

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

faisant de moi un panda triste

Voici un exemple d'encapsulation d'une fonction pour conserver la longueur. Ce serait bien s'il n'y avait qu'un petit module de nœud qui le faisait pour vous.

Merci pour la suggestion! Cela fonctionne si vous spécifiez le nombre d'arguments par définition d'étape, n'est-ce pas ?

par exemple.

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

Le problème le plus urgent pour moi est de ne pas pouvoir ajouter de middleware générique pour les définitions à plusieurs étapes. Quelque chose comme createProxy pourrait peut-être fonctionner si j'en faisais un objet qui permet l'enregistrement du middleware, puis lui dire le nombre d'arguments à chaque définition d'étape. (Regardez attentivement mon premier exemple et vous verrez que je ne peux pas utiliser directement createProxy , car la fonction retry l'enveloppera. Cela devrait être l'inverse, mais alors createProxy ne connaîtra pas le nombre d'arguments pour chaque rappel)

Cependant, cela semble toujours très gênant, par rapport à la possibilité de désactiver l'erreur. :innocent:

Je pense que vous pouvez utiliser une variante de cette fonction où au lieu de passer proxyLength vous passez simplement la fonction que vous encapsulez et utilisez function.length .

Super suggestion, merci !

J'ai quelque chose comme ce qui suit pour fonctionner:

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

Deux choses qu'il ne résout pas sont le cas de l'aide à la connexion et l'autorisation des paramètres par défaut, mais je peux contourner ces deux.

Heureux de pouvoir maintenant ajouter un middleware sans aucun ajustement à mes définitions d'étapes !

@thomasvanlankveld Juste pour que vous le sachiez, j'ai trouvé cette bibliothèque qui enveloppe une fonction pour lui donner une longueur de fonction spécifique : https://github.com/blakeembrey/arity

@sushil-rxr pourriez-vous quelque chose comme dans votre wrapper de fonction générique pour conserver la longueur de fonction d'origine?

Dans 2.0.0-rc.1 vous pouvez maintenant ajouter un wrapper de fonction générique. Il a également la fonctionnalité intégrée de conserver la longueur de la fonction d'origine.

Ce fil a été automatiquement verrouillé car il n'y a eu aucune activité récente après sa fermeture. Veuillez ouvrir un nouveau problème pour les bogues liés.

Cette page vous a été utile?
0 / 5 - 0 notes