Reutilizar etapas do pepino

103

Quero reutilizar alguns passos do Pepino, mas não consigo encontrar o caminho certo.

Eu quero escrever uma etapa como:

Given /^I login with (.*) credentials$/ |type|
  # do stuff with type being one of "invalid" or "valid"
end

Mas então dê outra etapa como:

Given /^I login successfully$
  # call "Given I login with valid credentials"
end

Portanto, ao testar a autenticação do usuário, posso usar o primeiro, mas na maioria dos outros lugares, posso usar o último e não preciso reproduzir o código.

Existe uma maneira de chamar essa outra etapa ou apenas coloco a lógica em um método auxiliar e chamo esse método de cada tarefa (basicamente uma refatoração de extração de método, que, após ler minha pergunta, me faz acreditar que é realmente a melhor maneira de qualquer forma)?

Daniel Huckstep
fonte
1
Caso alguém esteja confuso, todos aqui estão deixando de fora o donecessário para iniciar o do...endbloco na definição da etapa Ruby. Na verdade, é necessário.
Shaun Lebron

Respostas:

102

ATUALIZAÇÃO : o método descrito abaixo foi descontinuado. A maneira recomendada de chamar uma etapa de dentro de outra etapa agora se parece com esta:

Given /^I login successfully$/
    step "I login with valid credentials" 
end 

Método antigo e obsoleto (para referência):

Você pode chamar etapas de outras etapas como esta:

Given /^I login successfully$/
  Given "I login with valid credentials"
  Then "I should be logged in"
end

Se todos os cenários dentro de um recurso exigirem esta (ou outras etapas), você também pode adicionar um Plano de Fundo a cada recurso, com as etapas comuns, como:

Background:
  Given I log in with valid credentials

Scenario: Change my password
  Given I am on the account page
tomafro
fonte
5
Ainda mais fácil é colar o código de pepino da seguinte maneira:steps %Q{Given I am logged in}
BrendanDean
1
@BrendanDean Quando essa resposta foi aceita, o stepsmétodo não existia. Veja minha resposta abaixo.
michaeltwofish
Observe que as etapas de conjunção agora são consideradas um antipadrão e devem ser evitadas. Veja o wiki Cucumber - cucumber.io/docs/guides/anti-patterns/…
Jan Molak
103

Observe que o método para chamar as etapas dentro das etapas mudou nas versões recentes do pepino, que você verá se receber um erro como "AVISO: o uso de 'Dado / Quando / Então' nas definições da etapa está obsoleto, use 'etapa' para chame outras etapas: /path/to/step_definitions/foo_steps.rb: 631: in `block in '". Veja a wiki do pepino para detalhes.

A essência da mudança é que agora você deve usar os métodos stepou steps.

When /^I make all my stuff shiny$/
  step "I polish my first thing"
end

When /^I make all my stuff shiny$/
  steps %Q{
    When I polish my first thing
    When I shine my second thing
  }
end
michaeltwofish
fonte
18
Pelo que vale a pena, depois de mais tempo com Cucumber, recomendo não usar etapas dentro de etapas. Os problemas são difíceis de rastrear e, na verdade, tornam a manutenção mais difícil. Em vez disso, use métodos auxiliares.
michaeltwofish
2
Talvez você deva incluir este comentário em sua resposta, pois foi muito votado e ainda recebe votos. Isso ajudará as pessoas a perceberem essa informação
Andrei Botalov
oi @michaeltwofish, houve alguma mudança nisso em 2017? Estou recebendo syntax error, unexpected tIDENTIFIER, expecting keyword_end stackoverflow.com/questions/43319331/…
ericn
43

Chamar etapas a partir de definições de etapas é uma prática ruim e tem algumas desvantagens :

  1. Se o cenário falhar e houver chamadas de etapas aninhadas, você obterá apenas a última definição de etapa chamada no rastreamento de pilha. Pode ser difícil descobrir de qual lugar esse último stepdef foi chamado
  2. Call to stepdef às vezes é mais difícil de encontrar e ler do que o método ruby
  3. Os métodos Ruby oferecem mais poder do que chamar as etapas da etapa defs

Aslak Hellesøy recomenda extrair ações populares para o World em vez de reutilizar etapas. Ele isola essas ações em um lugar, torna este código mais fácil de localizar. Você também pode extrair código para classes ou módulos Ruby usuais.

#/support/world_extensions.rb
module KnowsUser
  def login
    visit('/login')
    fill_in('User name', with: user.name)
    fill_in('Password', with: user.password)
    click_button('Log in')
  end

  def user
    @user ||= User.create!(:name => 'Aslak', :password => 'xyz')
  end
end
World(KnowsUser)

#/step_definitions/authentication_steps.rb
When /^I login$/ do
  login
end

Given /^a logged in user$/ do
  login
end

Aqui está uma discussão útil sobre o assunto na lista de discussão do Pepino - link

Andrei Botalov
fonte
2
Eu acredito que essa abordagem é muito melhor do que chamar as funções step ou steps pelos mesmos motivos mencionados acima.
pisaruk
2
Isso tem outro benefício. Usando o Idea (ou Rubymine), você pode facilmente pular para as definições de função, mas não para as etapas nas etapas% {...}.
slipset de
também esta configuração segue o princípio DRY
Sorcerer86pt
2
Embora eu tenha encontrado o problema de reutilizar etapas, acho que isso é simplesmente ruim. O login é apenas a soma de diferentes etapas: "visite algo", "preencha algo". A maneira natural seria reutilizar as etapas, em vez de converter cada etapa em uma chamada para uma função. IMO, chamar as etapas dentro das etapas deve apenas ser melhorado.
dgmora
9

Melhor envolver suas etapas em% {} em vez de aspas. Então, você não precisa escapar as aspas duplas, que você precisará usar com freqüência:

Given /^I login successfully$
  step %{I login with valid credentials}
end

Given /^I login with (.*) credentials$/ |type|
  # do stuff with type being one of "invalid" or "valid"
end
Rimian
fonte
5
Isso deveria ter sido um comentário em vez de uma resposta.
Kelvin de
1

Reutilize palavras-chave no arquivo de recurso que fornecerá capacidade de reutilização de código.

É altamente NÃO recomendado chamar step defs dentro de step defs.

Eu escreveria meu arquivo de recurso desta forma,

Scenario Outline: To check login functionality
    Given I login with "<username>" and "<password>"
    Then I "<may or may not>" login successfully

Examples:
    |username|password|may or may not|
    |paul    |123$    |may           |
    |dave    |1111    |may not       |

Na minha definição de etapa, (isto é Java)

@Given(I login with \"([^\"]*)\" and \"([^\"]*)\"$)
public void I_login_with_and(String username, String password){

   //login with username and password

}

@Then(I \"([^\"]*)\" login successfully$)
public void I_login_successully_if(String validity){

    if(validity.equals("may")){
        //assert for valid login
    }
    else
    if(validity.equals("may not")){
        //assert for invalid login
    }
}

Dessa forma, há muita capacidade de reutilização do código. Seu mesmo Dado e Então lida com cenários válidos e inválidos. Ao mesmo tempo, seu arquivo de feição faz sentido para os leitores.

LINGS
fonte