O teste de unidade é fundamental no desenvolvimento de software, garantindo que os componentes do aplicativo funcionem isoladamente conforme o esperado. Ao escrever testes para unidades de código específicas, você pode identificar e corrigir erros no início do desenvolvimento, o que resulta em um software mais confiável e estável.

Em um pipeline de integração contínua/entrega contínua (CI/CD), você pode executar esses testes automaticamente após fazer alterações na base de código. Isso garante que o novo código não introduza erros nem danifique a funcionalidade existente.

Este artigo destaca a importância de testes unitários em aplicativos Laravel, detalhando como escrever testes unitários para um aplicativo Laravel implantado usando o serviço de Hospedagem de Aplicativos da Kinsta.

Introdução ao PHPUnit

O PHPUnit é um framework de testes amplamente utilizado no ecossistema PHP, projetado para testes unitários. Ele tem um conjunto robusto de ferramentas para criar e executar testes, o que o torna um recurso essencial para garantir a confiabilidade e a qualidade da sua base de código.

O Laravel oferece suporte a testes com PHPUnit e é equipado com métodos auxiliares convenientes que permitem que você teste seu aplicativo.

Configurar o PHPUnit em um projeto Laravel envolve ajustes mínimos. O Laravel fornece um ambiente de teste pré-configurado, incluindo um arquivo phpunit.xml e um diretório tests dedicado para seus arquivos de teste.

Adicionalmente, é possível modificar o arquivo phpunit.xml com opções personalizadas para uma experiência de teste sob medida. Você também pode criar um arquivo de ambiente .env.testing na pasta raiz do projeto em vez de usar o arquivo .env.

Layout padrão de testes no Laravel

O Laravel fornece um layout padrão de diretório estruturado. O diretório raiz do seu projeto Laravel contém um diretório tests com os subdiretórios Feature e Unit. Esse layout simplifica a tarefa de separar diferentes tipos de teste e mantém um ambiente de teste limpo e organizado.

O arquivo phpunit.xml em um projeto Laravel é crucial para orquestrar o processo de testes, garantindo a consistência nas execuções de testes e permitindo personalizar o comportamento do PHPUnit conforme os requisitos do projeto. Ele permite definir como executar os testes, incluindo a definição de suítes de teste, a especificação do ambiente de teste, e a configuração de conexões de bancos de dados.

Esse arquivo também especifica que a sessão, o cache e o e-mail devem ser definidos para o driver do array, garantindo que nenhum dado da sessão, do cache ou do e-mail persista durante a execução dos testes.

Você pode executar vários tipos de testes em seu aplicativo Laravel:

  • Teste de unidade — Concentra-se em componentes individuais do seu código, como classes, métodos e funções. Esses testes permanecem isolados do aplicativo Laravel e verificam se unidades de código específicas funcionam conforme o esperado. Observe que os testes definidos no diretório tests/Unit não inicializam o aplicativo Laravel, o que significa que eles não podem acessar o banco de dados ou outros serviços que o framework oferece.
  • Teste de recursos — Valida a funcionalidade mais ampla do seu aplicativo. Esses testes simulam solicitações e respostas HTTP, permitindo que você teste rotas, controladores e a integração de vários componentes. Os testes de recursos ajudam a garantir que as diferentes partes do seu aplicativo funcionem juntas conforme o esperado.
  • Teste de navegador — Vai além, automatizando interações com o navegador. Os testes usam o Laravel Dusk, uma ferramenta de automação e teste do navegador, para simular interações de usuário como o preenchimento de formulários e o clique em botões. Os testes de navegador são cruciais para validar o comportamento do seu aplicativo e a experiência do usuário em navegadores do mundo real.

Conceitos de desenvolvimento orientado por testes

Desenvolvimento orientado por testes (TDD) é uma abordagem de desenvolvimento de software que enfatiza os testes antes de implementar o código. Essa abordagem segue um processo conhecido como ciclo vermelho-verde-refatorar.

O desenvolvimento orientado por testes mostrando o ciclo vermelho-verde-refatorar.
O desenvolvimento orientado por testes mostrando o ciclo vermelho-verde-refatorar.

Aqui está uma explicação desse ciclo:

  • Fase vermelha — Escreva um novo teste para definir a funcionalidade ou uma melhoria em um teste existente antes de implementar o código real. O teste deve falhar (“vermelho”) porque não há código correspondente para que seja aprovado.
  • Fase verde — Escreva apenas o código suficiente para que o teste que falhou seja aprovado, alterando-o de vermelho para verde. O código não será ideal, mas atenderá aos requisitos do caso de teste correspondente.
  • Fase de refatoração — Refatore o código para melhorar sua legibilidade, manutenção e desempenho sem alterar seu comportamento. Nesta fase, você pode fazer alterações no código sem se preocupar com problemas de regressão, pois os casos de teste existentes os detectam.

O TDD tem vários benefícios:

  • Detecção precoce de bugs — O TDD ajuda a detectar bugs no início do processo de desenvolvimento, ajudando a reduzir o custo e o tempo de correção de problemas mais tarde no ciclo de desenvolvimento.
  • Design aprimorado — O TDD incentiva o código modular e pouco acoplado para melhorar o design do software. Incentiva a pensar na interface e nas interações dos componentes antes da implementação.
  • Confiança na refatoração — Você pode refatorar o código com confiança, sabendo que os testes existentes identificam rapidamente quaisquer regressões introduzidas durante a refatoração.
  • Documentação viva — Os casos de teste servem como documentação viva, fornecendo exemplos de como o código deve se comportar. Essa documentação está sempre atualizada, visto que falhas nos testes indicam problemas no código.

No desenvolvimento em Laravel, você aplica os princípios do TDD ao escrever testes para componentes como controladores, modelos e serviços antes de implementá-los.

O ambiente de teste do Laravel, incluindo o PHPUnit, fornece métodos e asserções convenientes para facilitar o TDD, garantindo que você possa criar testes significativos e seguir o ciclo vermelho-verde-refatorar de forma eficaz.

Exemplos básicos de teste de unidade

Esta seção explica como escrever um teste simples para verificar a funcionalidade do seu modelo.

Pré-requisitos

Para acompanhar, você precisa do seguinte:

  • Atender aos pré-requisitos listados no guia do blog do Laravel.
  • Um aplicativo Laravel. Este tutorial usa o aplicativo criado no guia do link acima. Você pode lê-lo e criar o aplicativo de blog, mas se precisar apenas do código-fonte para implementar os testes, siga as etapas abaixo.
  • Xdebug instalado e configurado com o modo de cobertura ativado.

Configure o projeto

  1. Execute este comando em uma janela de terminal para clonar o projeto.
    git clone https://github.com/VirtuaCreative/kinsta-laravel-blog.git
  2. Vá para a pasta do projeto e execute o comando composer install para instalar as dependências do projeto.
  3. Renomeie o arquivo env.example para .env.
  4. Execute o comando php artisan key:generate para gerar uma chave de aplicativo.

Crie e execute os testes

Para começar, certifique-se de ter o código do projeto em seu computador. O modelo que você testará é o modelo Post definido no arquivo app/Http/Models/Post.php. Esse modelo engloba vários atributos preenchíveis, como title, description e image.

Sua tarefa envolve criar testes unitários simples para esse modelo. Um deles verifica se os atributos estão definidos corretamente, enquanto outro examina a atribuição em massa ao tentar atribuir um atributo não preenchível.

  1. Execute o comando php artisan make:test PostModelFunctionalityTest --unit para criar um novo caso de teste. A opção --unit especifica que esse é um teste de unidade e o salva no diretório tests/Unit.
  2. Abra o arquivo tests/Unit/PostModelFunctionalityTest.php e substitua a função test_example por este código:
    public function test_attributes_are_set_correctly()
       {
           // create a new post instance with attributes
           $post = new Post([
               'title' => 'Sample Post Title',
               'description' => 'Sample Post Description',
               'image' => 'sample_image.jpg',
           ]);
    
           // check if you set the attributes correctly
           $this->assertEquals('Sample Post Title', $post->title);
           $this->assertEquals('Sample Post Description', $post->description);
           $this->assertEquals('sample_image.jpg', $post->image);
       }
    
       public function test_non_fillable_attributes_are_not_set()
       {
           // Attempt to create a post with additional attributes (non-fillable)
           $post = new Post([
               'title' => 'Sample Post Title',
               'description' => 'Sample Post Description',
               'image' => 'sample_image.jpg',
               'author' => 'John Doe',
           ]);
    
           // check that the non-fillable attribute is not set on the post instance
           $this->assertArrayNotHasKey('author', $post->getAttributes());
       }

    Esse código define dois métodos de teste.

    O primeiro cria uma instância Post com atributos específicos, usando o método de asserção assertEquals, afirma que você definiu os atributos title, description e image corretamente.

    O segundo método tenta criar uma instância de Post com um atributo adicional não preenchível (author) e afirma que esse atributo não está definido na instância do modelo usando o método de asserção assertArrayNotHasKey.

  3. Certifique-se de adicionar a seguinte instrução use no mesmo arquivo:
    use App\Models\Post;
  4. Execute o comando php artisan config:clear para limpar o cache de configuração.
  5. Para executar esses testes, execute o seguinte comando:
    php artisan test tests/Unit/PostModelFunctionalityTest.php

    Todos os testes devem ser aprovados e o terminal deve exibir os resultados e o tempo total de execução dos testes.

Testes de depuração

Se os testes falharem, você poderá depurá-los seguindo estas etapas:

  1. Revise a mensagem de erro no terminal. O Laravel fornece mensagens de erro detalhadas que apontam precisamente o problema. Leia atentamente a mensagem de erro para entender por que o teste falhou.
  2. Inspecione os testes e o código que você está testando para identificar discrepâncias.
  3. Certifique-se de que você configurou corretamente os dados e as dependências necessárias para o teste.
  4. Use ferramentas de depuração como a função dd() do Laravel para inspecionar variáveis e dados em pontos específicos do código de teste.
  5. Depois que você identificar o problema, faça as alterações necessárias e execute novamente os testes até que eles sejam aprovados.

Testes e bancos de dados

O Laravel fornece uma maneira conveniente de configurar um ambiente de teste usando um banco de dados SQLite in-memory, que é rápido e não persiste os dados entre execuções de testes. Para configurar o ambiente de banco de dados de teste e escrever testes que interagem com o banco de dados, siga as etapas abaixo:

  1. Abra o arquivo phpunit.xml e descomente as seguintes linhas de código:
    <env name="DB_CONNECTION" value="sqlite"/>
    <env name="DB_DATABASE" value=":memory:"/>
  2. Execute o comando php artisan make:test PostCreationTest --unit para criar um novo caso de teste.
  3. Abra o arquivo tests/Unit/PostCreationTest.php e substitua o método test_example pelo código abaixo:
    public function testPostCreation()
       {
           // Create a new post and save it to the database
           $post = Post::create([
               'title' => 'Sample Post Title',
               'description' => 'Sample Post Description',
               'image' => 'sample_image.jpg',
           ]);
    
           // Retrieve the post from the database and assert its existence
           $createdPost = Post::find($post->id);
           $this->assertNotNull($createdPost);
           $this->assertEquals('Sample Post Title', $createdPost->title);
       }
  4. Certifique-se de adicionar a seguinte instrução use:
    use App\Models\Post;

    Atualmente, a classe PostCreationTest estende a classe base PHPUnitFrameworkTestCase. A classe base é comumente usada para testes de unidade ao trabalhar com PHPUnit diretamente, fora do Laravel, ou ao escrever testes para um componente não firmemente acoplado ao Laravel. No entanto, você precisa acessar o banco de dados, o que significa que deve modificar a classe PostCreationTest para estender a classe TestsTestCase.

    Essa última classe adapta a classe PHPUnitFrameworkTestCase aos aplicativos Laravel. Ela fornece funcionalidade adicional e configuração específica do Laravel, como a propagação do banco de dados e a configuração do ambiente de teste.

  5. Certifique-se de substituir a instrução use PHPUnitFrameworkTestCase; por use TestsTestCase;. Lembre-se de que você definiu o ambiente de teste para usar um banco de dados SQLite in-memory. Então você deve migrar o banco de dados antes de executar os testes. Use a funcionalidade IlluminateFoundationTestingRefreshDatabase para fazer isso. Essa funcionalidade migra o banco de dados se o schema não estiver atualizado e redefine o banco de dados após cada teste para garantir que os dados do teste anterior não interfiram nos testes subsequentes.
  6. Adicione a seguinte instrução use ao arquivo tests/Unit/PostCreationTest.php para incorporar essa funcionalidade em seu código:
    use Illuminate\Foundation\Testing\RefreshDatabase;
  7. Em seguida, adicione a seguinte linha de código logo antes do método testPostCreation:
    use RefreshDatabase;
  8. Execute o comando php artisan config:clear para limpar o cache de configuração.
  9. Para executar esse teste, execute o seguinte comando:
    php artisan test tests/Unit/PostCreationTest.php

    Os testes devem ser aprovados e o terminal deve exibir os resultados do teste e o tempo total de teste.

Teste de recursos

Enquanto os testes de unidade verificam componentes individuais do aplicativo isoladamente, os testes de recursos verificam partes maiores do código, como a interação entre vários objetos. O teste de recursos é vital por vários motivos:

  1. Validação de ponta a ponta — Confirma que todo o recurso funciona perfeitamente, incluindo as interações entre vários componentes, como controladores, modelos, exibições e até mesmo o banco de dados.
  2. Teste de ponta a ponta — Abrange todo o fluxo do usuário, desde a solicitação inicial até a resposta final, o que pode revelar problemas que os testes de unidade talvez não detectem. Essa capacidade os torna valiosos para testar jornadas de usuários e cenários complexos.
  3. Garantia da experiência do usuário — Simula interações de usuário, ajudando a verificar se a experiência do usuário é consistente e se o recurso funciona como pretendido.
  4. Detecção de regressão — Detecta regressões e alterações que quebram o código ao introduzir um novo código. Se um recurso existente começar a falhar em um teste de recurso, isso indica que algo foi danificado.

Agora, crie um teste de recurso para PostController no arquivo app/Http/Controllers/PostController.php. Concentre-se no método store, validando os dados de entrada e criando e armazenando publicações no banco de dados.

O teste simula um usuário criando uma nova publicação por meio de uma interface web, garantindo que o código armazene a publicação no banco de dados e redirecione o usuário para a página Índice de Publicações após a criação. Para fazer isso, siga estas etapas:

  1. Execute o comando php artisan make:test PostControllerTest para criar um novo caso de teste no diretório tests/Feature.
  2. Abra o arquivo tests/Feature/PostControllerTest.php e substitua o método test_example por este código:
    use RefreshDatabase; // Refresh the database after each test
    
       public function test_create_post()
       {
           // Simulate a user creating a new post through the web interface
           $response = $this->post(route('posts.store'), [
               'title' => 'New Post Title',
               'description' => 'New Post Description',
               'image' => $this->create_test_image(),
           ]);
    
           // Assert that the post is successfully stored in the database
           $this->assertCount(1, Post::all());
    
           // Assert that the user is redirected to the Posts Index page after post creation
           $response->assertRedirect(route('posts.index'));
       }
    
       // Helper function to create a test image for the post
       private function create_test_image()
       {
           // Create a mock image file using Laravel's UploadedFile class
           $file = UploadedFile::fake()->image('test_image.jpg');
    
           // Return the path to the temporary image file
           return $file;
       }

    A função test_create_post simula um usuário criando uma nova publicação fazendo uma solicitação POST para a rota posts.store com atributos específicos, incluindo uma imagem simulada gerada usando a classe UploadedFile do Laravel.

    Em seguida, o teste afirma que o código armazenou com êxito a publicação no banco de dados, verificando a contagem de Post::all(). E verifica que o código redireciona o usuário para a página Índice de Publicações após a criação da publicação.

    Esse teste garante que a funcionalidade de criação de publicação funciona e que o aplicativo lida corretamente com as interações e redirecionamentos do banco de dados após o envio de uma publicação.

  3. Adicione as seguintes instruções use ao mesmo arquivo:
    use App\Models\Post;
    use Illuminate\Http\UploadedFile;
  4. Execute o comando php artisan config:clear para limpar o cache de configuração.
  5. Para executar esse teste, dê este comando:
    php artisan test tests/Feature/PostControllerTest.php

    O teste deve ser aprovado e o terminal deve mostrar os resultados do teste e o tempo total de execução do teste.

Confirme a cobertura do teste

A cobertura do teste refere-se à quantidade da base de código que os testes de unidade, recurso ou navegador verificam, expressa como uma porcentagem. Ela o ajuda a identificar áreas não testadas na base de código e áreas subtestadas que podem conter bugs.

Ferramentas como o recurso de cobertura de código do PHPUnit e o relatório de cobertura integrado do Laravel geram relatórios que mostram quais partes da sua base de código os testes cobrem. Esse processo fornece informações críticas sobre a qualidade dos seus testes e ajuda você a se concentrar nas áreas que podem exigir testes adicionais.

Gere um relatório

  1. Exclua os arquivos tests/Feature/ExampleTest.php e tests/Unit/ExampleTest.php, pois você não os modificou e eles podem causar erros.
  2. Execute o comando php artisan test --coverage em uma janela de terminal. Você deve receber um resultado como o seguinte:
    Execução do comando php artisan test --coverage.
    Execução do comando php artisan test –coverage.

    O relatório de cobertura de código mostra os resultados do teste, o número total de testes aprovados e o tempo para executar os resultados. Também lista cada componente em sua base de código e sua porcentagem de cobertura de código. As porcentagens representam a proporção do código que seus testes cobrem.

    Por exemplo, Models/Post tem 100% de cobertura, o que significa que todos os métodos e linhas de código do modelo são cobertos. O relatório de cobertura de código também exibe Total Coverage — a cobertura geral do código para toda a base de código. Neste caso, os testes cobriram apenas 65,3% do código.

  3. Para especificar um limite mínimo de cobertura, execute o comando php artisan test --coverage --min=85. Esse comando define um limite mínimo de 85%. Você deve receber a seguinte saída:
    Teste com limite mínimo de 85%.
    Teste com limite mínimo de 85%.

    As suítes de teste falham porque o código não atende ao limite mínimo definido de 85%.

    Embora a meta seja uma cobertura de código mais alta, geralmente de 100%, o mais importante é testar minuciosamente as partes críticas e complexas do seu aplicativo.

Resumo

Ao adotar as práticas recomendadas descritas neste artigo, como escrever testes significativos e abrangentes, e aderir ao ciclo vermelho-verde-refatorar no TDD, aproveitando os recursos de teste fornecidos pelo Laravel e pelo PHPUnit, você pode criar aplicativos robustos e de alta qualidade.

Além disso, você tem a opção de hospedar seu aplicativo Laravel com a infraestrutura rápida, segura e confiável da Kinsta. Além disso, você pode utilizar a API da Kinsta para iniciar implantações em seus pipelines de CI/CD por meio de plataformas como GitHub Actions, CircleCI e muito mais.

Jeremy Holcombe Kinsta

Editor de Conteúdo & Marketing na Kinsta, Desenvolvedor Web WordPress e Escritor de Conteúdo. Fora do universo WordPress, eu curto praia, golfe e filmes. Também enfrento problemas de gente alta ;).