Advertisement
  1. Code
  2. Web Development

Primeiros Passos com Phpspec

by
Read Time:15 minsLanguages:

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

Nesse pequeno, porém fácil, tutorial, nós falaremos sobre desenvolvimento movido a comportamento (do inglês, Behavior Drive Development, ou BDD), com phpspec. No geral, será uma introdução à ferramenta phpspec, mas, enquanto a apresentemos, falaremos sobre os diferentes conceitos de BDDBDD é um dos assuntos do momento e o phpspec ganhou bastante atenção na comunidade PHP, recentemente.

SpecBDD & Phpspec

DBB é sobre descrever o comportamente do aplicativo, de modo que tenhamos o projeto da forma correta. Ele é, geralmente, associado ao TDD (do inglês, Test Driven Development, ou, em português, desenvolvimento movido a testes), mas, enquanto o TDD foca em testarsua aplicação, o BDD está mais para descrever o comportamento da mesma. Usar a abordagem do BDD forçará você a considerar, constantemente, os requerimentos verdadeiros e o comportamente desejado do software que está construindo.

Duas ferramentas para BDD ganharam bastante atenção da comunidade PHP, recentemente: Behat e Phpspec. Behat ajuda você a descrever ocomportamente externo da sua aplicação, usando a linguagem de fácil leitura, Gherkin. phpspec, por sua vez, ajuda a descrever ocomportamente interno da sua aplicação, ao escrever pequenas "especificações" usando PHP - daí o nome SpecBDD (Spec de specification). Essas especificações testam se seu código tem o comportamente desejado.

O Que Nós Faremos

Nesse tutorial, cobriremos tudo relacionado a começa com o phpspec. No caminho, criaremos a base de uma aplicação do tipo "lista de tarefas", passo-a-passo, usando a abordagem ddo SpecBDD. Enquanto programamos, lançaremos mão do phpspec.

Nota: Esse é um artigo intermediário sobre PHP. Estou assumindo que você tem conhecimento suficiente sobre orientação a objetos com PHP

Instalação

Para esse tutorial, assumiremos que você tem as seguintes ferramentas instaladas e funcionando:

  • Ambiente de desenvolvimento PHP (versão mínima 5.3)
  • Composer

Instalar o phpspec usado Composer é a forma mais fácil. Tudo que você precisa fazer é rodar o seguinte comando no seu terminal:

Para garantir que tudo esteja em seu devido lugar e funcionando, rode o comando phpspec e veja se obtém o seguinte retorno:

Configuração

Antes de começarmos, nós precisamos fazer pequenas modificações na configuração. Quando phpspec rodar, ele procurará por um arquivo chamado phpspec.yml. Já que usaremos namespace em nossos códigos, precisamos fazer com que o phpspec saiba disso. Aproveitaremos, também, para garantir que nossas especificações apareçam de forma fácil e bonita de se ver quando mandarmos executá-las.

Vá em frente e crie o arquivo com o conteúdo a seguir:

Há inúmeras outras opções de configuração. Você pode ler mais sobre elas na documentação.

Outra coisa que precisamos fazer, é dizer ao Composer para carregar automaticamente nosso código. phpspec usará o autoarregador do Composer, logo, isso é obrigatório que nossas especificações executem.

Adicione um elemento autoload ao arquivo composer.json que o Composer criou para você:

Executar o comando composer dump-autoload atualizará o autocarregador após essa mudança.

Nossa Primeira Especificação

Agora, que já estamos prontos, é hora de escrever nossa primeira especificação. Começaremos descrevendo a classe chamadaTaskCollection. Faremos com que phpspec gere uma classe de especificação para nós, usando o comendo describe (ou a versão abreviada, desc).

O que acabou de acontecer aqui? Primeiro, nós pedimos para o phpspec criar a classe TaskCollection. Segundo, nós executamos o conjunto de especificações e, automagicamente, o phpspec ofereceu para criar a classe TaskCollection para nós. Legal, não é?

Vá em frente, rode o conjunto novamente e verá que já temos um exemplo em nossas especificações (logo veremos o que é um exemplo):

Desse retorno, podemos ver que a classe TaskCollection éinicializável. O que isso significa? Veja o arquivo de especificação que o phpspec criou e tudo ficará claro:

A frase 'is initializable' é detivada da função it_is_initializable(), a qual o phpspec adiciona à classe TaskCollectionSpec. Essa função é o que nós chamamos de exemplo (example, no retorno anterior). Nesse exemplo em particular, nós temos o que podemos chamar de combinador, chamado de shouldHaveType(), que verifica o tipo da nossa classe TaskCollection. Se você mudar o parâmetro passado para essa função para qualquer outra coisa e roda a especificação novamente, verá que ela falhará. Antes de entender isso completamente, preciamos descobrir a que, precisamente, se refere a variável $thisem nossa especificação.

O Que É $this?

Obviamente, $this refere-se à instância da classeTaskCollectionSpec, uma vez que isso, nada mais é, que um código PHP comum. Mas, com phpspec, você tem de tratar o $this de forma diferente com o que está acostumado, já que, por baixo dos panos, ele se refere, na verdade, ao objeto em teste, que, nesse caso, é a classeTaskCollection. Esse comportamente é herdado da classeObjectBehavior, que garante que as chamadas às funções sejam redirecionadas (através de um Proxy) à classe verdadeira. Isso significa que AlgumaClasseSpec redirecionará as chamadas dos métodos para uma instância de AlgumaClasse. phpspec envolverá essas chamadas de métodos para garantir que os seus resultados sejam rodados a algum combinador como o que você acabou de ver.

Você não precisa entender perfeitamente isso para usar o phpspec, apenas se lembre que a $this, na verdade, refere-se ao objeto sob teste.

Construindo Nossa Coleção de Tarefas

Até agora, não fizemos muita coisas por conta própria. Mas o phpspec criou uma classe TaskCollection para nós usarmos. Agora, é hora de escrever um pouco de código e tornar essa classe útil. Nós adicionaremos dois métodos: um add(), para adicionar tarefas, e umcount(), para contar o número de tarefas na coleção.

Adicionando uma Tarefa

Antes de escrevermos qualquer código de verdade, nós deveríamos criar um exemplo em nossa especificação. Em nosso exemplo, nós queremos tentar adicionar uma tarefa à nossa coleção e, então, garantir que essa tarega foi adicionada de fato. Para fazer isso, ´precismos de uma instância da classe Task (ainda não existente). Se adicionarmos essa dependência como parâmetro para a nossa função de especificação, o phpspec nos dará, automaticamente, uma instância para que possamos usar. Na verdade, a instância não é de verdade, mas algo que o phpspec identifica como Collaborator. Esse objeto agirá como o verdadeiro objeto, mas o phpspec permitirá que façamos muitas coisas com ele. Algo que veremos em breve, inclusive. Embora a classe Task não exista ainda, por hora, finja que ela existe. Abra a classeTaskCollectionSpec e adicione a instrução use para adicionar a classe Task e, depois, adicionae o exemploit_adds_a_task_to_the_collection():

Em nosso exemplo, escrevemos o código que "gostaríamos de ter". Nós chamamos o método call() e então tentamos passá-lo uma $task. Checamos, então, se a tarefa foi, de fato, adicionada à variável de instância, $tasks. O combinador shouldBe() é um combinador de identidade idêntico ao comparador === do PHP. Você usar tanto oshouldBe()shouldBeEqualTo()shouldEqual() oushouldReturn() - todos fazem a mesma coisa.

Ao executar o phpspec, teremos alguns erros, já que não temos a classeTask ainda.

Façamos com que o phpspec arrume isso para nós:

Executando o phpspec novamente, algo interessante acontece:

Perfeito! Se você for olhar o arquivo TaskCollection.php, verá que o phpspec criou uma função add() para que a preenchamos.

Porém, phpspec ainda reclama. Nós não temos nossa array $tasks. Então, vamos criá-ça e adicionar nossa tarefa a ela:

Agora, nossas especificações estão legais e todas verdes. Note que fiz questão de lançar mão da checagem de tipos no parâmetro $task;

Só para garantir que nós fizemos tudo certo, vamos adicionar outra tarefa:

Ao rodar phpspec, tudo estará legal.

Implementando a Interface Countable

Querer saber quantas taremos existem em uma coleção é uma excelente razão para usaarmos uma das interfaces da Bbiblioteca Padrão do PHP(do inglês, Standard PHP Library, SPL), mais especificamente aCountable interface.

Mais cedo, nós usamos o combinador shouldHaveType(), que é um combinador de tipo. Ele usa o comparador instanceof do PHP para validar se um objeto é uma instância de uma dada classe. Há quatro combinadores de tipo, os quais fazem a mesma coisa. Um deles é oshouldImplement(), que é perfeito para nosso objetivo, então vamos usá-lo em nosso exemplo:

Você percebe o quão bonito e simple é de ler? Vamos executar o exemplo e deixar que o phpspec nos guie:

Certo, então. Nosso código não é uma instância de Countable, uma vez que não a implementamos ainda. Vamos atualizar o código da nossa class TaskCollection:

Nossos testes não executarão, já que a interface Countable tem um método abstrato, count(), que nós devemos implementar. Um método vazia funcionará, por hora:

E voltamos ao verde. Nesse momento, nosso método count() não faz muita coisa e, na verdade, é bem inútil. Escrevamos uma especificação para o comportamente que desejamos que ele tenha. Primeiro, sem tarefas, é esperado retornar zero de nossa função:

It returns null, não 0. Para fazermos o teste ficar verde, vamos conserta-la ao modo TDD/BDD:

Agora, estamos verde e tudo está bem, exceto que esse não é o provável comportamento que queremos. Ao invés disso, expandamos nossa especificação e adicionemos algo à array $tasks:

Obviamente, nosso código ainda retorna 0 e nós temos um passo em vermelho. Ajustar isso não é muito difícil e nossa class TaskCollectiondeve parecer com isso, agora:

Agora, nós temos um teste verde e nosso método count() funciona. Que legal!

Expectativas e Promessas

Você se lembra que eu falei que o phpspec permite você fazer várias coisas legais com as intâncias da classe Collaborator, também conhecidas como as instâncias que são injetadas automaticamente pelo phpspec? Se você já escreveu testes antes, você sabe o que são os objetos simulados (mocks) e as funções substitutas (stubs). Se você não sabe, não se preocupe quanto a isso. São só jargões. Eles se referem ao objetos "falsos" que agirão no lugar dos objetos de verdade, que permitem você fazer testes isolados. phpspec, automaticamente, transforma as instâncias de Collaborator em mocks e stubs se você precisar em seus objetos.

Isso é incrível. Por trás, phpspec usa a biblioteca Prophecy, que é um arcabouço (framework) bem particular que trabalha bem com phpspec (e é construída pelos mesmos criadores do phpspec). Você pode estabelecer uma expectativa em um colaborador (mocking), algo como "esse método deveria ser invocado" e pode adicionar promessas (stubbing), como "esse método retornará esse valor". Com o phpspec, essa tarefa é bem simples e nós faremos as duas, logo a seguir.

Criemos uma classe, chamada de TodoList, que faz uso de nossa coleção:

Adicionando Tarefas

O primeiro exemplo que adicionaremos é um para adicionar tarefas. Nós criaremos um método addTask(), que nada mais faz que adicionar uma tarefa à nossa coleção. Ele simplesmente direciona a chamada ao método add() da coleção, logo, aqui é um lugar perfeito para usar uma Expectativa. Nós não queremos invocar o método add() de verdade, nós só queremos garantir que ele tente fazê-lo. Além do mais, nós queremos garantir que ele seja chamado apenas uma vez. Veja como podemos faz isso com phpspec:

Primeiro, fazemos com phpspec proveja dois colaboradores que precisamos: uma coleção de tarefas e uma tarefa. Depois disso, estabelecemos uma expectativa no colaborador da coleção de tarefas que, basicamente, diz: "O método add() deverá ser chamado, exatamente, 1 vez com a variável $task como parâmetro". É assim que preparamos nosso colaborador, que agora é um mock, antes de atribuí-lo à propriedade $tasks de TodoList. Finalmente, nós tentamos invocar o método addTask() de verdade.

Certo, o que o phpspec tem a dizer sobre isso:

A propriedade $tasks não existe. Ajustar esse problema é bem fácil:

Tente novamente e deixe o phpspec ser nosso guia:

Okay, algo interessante aconteceu agora. Viua mensagem "Expected exactly 1 calls that match: ...."? Essa é nossa expectativa falha. Isso acontece porque, depois de invocar o método addTask(), o métodoadd() na coleção não foi invocado, o que era esperado acontecer.

Para voltar a termos tudo verde, coloque o seguinte código dentro do método addTask():

Tudo verde, novamente. Isso é muito bom, não é?

Verificando Tarefas

Façamos algo relacionado a promessas, também. Nós queremos um método que nos diga se há alguma tarefa em nossa coleção. Para isso, simplesmente verificaremos o valor de retorno do método count() da coleção. Novamente, nós não precisaremos de uma instância de verdade com um método count() de verdade. Nós só precisamos garantir que nosso código chame algum método count() e realize alguma tarefa, dependendo do valor de retorno do método.

Veja o seguinte exemplo:

Nós temos um colaborador de coleção de tarefas que tem um métodocount() que retornará zero. Essa é nossa promessa. Isso significa que, toda vez que alguém chamar o método count(), ele retornará zero. Nós, então, atribuímos o colaborador preparado à propriedade $tasksdo nosso objeto. Por fim, nós tentamos chamar um método,hasTasks(), e garantir que ele retorna false.

O que a especificação tem a nos dizer sobre isso?

Legal. phpspec criou para nós um método hasTasks() e, sem surpresas, ele retorna null e não false.

Novamente, essa é uma tarefa fácil de resolver:

Obtivemos tudo verde, mas, isso não é bem o que queremos. Verifiquemos pelas tarefas quando tivermos 20 delas. Dessa vez, deverá retornar true:

Execute phpspec e você terá:

Certo, false não é true, então, precisamos melhorar nosso código. Vamos usar o método count() para verificar se há tarefas ou não:

Tah dah! Tudo verde, de novo!

Construindo Combinadores Customizados

Parte de criar boas especificações é fazê-las o mais legível possível. Nosso último exemplo pode, na verdade, ser aprimorado um pouco, graças aos combinadores customizados do phpspec. É fácil de implementar combinadores customizados -tudo que temos de fazer é sobrescrever o método getMatchers() que é herdado deObjectBehavior. Ao implementarmos dois combinadores customizados, nossa especificação pode ser alterada para parecer com isso:

Eu acredito que isso esteja parecendo bem bacana. Lembre-se que refatorar suas especificações é importante para mante-las atualizadas. Implementar seus próprios combinadores customizados pode limpar suas especificações e torná-las mais legíveis.

Na verdade, nós podemos usar a negação dos combinadores também:

Bem legal!

Conclusão

Todas as nossas especificações estão verdes e veja como elas documentam de forma legal nosso código!

Nós descrevemos de forma efetiva e alcançamos o comportamento desejado de nosso código. Sem mencionar que, uma vez que nosso código está coberto por nossas especificações, a refatoração do mesmo não será uma experiência pavorosa.

Por ter seguido esse tutorial, espero ter inspirado você a dar uma chance ao phpspec. Ele é mais que uma ferramenta de testes - é uma ferramenta de design. Uma vez acostumado a usar o phpspec (e suas maravilhosas ferramentas de geração de código), você terá problemas em largar dele novamente! As pessoas, frequentemente, reclamam que seguir o caminho TDD ou BDD diminui a velocidade deles. Depois de incorporar o phpspec no meu fluxo de trabalho, eu sinto exatamente o contrário - minha produtividade melhorou bastante. Sem falar que meu código está muito mais sólido!

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.