Cucumber-js: Uma opção para relaxar os argumentos, verifique as definições das etapas

Criado em 12 fev. 2016  ·  14Comentários  ·  Fonte: cucumber/cucumber-js

Olá, Embora eu compreenda o valor da verificação estrita que está sendo feita para os argumentos da etapa, também sinto que é necessário passar esse controle de volta para os usuários.

Por exemplo, no meu caso, desejo implementar funções de invólucro genéricas em torno das funções de implementação da etapa onde desejo adicionar alguma lógica de processamento genérica antes e depois da invocação da etapa. Para este wrapper genérico, preciso acessar apenas quaisquer argumentos que foram passados ​​e, portanto, preciso acessar a matriz de argumentos em vez de declarar quaisquer parâmetros explícitos. Atualmente, o pepino apenas me impediria de fazer isso, pois faz uma verificação rigorosa dos parâmetros.

Eu gostaria de propor a adição de outro parâmetro de configuração ao objeto de opções chamado talvez algo como skipStrictParameterCheck que, se não definido, seria considerado falso. Dessa forma, para o uso mais comum, o comportamento padrão seria verificações estritas, mas para outros que desejam usar a estrutura para construir algo mais em torno dela, isso lhes dá a flexibilidade de aproveitar alguns dos recursos dinâmicos do JavaScript.

Todos 14 comentários

Fiz o mesmo pedido, consulte # 445 :)

@ riaan53 , sim, eu dei uma olhada nisso e, infelizmente, a solicitação foi rejeitada devido, talvez, a alguns dos motivos certos. Embora eu não discorde do raciocínio, acho que, como parte da construção de uma estrutura maior, esse recurso é importante, desde que os usuários não abusem dele, por isso minha recomendação de torná-lo uma parte estritamente opcional da configuração.

O que é parte dessa lógica de processamento genérica de que você está falando?

Certo. Em um caso de uso, quero que todos os desenvolvedores possam usar referências de expressão em suas definições de etapa. por exemplo, 'Quando o usuário $ {admin} efetua login no sistema'.

Agora, neste caso, $ {admin} seria resolvido talvez a partir de um arquivo JSON e a responsabilidade pela resolução seria do desenvolvedor durante a implementação da definição da etapa. se você realmente olhar para esse tipo de resolução de propriedade, no entanto, isso pode ser feito por um código genérico, sem que o desenvolvedor esteja ciente disso.

Para permitir isso, posso criar facilmente um wrapper de função genérico em torno das implementações da etapa do desenvolvedor que aceitaria os argumentos brutos injetados pelo Cucumber, resolveria e então injetaria os valores resolvidos na implementação da etapa real.

Atualmente, eu não seria capaz de escrever uma função genérica porque as validações de Cucumber garantiriam que a função tivesse o número certo de parâmetros que, no meu caso, não seria, pois minha função de wrapper genérica não aceitaria nenhum argumento nomeado e eu estaria usando o objeto 'argumentos'.

Espero que eu possa fazer sentido. Existem outros casos de uso também em nosso projeto atual onde uma função de wrapper genérica em torno das implementações de etapas seria necessária

Se implementássemos transformações de argumento de etapa, isso resolveria seu problema: https://github.com/cucumber/cucumber/wiki/Step-Argument-Transforms?

Ah sim, poderia potencialmente resolver o problema de resolução de referência. No entanto, ainda tenho a necessidade de colocar uma função de wrapper genérica em torno das implementações de etapas por outros motivos.

Por exemplo, um dos motivos é que estou usando o Protractor com Cucumber como estrutura e, no Protractor, é necessário registrar as promessas personalizadas no fluxo de controle do WebDriver para que ele aguarde diligentemente que essas promessas personalizadas sejam resolvidas antes de prosseguir para a próxima etapa.

Embora o Cucumber espere que uma promessa seja resolvida (se retornada da implementação da etapa), obviamente não reconhece o WebDriver e, na maioria das vezes, precisamos adicionar código adicional a cada implementação da etapa para registrar a promessa retornada com o WebDriver.

Novamente, isso é muito fácil de resolver por meio de um wrapper de função genérico, para o qual a verificação de parâmetro deve ser relaxada pelo Cucumber.

Eu encontrei esse problema algumas vezes agora.

O mais proeminente é a implementação de um ajudante retry em cada Then passo:

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

Eu uso esse auxiliar para lidar com problemas de tempo em um back-end eventualmente consistente. Basicamente, ele executa o retorno de chamada a cada x segundos até que y segundos tenham se passado ou o retorno de chamada seja aprovado.

Infelizmente, isso causa

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

A alternativa que estou usando agora é chamar o helper a cada Then passo, o que causa MUITA duplicação 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;
  });
});

Outro caso é mais simples em que desejo uma função auxiliar para login.

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

Além disso, este me pouparia muita duplicação de código.

Finalmente, espero em algum momento querer adicionar um pacote que execute todos os meus testes de aceitação, mas reinicie o servidor antes de cada Then callback (para ter certeza de que as reinicializações do servidor não bagunçam as coisas). Novamente, muita duplicação.

PS O auxiliar de nova tentativa:

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_

Tentei hackear minha maneira de contornar isso:

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

mas

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

me fazendo um panda triste

Aqui está um exemplo de encapsulamento de uma função para reter o comprimento. Seria bom se houvesse apenas um pequeno módulo de nó que fizesse isso para você.

Obrigado pela sugestão! Isso funciona se você especificar o número de argumentos por definição de etapa, certo?

por exemplo.

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

O problema mais urgente para mim é não ser capaz de adicionar middleware genérico para várias definições de etapas. Algo como createProxy poderia funcionar se eu transformasse em um objeto que permite o registro de middleware e, em seguida, contasse a ele o número de argumentos em cada definição de etapa. (observe atentamente meu primeiro exemplo e você verá que não posso usar createProxy diretamente, porque a função retry irá envolvê-lo. Deveria ser o contrário, mas então createProxy não saberá o número de argumentos para cada retorno de chamada)

Ainda assim, parece muito estranho, em comparação a ser capaz de desligar o erro. :inocente:

Acho que você pode usar uma variante dessa função onde, em vez de passar proxyLength você apenas passa a função que está encapsulando e usa function.length .

Ótima sugestão, obrigado!

Eu tenho algo parecido com o seguinte 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);
  }
}

Duas coisas que ele não resolve é o caso do auxiliar de login e a permissão de parâmetros padrão, mas posso contornar esses dois.

Ainda bem que agora posso adicionar middleware sem ajustes nas minhas definições de etapa!

@thomasvanlankveld Só para você saber, eu encontrei esta biblioteca que envolve uma função para dar a ela um comprimento de função específico: https://github.com/blakeembrey/arity

@sushil-rxr você poderia usar algo como em seu invólucro de função genérico para reter o comprimento da função original?

Em 2.0.0-rc.1 agora você pode adicionar um wrapper de função genérico. Ele também possui a funcionalidade embutida de reter o comprimento da função original.

Este tópico foi bloqueado automaticamente, pois não houve nenhuma atividade recente depois que foi fechado. Abra um novo problema para bugs relacionados.

Esta página foi útil?
0 / 5 - 0 avaliações