BT

Disseminando conhecimento e inovação em desenvolvimento de software corporativo.

Contribuir

Tópicos

Escolha a região

Início Artigos Opinião: O Cucumber ainda tem o seu valor

Opinião: O Cucumber ainda tem o seu valor

Nos últimos meses, tenho percebido uma tendência da comunidade Ruby em valorizar alternativas "mais minimalistas" de testes de aceitação. Com minimalista, refiro-me a opções em que as histórias das funcionalidades são escritas diretamente em Ruby, em vez de usando alguma DSL próxima de uma linguagem natural.

As alternativas mais citadas são Steak/Capybara e specs de Requests do Rspec. São ferramentas que têm se tornado populares em substituição ao Cucumber, que reinou como principal ferramenta de testes de aceitação nos últimos anos.

Eu mesmo defendi essas alternativas em posts no meu blog e em palestras nos eventos Oxente Rails e Ruby on Rio. No entanto, depois de passar quase um ano utilizando Steak/Capybara e RSpec, estou voltando a adotar o Cucumber como padrão para escrita de testes de aceitação. Neste artigo, apresento as razões me levaram a isso, aproveitando para mostrar em funcionamento o Cucumber e uma das alternativas, bem como a filosofia por trás de cada uma.

Gostaria de deixar claro, no entanto, que este artigo não tem objetivo de tentar convencer o leitor do que é melhor ou pior. A intenção é apenas compartilhar um pouco das minhas experiências com as ferramentas e os seus benefícios mais claros, e deixar a decisão do que usar para você e sua equipe.

Entendendo as diferenças

O Cucumber é uma Gem que, ao ser instalada, cria um novo ambiente no projeto e permite a escrita de testes de aceitação em uma linguagem muito próximo da natural. O idioma usado será inglês nos exemplos adiante, mas podem ser usadas palavras em português ou outra língua. Com o Cucumber, é possível escrever testes como no arquivo .feature abaixo:

Feature: Page creation 
    As a site owner 
    I want to add a new page to my site 
    So that I can share some infos about me on the internet 

    Scenario: successful submission 
        Given I'm on the page creation screen 
        When I add a new page with title About Me 
        Then I should see the notice 'Your new page was successfully created!' 
        And the collection of pages with About Me included

Esse código descreve a funcionalidade para criar uma nova página em um CMS. No entanto, por ser escrito em texto simples, é preciso ensinar o Cucumber a processar cada uma das frases, para que a documentação se torne executável. Isso é feito através dos Steps. Os Steps, para esse exemplo, poderiam ser escritos em um arquivo como este:

# alguns dos steps como "Given I'm on the page X"
# já são criados pelo Cucumber, por isso não são incluídos aqui 
When "I add a new page (\w+)" do |page_title| 
    fill_in 'Title', :with => page_title 
    fill_in 'Permalink', :with => 'about-me' 
    fill_in 'Body', :with => 'Lorem ipsum dolor sit amet...' 
    check 'Visible' 
    click_button 'Create' 
end 
And "the (\w+) with (\w+) included" do |page_name, new_resource| 
    visit path_to(page_name) unless page.current_path == path_to(page_name) 
    within ".grid" do 
        page.should have_content(new_resource) 
    end
end

Ou seja, o processo comum no Cucumber é escrever a história e depois os passos para torná-la executável (quando o assunto é Behavior Driven Development estamos falando de documentação executável!). Vejamos agora um exemplo com Capybara e sua DSL para testes de aceitação:

require 'acceptance/acceptance_helper' 
feature "Page Creation", %q{ As a site owner 
I want to add a new page to my site 
So that I can share some infos about me on the internet } do 
    scenario "successful submission" do 
        visit '/pages/new' 
        fill_in 'Title', :with => 'About me' 
        fill_in 'Permalink', :with => 'about-me' 
        fill_in 'Body', :with => 'Lorem ipsum dolor sit amet...' 
        check 'Visible' 
        click_button 'Create' 
        within ".notice" do 
            page.should have_content('Your new page was successfully created!') 
        end 
        within ".grid" do 
            page.should have_content("About me") 
        end 
    end 
end

Aqui, o único passo adicional necessário é escrever diretamente quais comandos devem ser executados para garantir a aceitação da funcionalidade "Page Creation". Fazendo um paralelo com o Cucumber, é como se precisássemos escrever apenas os Steps. E como Ruby é uma linguagem especialmente legível, o código também é amigável.

Uma observação é que o código com Capybara faz referência para vários detalhes de implementação, enquanto o código do Cucumber reserva isso apenas para os Steps e não para o arquivo de feature.

Meu cliente não escreve as histórias

Um dos grandes benefícios destacados pelos criadores do Cucumber é o fato de que, sendo o arquivo de feature (que descreve a história) muito próximo da linguagem natural, o próprio cliente poderia descrever suas necessidades e passá-las à equipe de desenvolvimento. E a equipe, fazendo pequenos ajustes, já poderia implementar os Steps. Porém, na prática isso raramente ocorre. Um dos grandes desafios em desenvolvimento ágil é reduzir o ciclo de feedback entre o cliente e os desenvolvedores.

Se já é complicado fazer isso com cartões e post-its, pedir ao cliente que escreva as funcionalidades em uma DSL específica é um passo além, que exige um esforço de aprendizado do cliente: um passo que o cliente raramente terá disponibilidade realizar. E se na grande maioria dos casos o cliente não escreve as histórias do Cucumber, parece um desperdício de trabalho o desenvolvedor escrever esse arquivo, para depois escrever o real código de testes (os Steps).

Assim, é natural pensar que se pode remover o primeiro passo e passar diretamente à implementação executável. Ou seja, partir para alternativas como Steak/Capybara e deixar o Cucumber de lado. No entanto, voltemos para um dos objetivos do Behavior Driven Development que conhecemos hoje, e que é bem descrito no RSpec Book, de David Chelismky.

Trata-se da prática que diz que em BDD devemos seguir um fluxo Outside-In (de fora para dentro) Seguindo esse fluxo, devemos começar em um nível mais macro, coletando as necessidades do cliente (seja em cartões, em post-its, em um software, ou em arquivos de feature do Cucumber) – e só depois implementar o que deveriam ser os passos para essa funcionalidade ser válida. Apenas após o levantamento de requisitos deveríamos passar para um nível mais interno, guiado por testes unitários, até termos os testes de aceitação passando – finalizando o requisito do cliente e integrando, para que o próprio cliente possa validar o benefício do código escrito.

O que podemos observar como objetivo de um fluxo Outside-in é aproximar o cliente o máximo possível do desenvolvimento, além de reduzir o ciclo de feedback. Isso maximiza a chance da aprovação do cliente em uma primeira tentativa de desenvolvimento, porque a funcionalidade, de forma granular, foi descrita pelo próprio cliente.

Independentemente de o cliente escrever ou não o arquivo do Cucumber, deve-se ter em mente que precisamos coletar as histórias que descrevem as necessidades do cliente de forma granularizada, e armazenar isso de alguma forma. Para isso, utilizo o Trajectory e depois transformo a mensagem do meu cliente em um arquivo executável.

Na verdade, não importa onde e de que forma você vai coletar as histórias. Pode-se utilizar desde um software sofisticado a uma folha de papel; mas depois será necessário transformar isso em um teste de aceitação executável que seja o mais legível possível. Dessa forma, não há grande diferença entre fazer um copiar/colar da mensagem do cliente para um arquivo de feature do Cucumber, ou copiar para um arquivo .rb do Capybara. O trabalho é basicamente o mesmo, já que vai ser preciso fazer a adequação do que o cliente disse para a sintaxe do Capybara/Steak ou a do Cucumber.

Na prática, portanto, o que interessa mesmo não é se o cliente escreve ou não o arquivo de feature do Cucumber, mas sim aproximar o cliente. Isso normalmente é feito em uma conversa (virtual ou física), em que as necessidades são anotadas e transferidas para os testes do software – e o fluxo deve ser assim com o Cucumber ou com qualquer outra ferramenta.

Outro aspecto que podemos observar é que, se no seu ambiente o cliente não irá escrever as features do Cucumber, tanto faz se esses arquivos usam palavras em português ou inglês. Pessoalmente, prefiro escrever o meu software todo em inglês, inclusive os testes (que trato como documentação).

Pontos onde o Cucumber se sai melhor

Já que com ou sem Cucumber será necessário digitar as funcionalidades nos testes de aceitação, reduzir um passo nesse processo pode parecer mais produtivo. Ou seja, pular o passo de escrever a funcionalidade em texto corrido e passar direto à execução. No entanto, o que não parece óbvio à primeira vista é que, ao escrever a funcionalidade em texto e depois os passos, teremos duas camadas de abstração. Vamos ver as vantagens disso.

Planejamento

O primeiro benefício é que, ao passar as necessidades do cliente para um arquivo de feature, pode-se pensar apenas no valor de negócio e excluir completamente detalhes de implementação. Dessa forma, ganhamos um momento de reflexão e planejamento de como aquilo deve se comportar, independentemente de campos, botões ou URLs.

Como mostrado no primeiro exemplo, no arquivo de feature não existe nada que especifique a implementação. Há apenas detalhes do comportamento. Percebo que não escrever diretamente os passos de execução me ajuda a focar no objetivo final, ao invés de pensar em código Ruby, nomes de campos, links, botões e endereços (o que ainda não deveria importar, já que estamos em um nível mais macro do ciclo de BDD).

Reaproveitamento

Outra grande vantagem de se ter duas camadas de abstração ao invés de apenas uma é o ganho em reutilização. Manter um fluxo de navegação e de uso semelhante na aplicação toda é uma boa prática de design de interfaces. Rapidamente, será possível perceber que, à medida que seu sistema cresce, os elementos visuais na grande maioria das telas se repetem (grids, accordions na posição X, abas na posição Y, mensagens com uma determinada classe de CSS etc). Características dos fluxos também se repetem, por exemplo: "ao criar um registro o usuário volta para a listagem de registros e aparece uma mensagem de sucesso".

Ao utilizar o Cucumber, você perceberá que nas primeiras semanas será necessário "mapear" todas essas coisas com os Steps, para interagir com os elementos e fluxos. No entanto, rapidamente os passos se tornarão repetitivos e será posssível reutilizar esses arquivos em vários outros pontos do projeto. Em pouco tempo, escrevendo as histórias de forma mais declarativa e menos imperativa, poucas vezes será necessário escrever novos arquivos de passos.

A grande vantagem é que o tempo gasto para escrever um teste de aceitação se torna menor e mais legível (querendo ou não, ler código Ruby ainda exige um esforço cognitivo um pouco maior do que linguagem natural). Algumas pessoas podem argumentar que em alternativas como Capybara/Steak é possível criar helpers, mas isso ainda é uma abstração que rapidamente pode se tornar caótica, dependendo do tamanho do projeto. É também menos flexível, e terminará criando métodos como should_have_grid_with_first_row('xyz'))

No exemplo mostrado para o uso do Cucumber, já é possível perceber dois passos que se tornarão reaproveitáveis para várias das próximas funcionalidades:

Then I should see the notice 'Your new page was successfully created!' 
And the collection of pages with About Me included

O passos para execução destas linhas serão suficientes para também descrever coisas como:

Then I should see the notice 'Your new theme was successfully created!' 
And the collection of themes with Minimalist Theme included

 

Conclusões

Depois de um ano usando alternativas como Steak/Capybara, concluí que, contanto que as funcionalidades do Cucumber sejam descritas de forma organizada e mais declarativa, acaba-se sendo mais produtivo em duas linhas: no planejamento focado no benefício ao invés de em detalhes de implementação; e no alto reaproveitamento em fluxos repetitivos, sem a necessidade de dezenas de helpers. Além disso, não importa se seu cliente escreve ou não arquivos de feature; o importante é ter um cliente ativo durante o desenvolvimento.

Poder integrar o Cucumber nesse processo é apenas uma das possibilidades, e não algo obrigatório quando se usa a ferramenta. Espero que esse artigo o ajude a pensar nos reais motivos para o uso de cada ferramenta e os seus benefícios, além de verificar o que se aproxima mais da realidade da sua equipe – contribuindo para um software de maior qualidade.


Sobre o autor

Daniel Lopes (@danielvlopes) trabalha com desenvolvimento de software há quase uma década e é sócio da Objetiva Software. Atua na maior parte do seu tempo como designer de interfaces e desenvolvedor Ruby/Frontend. É instrutor de Ruby pela eGenial desde 2007 e professor de Pós-graduação na UNA-BH.

Avalie esse artigo

Relevância
Estilo/Redação

Conteúdo educacional

BT