Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Laravel
Code

Laravel, BDD e Você: A Primeira Funcionalidade

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Laravel, BDD And You.
Laravel, BDD and You: Let’s Get Started

Portuguese (Português) translation by Erick Patrick (you can also view the original English article)

Na segunda parte desta série chamada Laravel, BDD e Você, começaremos a descrever e construir nossa primeira funcionalidade, usando o Behat e o PhpSpec. No artigo anterior, preparamos tudo e vimos o quão fácil podemos interagir com o Laravel, usando cenários do Behat.

Recentemente, o autor do Behat, Konstantin Kudryashov (também conhecido como everzet), escreveu um ótimo artigo: Introduzindo Modelagem Através de Exemplos. O fluxo de trabalho que usaremos, ao construir essa funcionalidade, é bastante inspirada na apresentada por everzet. 

Em resumo, usaremos a mesma .feature para projetar tanto o domínio base quanto nossa interface de usuário. Sempre senti que tinha muita duplicidade em minhas características nos meus conjuntos de testes de aceitação/funcional e de integração. Ao ler a sugestão de everzet sobre usar as mesmas características para múltiplos contextos, tive uma epifania e acredito que esse seja o jeito certo de fazer as coisas. 

Em nosso caso, teremos o contexto funcional, que, por hora, também servirá como nossa camada de aceitação, e o contexto de integração, que cobrirá nosso domínio. Começaremos pela construção do domínio e, então, adicionaremos nossa interface de usuário e coisas específicas do framework mais para frente.

Pequenas Refatorações

Para usarmos a abordagem de "característica compartilhada, contextos múltiplos", precisamos realizar algumas refatorações em nosso projeto.

Primeiro, removeremos a feature de boas-vindas que fizemos no primeiro tutorial, uma vez que ela não é necessária, além de não seguir qualquer estilo genérico que precisamos para permitir os contextos múltiplos.

Segundo, colocaremos nossas características na raiz do diretório features. Assim, podemos remover o atributo path do nosso arquivo behat.yml. Também renomearemos o contexto LaravelFeatureContext para FunctionalFeatureContext (lembre-se de também renomear a classe correspondente):

Finalmente, para limpar um pouco as coisas, acredito que devemos mover todas as coisas relacionadas ao Laravel para uma trait própria:

Então podemos usar o trait que acabamos de criar, lá no contexto FunctionalFeatureContext, e remover as coisas que acabamos de copiar:

Os traits são uma ótima forma de deixar seus contextos mais limpos e simples.

Compartilhando a Característica

Como apresentado na parte um, construiremos uma pequena aplicação de marcação de tempo. A primeira característica será em relação à marcação do tempo em si e da geração de uma planilha com todos os tempos registrados. Eis a descrição da nossa característica:

Lembre-se, isso é só um exemplo. Eu acho mais fácil de definir uma as características em casos da vida real, uma vez que você possui um problema real para resolver e, geralmente, tem a chance de discutir as características com os colegas, clientes ou outros interessados.

Certo, deixemos o Behat gerar os passos do cenário para nós:

Precisamos ajustar os passos gerados só um pouco. Só precisamos de quatro passos para cobrir o cenário. O resultado final deve parece com isso:

Nosso contexto funcional está pronto para usar, mas também precisamos de um contexto para nosso conjunto de integração. Primeiro, adicionaremos o conjunto no arquivo behat.yml:

Agora, podemos apenas duplicar o contexto padrão, FeatureContext:

Lembre-se de renomear a classe para IntegrationFeatureContext e também copiar a declaração use para a exceção PendingException.

Por fim, como estamos compartilhando essa característica, podemos apenas copiar as quatro definições de passos do contexto funcional. Se executar o Behat, você verá que a característica é executada duas vezes: uma para cada contexto.

Projetando o Domínio

Já podemos continuar com os passos pendentes em nosso contexto de integração, para projetar o domínio base de nossa aplicação. O primeiro passo é Given A planilha abaixo com as marcações, seguida pela tabela com os registros dos tempos. Mantendo a simplicidade, iteremos pelos registros da tabela, tentando instanciar um objeto de registro de tempo (TimeEntry), adicionando-os a um vetor com todas as entradas no contexto:

Executar o Behat causará um erro fatal, uma vez que a classe TimeTracker\TimeEntry não existe ainda. É aqui que o PhpSpec entra em ação. No final das contas, TimeEntry será uma classe filha do Eloquent, mesmo que não nos preocupemos com ela ainda. O PhpSpec e ORMs como o Eloquent não se dão muito bem juntos, mas podemos usar o PhpSpec para gerar a classe e até especificar algum comportamento básico. Usemos os geradores do PhpSpec para gerar a classe TimeEntry:

Após a classe ser gerada, precisamos atualizar a seção de auto carregamento (autoload) do nosso arquivo composer.json:

E, claro, executar o comando composer dump-autoload.

Executando o PhpSpec nos dará tudo verde. Executando o Behat também. Ótimo começo!

Permitindo que o Behat guie nosso caminho, é hora de seeguir para o próximo passo, na hora de gerar a planilha de tempo, certo?

A palavra-chave aqui é "gerar", que parecer ser um termo de nosso domínio. No mundo de um programador, traduzir "gerar planilha de tempo" em código apenas significa instancia a classe TimeSheet com um conjunto de registros de tempo. É importante tentar aderir à linguagem do nosso domínio ao projetar nosso código. Dessa forma, nosso código ajudará a descrever o comportamento intendido da aplicação. 

Tomo o temo gerar como importante para o domínio, porque acredito que deveríamos ter um método estático de geração, na classe TimeSheet, que sirva como uma alternativa para o método construtor. Esse método receberia uma coleção de registros de tempo e os salvaria em uma planilha de tempos. 

Ao invés de usar um vetor, acredito que faça mais sentido usar a classe Illuminate\Support\Collection que vem com o Laravel. Uma vez que a classe TimeEntry é um modelo Eloquente, quando consultarmos a base de dados por registros, usaremos uma dessas coleções do Laravel de qualquer jeito. Que tal algo mais ou menos assim:

Por falar nisso, TimeSheet não será uma subclasse do Eloquent. Pelo menos, não por agora. Nós só precisamos que os registros de tempo sejam persistidos e a planilha de tempo será gerada a partir desses registros.

Executar o Behat, novamente, causará um erro fatal, porque a classe TimeSheet não existe. O PhpSpec pode nos ajudar com isso:

Ainda obtemos um erro fatal após criar a classe, porque o método estático generate() ainda não existe. Uma vez que ele é um método bem simples, não creio que precisemos de uma especificação para ele. Ele não passar de um invólucro para o construtor:

Isso fará com que o Behat volte a ficar verde, mas, agora, o PhpSpec reclama da gente, dizendo: Argument 1 passed to TimeTracker\TimeSheet::__construct() must be an instance of Illuminate\Support\Collection, none given. Podemos resolver isso, bastando criar uma função let() simples, que será chamada antes de cada especificação:

Isso nos trará de volta ao verde em todos os lugares. A função garante que a planilha de tempo sempre seja construída com um simulacro da classe Collection.

Podemos continuar, sem problemas, para o passo Then meu tempo total gasto.... Precisamos de um método que receba o nome de uma tarefa e retorne a duração acumulada de todos os registros em que a tarefa tenha aquele nome. Traduzido diretamente do código gherkin, Isso poderia ser algo mais ou menos assim totalTimeSpentOn($task):

O método não existe, logo, ao executar o Behat, retornará Call to undefined method TimeTracker\TimeSheet::totalTimeSpentOn().

Para especificar o método, criaremos uma especificação que se assemelha ao que já temos no cenário:

Perceba que não usamos simulacros para as instâncias de TimeEntry eCollection. Esse é o nosso conjunto de integração e não creio que haja necessidade de simular as coisas por aqui. Os objetos são bem simples e queremos ter certeza que os objetos do nosso domínio interagem da forma que esperamos que o façam. Com certeza, há inúmeras opiniões sobre isso, mas é isso o que faz sentido para mim.

Continuando:

Para filtrar os registros, podemos usar o método filter() da classe Collection. Uma solução simples que nos traz de volta ao verde:

Nossa especificação está verde, mas acredito que nos beneficiaríamos se refatorássemos algo aqui. O método parece realizar duas coisas diferentes: filtragem dos registros e acúmulo da duração. Extraiamos o segundo em seu próprio método:

O PhpSpec continua verde e agora temos três passos verdes no Behat. O último passo deve ser de fácil implementação, já que é bem parecido com o que acabamos de fazer.

Executando o Behat teremos Call to undefined method TimeTracker\TimeSheet::totalTimeSpent(). Ao invés de criar um exemplo separado em nossa especificação para esse método, que tal apenas adicioná-lo à que já temos? Pode não ser bem a forma "correta" de se fazer as coisas, mas sejamos um pouco pragmáticos:

Deixemos o PhpSpec gerar o método:

Conseguir ficar verde é fácil, agora que temos o método sumDuration():

E, agora, temos uma característica toda verde. Nosso domínio está evoluindo um pouco!

Projetando a Interface do Usuário

Agora, iremos para o conjunto de testes funcional. Projetaremos a interface do usuário para lidar com todas as coisas específicas do Laravel que não problema do nosso domínio.

Ao trabalha no conjunto funcional, podemos adicionar o semáforo -s para instruir o Behat a executar nossas características apenas pelo FunctionalFeatureContext:

O primeiro passo será parecido com o primeiro do contexto de integração. Ao invés de apenas persistir os registros do contexto em um vetor, precisamos fazer com eles sejam persistidos numa base de dados para que sejam obtidos futuramente:

Executar o Behat trará um erro fatal Call to undefined method TimeTracker\TimeEntry::save(), uma vez que a classe TimeEntry ainda não é um modelo do Eloquent. Isso é fácil de corrigir:

Se executarmos o Behat novamente, o Laravel reclamará, informando que não pode conectar-se à base de dados. Podemos corrigir isso, adicionando um arquivo database.php no diretório app/config/testing, para popular os detalhes da conexão para nossa base de dados. Para projetos maiores, provavelmente, quererá usar algum servidor de base de dados para seus códigos de testes e de produção, mas, em nosso caso, usaremos uma base de dados em memória com o SQLite. Isso é bem simples de configurar, usando o Laravel:

Agora, se executarmos o Behat, ele informará que não existe a tabela time_entries. Para corrigir isso, precisamos criar uma migração:

Ainda não estamos verdes, uma vez que precisamos de uma forma para instruir o Behat para executar nossas migrações antes de cada cenário, para que tenhamos um ambiente recém construído todas as vezes. Ao usar as anotações do Behat, podemos adicionar esses dois métodos ao trait LaravelTrait:

Isso é bem legal e é o nosso primeiro passo em direção ao verde.

Agora, é a vez do passo When na hora de gerar a planilha de tempo. A geração da planilha de tempo é o equivalente a visitar a ação index do recurso de registro de tempo, uma vez que a planilha de tempo é uma coleção de todos os registros de tempo. Assim, o objeto da planilha de tempo é como um recipiente para todos os registros de tempo e nos fornece uma forma fácil de manipular os registros. Ao invés de acessar /time-entries para ver a planilha, acredito que o empregado deveria acessar /time-sheet. Devemos colocar isso na definição do nosso passo:

Isso trará uma exceção do tipo NotFoundHttpException, uma vez que a rota ainda não está definida. Como acabei de explicar, acredito que essa URL deve ser mapeada para a ação index do recurso dos registros de tempo:

Para obtermos o verde, precisamos gerar o controlador:

E, criamos.

Finalmente, precisamos rastrear a página para encontrar a duração total de cada registro de tempo. Sei que teremos uma espécie de tabela que resumirá as durações. Os dois últimos passos são tão semelhantes que os implementaremos ao mesmo tempo:

O rastreador procurará por um node <td> com um identificador [nome_da_tarefa]TotalDurationou totalDuration no último exemplo.

Como ainda não temos uma visão, o rastreador dirá The current node list is empty. (A lista de nodos atual está vazia)

Para corrigir isso, criemos a ação index. Primeiro, buscaremos a coleção dos registros de tempo. Segundo, geraremos uma planilha de tempo a partir dos registros e enviaremos para uma visão (ainda inexistente).

A visão, por hora, consistirá apenas de uma tabela simples, com o resumo dos valores das durações:

Se executar o Behat novamente, verá que fomos bem sucedidos na implementação da característica. Talvez devêssemos para um momento para perceber que, em momento algum, abrimos o navegador! Isso é um aprimoramento absurdo em nosso fluxo de trabalho e, como um bônus, temos testes automatizados para nossa aplicação. YEAH!

Conclusão

Se executar vendor/bin/behat para rodar ambas os conjuntos de testes do Behat, você verá que ambos estão verdes, agora. Se executar o PhpSpec, infelizmente, verá que nossas especificações falharam. Obtemos um erro fatal Class 'Eloquent' not found in .... Isso se dá porque Eloquent é um apelido. Se você olhar no arquivo app/config/app.php na chave aliases, você verá que Eloquent é, na verdade, um apelido para Illuminate\Database\Eloquent\Model. Para fazer com que o PhpSpec torne ao verde, devemos importar essa classe:

Se executar esses dois comandos:

Você verá que todos os testes estão verdes, tanto os do Behat quanto os do PhpSpec. Que beleza! 

Nós descrevemos e projetamos nossa primeira funcionalidade, usando a abordagem do BDD. Vimos como podemos nos beneficiar do projeto do domínio base da nossa aplicação, antes de preocuparmo-nos com interface do usuário e coisas específicas de framework. Também vimos o quão fácil é interagir com o Laravel e, especialmente, a base de dados, nos contextos do Behat. 

No próximo artigo, realizaremos várias refatorações para evitar muita lógica em nossos modelos Eloquent, uma vez que eles são mais difíceis de testar isoladamente e estão bastante acoplados ao Laravel. Fique ligado!

Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.