Back-End

21 nov, 2013

Mudando de TDD para BDD Com Behat e Symfony2

Publicidade

Recentemente comecei um pequeno projeto extra com um colega do meu PHP User Group local, já que o projeto era bastante simples e nós dois estávamos dispostos a aprender algo novo, e decidimos fazer uma tentativa com BDD. No mundo PHP, BDD significa Behat, Mink e PHPSpec.

Trabalhar em um projeto real é sempre diferente de lidar com documentação, e tivemos que passar por vários problemas diferentes. A maioria desses problemas relacionada com os nossos velhos hábitos com frameworks de testes XUnit e e alguns outros devido à nossa dificuldade em nos acostumar com APIs diferentes.

Neste artigo, eu não cobrirei todos os detalhes sobre BDD, Behat ou PHPSpec, prefiro descrever como eu mudei de PHPUnit e TDD para BDD (e também mostrar alguns pedaços de código).

Mudar de TDD para BDD não é realmente sobre bibliotecas, é mais sobre como mudar a sua mentalidade de testar para descrever. A comunicação é realmente rei aqui. Pode parecer um pouco bobo, mas ter que você não está verificando se o seu código está funcionando, e sim descrevendo como ele deve funcionar, realmente é a chave para tudo isso. Você poderia fazer BDD usando PHPUnit em vez de Behat ou PHPSpec – não é uma questão de ferramentas, e sim de como você está abordando seu código.

Dito isso, as ferramentas são importantes, e descobri que tanto Behat e PHPSpec fazem um grande trabalho ajudando você a mudar a sua mentalidade de testar para descrever.

Os componentes

O primeiro passo é entender com que biblioteca você tem que lidar:

  • Behat é o verdadeiro patrão: o framework de testes . Ele permite que você escreva as expectativas em linguagem gherkin.
  • Mink é uma biblioteca de testes de aceitação web. Ele permite que você escreva testes para sua aplicação web. Você poderia usá-lo como uma biblioteca isolada de testes, mas usá-lo com Behat é a maneira mais divertida.
  • MinkExtension é a cola entre Behat e Mink. A extensão vai configurar e inicializar Mink, e você vai ser capaz de usá-la dentro das classes *Context do Behat.
  • Symfony2Extension lhe dá acesso ao kernel do Symfony a partir das classes *Context do Behat e fornece uma sessão Symfony2 para Mink.

A fim de colocar todas essas peças no lugar, você pode seguir a documentação oficial, eu não vou repetir todos os passos aqui.

Organizando seus testes

Symfony2Extension lhe dá a possibilidade de ter os arquivos de recursos separados para cada pacote, ou mantê-los todos juntos no nível do aplicativo. Optei por manter os recursos no nível do aplicativo. Dado que os recursos são uma descrição do aplicativo como um todo, não soa natural para mim tê-los separados por pacote. Organização em pacotes é um detalhe de implementação e eu não quero esse tipo de detalhe exposto em meus recursos gherkin. É claro que essa é uma decisão muito pessoal, e você deve pensar em como quer manter seus arquivos de recurso organizados.

Se você, como eu, quer manter todos os arquivos que possui em conjunto em nível de aplicação, você tem que defini-lo em seu arquivo de configuração:

behat.yml

      default:
        paths:
          features: features

Uma vez que você começa a escrever recursos, também começa a implementar passos em suas classes *Context.

Na minha opinião, esta é a parte mais difícil.

Classes *Contextde crescem muito rápido; enquanto você está testando o aplicativo, você está implementando continuamente novos passos e sempre tem que manter seus arquivos de recursos e suas classes *Context fáceis de ler.

Isso significa que você precisa definir passos macro e tem que organizá-los em sub- contextos, a fim de manter seu código claro. Você também vai precisar se acostumar a fazer uma definição passo a passo de implementação, o que pode ser um pouco confuso no início.

Eu acho que isso não é uma coisa que você possa aprender com a documentação, você vai precisar de um pouco de experiência para ficar bom nisso. Nesta primeira experiência, eu senti a necessidade de refatorar e reorganizar minhas etapas de implementação algumas vezes, até mesmo com um pequeno conjunto de testes. A coisa boa é que toda vez que você move o código, você sentirá que seu entendimento do domínio se torna mais profundo. Toda vez que uma etapa começava a dar errado, era porque eu estava deixando passar algo em meu domínio, e cada refatoração me levou a um melhor entendimento da aplicação que eu estava construindo.

Iniciando os testes

Logo após a configuração do Behat, eu comecei a escrever recursos para o meu aplicativo. Eu não escrevi um único recurso, eu escrevi um monte deles, só para esclarecer o quadro geral e para me ajudar a decidir por onde começar.

Imediatamente depois de escrever os primeiros cenários, percebi que eu não queria executá-los todos juntos; em vez disso, eu teria gostado de marcar alguns cenários como “estou trabalhando nisso” e outros como “vou trabalhar nisso mais tarde” ou “isso está pronto”.

Marcação era o que eu estava procurando.

Eu marquei todos os cenários como @tbd, o que eu estava realmente trabalhando era @wip e então eu defini alguns perfis em behat.yml:

behat.tml

      default:
        filters:
            tags:   "~@tbd"
      wip:
        filters:
            tags:       "@wip"

Com essa configuração, a execução:

bin/behat

Eu era capaz de executar todos os cenários não marcados com @tbd e em execução:

bin/behat --profile wip

Eu era capaz de executar o cenário do atual “trabalho em andamento”.

Isto é muito bom: ter perfis diferentes é bastante semelhante a ter diferentes suítes, e excluir a tag @tbd do perfil padrão me permitiu executar todos os cenários sem ter a perturbação dos passos não-definidos nas etapas que não foram implementadas.

Esse não era o meu primeiro projeto guiado por testes, mas eu nunca tinha escrito um monte de testes funcionais antes da implementação. Normalmente eu teria escrito o teste para o próximo recurso e então o eu teria implementado. Escrever em linguagem gherkin me ajudou a definir um conjunto muito maior de especificações, o que é muito bom porque me deu a ideia geral do que eu estava fazendo.

Eu acho que essa é uma das grandes vantagens de BDD: ele realmente muda a forma como você pensa sobre sua aplicação. Claro, agora que eu percebi isso, poderia fazer a mesma coisa usando o bom e velho PHPUnit, mas antes desse exercício eu nunca havia focado em descrever um conjunto de funcionalidades com uma suíte de testes antes da codificação.

Trabalhar com um banco de dados

Testes funcionais de sua aplicação significam lidar com um banco de dados e acessórios. As regras básicas são sempre as mesmas: você quer ter uma base de dados limpa no início de cada teste, então você vai preenchê-la com dados pré-definidos e depois você vai fazer afirmações contra ela. Ter um ponto de partida limpo para cada cenário é a chave para evitar as dependências entre eles.

A principal diferença entre Behat e seu velho e bom teste funcional é que você não vai definir um conjunto de dispositivos elétricos em seu método setUp(); em vez disso, você irá definir um conjunto de passos.

Given there is a "user1" User in the database
And there are 2 bookable Courts

Behat expõe vários hooks que você pode usar para executar ações como dropping e a construção do banco de dados; eu usei @BeforeScenario para isso, com uma implementação um pouco diferente do método loadFixtures LiipFunctionalTestBundle  Aqui está minha implementação :

    public function cleanDatabase()
{
    $container = $this->kernel->getContainer();
    $registry = $container->get('doctrine');
    $om = $registry->getManager();
    $type = 'ORM';

    $executorClass = 'Doctrine\\Common\\DataFixtures\\Executor\\' . $type . 'Executor';
    $referenceRepository = new ProxyReferenceRepository($om);
    $cacheDriver = $om->getMetadataFactory()->getCacheDriver();

    if ($cacheDriver) {
        $cacheDriver->deleteAll();
    }

    $connection = $om->getConnection();
    if ($connection->getDriver() instanceOf SqliteDriver) {
        $params = $connection->getParams();
        $name = isset($params['path']) ? $params['path'] : $params['dbname'];

        if (!isset(self::$cachedMetadatas)) {
            self::$cachedMetadatas = $om->getMetadataFactory()->getAllMetadata();
        }
        $metadatas = self::$cachedMetadatas;

        // TODO: handle case when using persistent connections. Fail loudly?
        $schemaTool = new SchemaTool($om);
        $schemaTool->dropDatabase($name);
        if (!empty($metadatas)) {
            $schemaTool->createSchema($metadatas);
        }

        $executor = new $executorClass($om);
        $executor->setReferenceRepository($referenceRepository);
    }

    $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\' . $type . 'Purger';
    $purger = new $purgerClass();
    $executor = new $executorClass($om, $purger);
    $executor->setReferenceRepository($referenceRepository);
    $executor->purge();

    return $executor;
}

Essa implementação não é realmente otimizada para desempenho, mas foi o suficiente para o meu projeto, que teve um pequeno banco de dados e poucos cenários. Talvez para um caso de uso mais complexo, você vai precisar de uma maneira mais eficiente de eliminar e recriar o banco de dados (talvez com algum tipo de cache do arquivo sqlite ?).

Conclusão

Acho que vou continuar a explorar com BDD. Ferramentas são maduras e estáveis o suficiente, e todo o ecossistema parece sólido e pronto para usar. Behat e Mink são ótimas ferramentas, PHPSpec (que eu vou abordar em outro artigo) é tão bom quanto.

Eu ainda não encontrei as melhores práticas. Não tenho certeza de que estou sempre fazendo a coisa certa e eu tenho alguns velhos hábitos que preciso perder, mas a impressão geral é muito boa.

***

Artigo traduzido pela Redação iMasters, com autorização do autor. Publicado originalmente em http://www.ftassi.com/blog/2013/08/12/switching-from-tdd-to-bdd-with-behat-and-symfony2/