bens

Aprenda a fazer testes no Laravel com exemplos simples, usando PHPUnit e PEST

Quando se trata de testes automatizados ou testes unitários, em qualquer linguagem de programação, existem duas opiniões opostas:

  • tempo perdido
  • Você não pode ficar sem isso

Portanto, com este artigo tentaremos convencer o primeiro, principalmente demonstrando como é fácil começar a fazer testes automatizados no Laravel.

Primeiro vamos falar sobre o “porquê” e depois vamos ver alguns exemplos de como.

Por que precisamos de testes automatizados

Os testes automatizados executam partes do código e relatam quaisquer erros. Essa é a maneira mais simples de descrevê-los. Imagine lançar um novo recurso em um aplicativo e, em seguida, um assistente de robô pessoal testaria manualmente o novo recurso, ao mesmo tempo em que testaria se o novo código não violava nenhum dos recursos antigos.

Esta é a principal vantagem: testar novamente todos os recursos automaticamente. Isto pode parecer um trabalho extra, mas se você não disser ao “robô” para fazer isso, devemos alternativamente fazê-lo manualmente, certo? 

Ou novos recursos podem ser lançados sem testar se funcionam, na esperança de que os usuários relatem bugs.

Os testes automatizados podem nos trazer várias vantagens:

  • Economize tempo de teste manual;
  • Permitem poupar tempo tanto na nova função implementada como nas funções consolidadas, evitando regressões;
  • Multiplique esse benefício por todos os novos recursos e por todos os recursos já implementados;
  • Os três pontos anteriores aplicam-se a todas as novas versões;
  • ...

Tente imaginar sua aplicação daqui a um ou dois anos, com novos desenvolvedores na equipe que não conhecem o código escrito nos anos anteriores, ou mesmo como testá-lo. 

Nossos primeiros testes automatizados

Para realizar o primeiro testes automatizados em Laravel, você não precisa escrever nenhum código. Sim, você leu certo. Já está tudo configurado e preparado na pré-instalaçãodefifinal do Laravel, incluindo o primeiro exemplo básico.

Você pode tentar instalar um projeto Laravel e executar os primeiros testes imediatamente:

laravel new project
cd project
php artisan test

Este deve ser o resultado no seu console:

Se dermos uma olhada no prédefinoite de Laravel /tests, temos dois arquivos:

testes/Feature/ExampleTest.php :

class ExampleTest extends TestCase
{
    public function test_the_application_returns_a_successful_response()
    {
        $response = $this->get('/');
 
        $response->assertStatus(200);
    }
}

Você não precisa conhecer nenhuma sintaxe para entender o que está acontecendo aqui: carregue a página inicial e verifique se o código de status HTTP é "200 OK".

Também conhecido como nome do método test_the_application_returns_a_successful_response() torna-se um texto legível quando você visualiza os resultados do teste, simplesmente substituindo o símbolo de sublinhado por um espaço.

testes/Unit/ExampleTest.php :

class ExampleTest extends TestCase
{
    public function test_that_true_is_true()
    {
        $this->assertTrue(true);
    }
}

Parece um pouco inútil verificar se isso é verdade? 

Falaremos especificamente sobre testes unitários um pouco mais tarde. Por enquanto, você precisa entender o que geralmente acontece em cada teste.

  • Cada arquivo de teste na pasta /tests é uma classe PHP que estende o TestCase de PHPUnitName
  • Dentro de cada classe, você pode criar vários métodos, geralmente um método para testar uma situação
  • Dentro de cada método existem três ações: preparação da situação, depois ação e depois verificação (afirmação) se o resultado é o esperado

Estruturalmente, isso é tudo que você precisa saber, todo o resto depende exatamente do que você deseja testar.

Para gerar uma classe de teste vazia, basta executar este comando:

php artisan make:test HomepageTest

O arquivo é gerado tests/Feature/HomepageTest.php:

class HomepageTest extends TestCase
{
    // Replace this method with your own ones
    public function test_example()
    {
        $response = $this->get('/');
 
        $response->assertStatus(200);
    }
}

Agora vamos ver o que acontece se um código de teste falhar no Laravel

Vamos agora ver o que acontece se as asserções do teste não retornarem o resultado esperado.

Vamos mudar os testes de exemplo para isto:

class ExampleTest extends TestCase
{
    public function test_the_application_returns_a_successful_response()
    {
        $response = $this->get('/non-existing-url');
 
        $response->assertStatus(200);
    }
}
 
 
class ExampleTest extends TestCase
{
    public function test_that_true_is_false()
    {
        $this->assertTrue(false);
    }
}

E agora, se executarmos o comando php artisan test de novo:

 FAIL  Tests\Unit\ExampleTest
⨯ that true is true
 
 FAIL  Tests\Feature\ExampleTest
⨯ the application returns a successful response
 
---
 
• Tests\Unit\ExampleTest > that true is true
Failed asserting that false is true.
 
at tests/Unit/ExampleTest.php:16
   12▕      * @return void
   13▕      */
   14▕     public function test_that_true_is_true()
   15▕     {
➜  16▕         $this->assertTrue(false);
   17▕     }
   18▕ }
   19▕
 
• Tests\Feature\ExampleTest > the application returns a successful response
Expected response status code [200] but received 404.
Failed asserting that 200 is identical to 404.
 
at tests/Feature/ExampleTest.php:19
   15▕     public function test_the_application_returns_a_successful_response()
   16▕     {
   17▕         $response = $this->get('/non-existing-url');
   18▕
➜  19▕         $response->assertStatus(200);
   20▕     }
   21▕ }
   22▕
 
 
Tests:  2 failed
Time:   0.11s

Existem dois testes que falharam, marcados como FAIL, com explicações abaixo e setas apontando para a linha exata de testes que falharam. Os erros são indicados desta forma.

Exemplo: Testando o código do formulário de registro no Laravel

Suponha que temos um formulário e precisamos testar vários casos: verificamos se ele falha com dados inválidos, verificamos se funciona com a entrada correta, etc.

O kit inicial oficial por Laravel Breeze inclui eu testando a funcionalidade dentro dele. Vejamos alguns exemplos daí:

tests/Feature/RegistrationTest.php

use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
 
class RegistrationTest extends TestCase
{
    use RefreshDatabase;
 
    public function test_registration_screen_can_be_rendered()
    {
        $response = $this->get('/register');
 
        $response->assertStatus(200);
    }
 
    public function test_new_users_can_register()
    {
        $response = $this->post('/register', [
            'name' => 'Test User',
            'email' => 'test@example.com',
            'password' => 'password',
            'password_confirmation' => 'password',
        ]);
 
        $this->assertAuthenticated();
        $response->assertRedirect(RouteServiceProvider::HOME);
    }
}

Aqui temos dois testes em uma aula, pois ambos estão relacionados ao formulário de inscrição: um verifica se o formulário está carregado corretamente e outro verifica se o envio funciona bem.

Vamos nos familiarizar com mais dois métodos de verificação do resultado, mais duas afirmações: $this->assertAuthenticated()$response->assertRedirect(). Você pode conferir todas as assertivas disponíveis na documentação oficial do PHPUnitName e Resposta do Laravel . Observe que algumas afirmações gerais ocorrem sobre o assunto $this, enquanto outros verificam o específico $responseda chamada de rota.

Outra coisa importante é o use RefreshDatabase;declaração, com o traço, inserida acima da classe. É necessário quando ações de teste podem afetar o banco de dados, como neste exemplo, o log adiciona uma nova entrada no userstabela do banco de dados. Para isso, você deve criar um banco de dados de teste separado que será atualizado com php artisan migrate:freshtoda vez que os testes são executados.

Você tem duas opções: criar fisicamente um banco de dados separado ou usar um banco de dados SQLite na memória. Ambos estão configurados no arquivo phpunit.xmlfornecido por padrãodefiNita com Laravel. Especificamente, você precisa desta parte:

<php>
    <env name="APP_ENV" value="testing"/>
    <env name="BCRYPT_ROUNDS" value="4"/>
    <env name="CACHE_DRIVER" value="array"/>
    <!-- <env name="DB_CONNECTION" value="sqlite"/> -->
    <!-- <env name="DB_DATABASE" value=":memory:"/> -->
    <env name="MAIL_MAILER" value="array"/>
    <env name="QUEUE_CONNECTION" value="sync"/>
    <env name="SESSION_DRIVER" value="array"/>
    <env name="TELESCOPE_ENABLED" value="false"/>
</php>

Veja o DB_CONNECTIONDB_DATABASEquais são comentados? Se você tiver SQLite em seu servidor, a ação mais simples é simplesmente descomentar essas linhas e seus testes serão executados no banco de dados na memória.

Neste teste dizemos que o usuário foi autenticado com sucesso e redirecionado para a página inicial correta, mas também podemos testar os dados reais no banco de dados.

Além deste código:

$this->assertAuthenticated();
$response->assertRedirect(RouteServiceProvider::HOME);

Também podemos usar as asserções de teste do banco de dados e faça algo assim:

$this->assertDatabaseCount('users', 1);
 
// Or...
$this->assertDatabaseHas('users', [
    'email' => 'test@example.com',
]);

Exemplo da página de login

Vamos agora ver outro exemplo de página de Login com Laravel Breeze

tests/Feature/AuthenticationTest.php:

class AuthenticationTest extends TestCase
{
    use RefreshDatabase;
 
    public function test_login_screen_can_be_rendered()
    {
        $response = $this->get('/login');
 
        $response->assertStatus(200);
    }
 
    public function test_users_can_authenticate_using_the_login_screen()
    {
        $user = User::factory()->create();
 
        $response = $this->post('/login', [
            'email' => $user->email,
            'password' => 'password',
        ]);
 
        $this->assertAuthenticated();
        $response->assertRedirect(RouteServiceProvider::HOME);
    }
 
    public function test_users_can_not_authenticate_with_invalid_password()
    {
        $user = User::factory()->create();
 
        $this->post('/login', [
            'email' => $user->email,
            'password' => 'wrong-password',
        ]);
 
        $this->assertGuest();
    }
}

É sobre o formulário de login. A lógica é parecida com o cadastro, certo? Mas três métodos em vez de dois, então este é um exemplo de teste de cenários bons e ruins. Portanto, a lógica comum é que você deve testar os dois casos: quando as coisas vão bem e quando falham.

Boletim de inovação
Não perca as notícias mais importantes sobre inovação. Cadastre-se para recebê-los por e-mail.

Além disso, o que você vê neste teste é o uso de Fábricas de banco de dados : Laravel cria usuário falso ( novamente, em seu banco de dados de teste atualizado ) e tenta fazer login com credenciais corretas ou incorretas.

Mais uma vez, o Laravel gera o pré-fábricadefinita com dados falsos para o Usermodelo, fora da caixa.

database/factories/UserFactory.php:

class UserFactory extends Factory
{
    public function definition()
    {
        return [
            'name' => $this->faker->name(),
            'email' => $this->faker->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
        ];
    }
}

Veja, quantas coisas são preparadas pelo próprio Laravel, então seria fácil começarmos os testes?

Então, se executarmos php artisan testdepois de instalar o Laravel Breeze, devemos ver algo assim:

 PASS  Tests\Unit\ExampleTest
✓ that true is true
 
 PASS  Tests\Feature\Auth\AuthenticationTest
✓ login screen can be rendered
✓ users can authenticate using the login screen
✓ users can not authenticate with invalid password
 
 PASS  Tests\Feature\Auth\EmailVerificationTest
✓ email verification screen can be rendered
✓ email can be verified
✓ email is not verified with invalid hash
 
 PASS  Tests\Feature\Auth\PasswordConfirmationTest
✓ confirm password screen can be rendered
✓ password can be confirmed
✓ password is not confirmed with invalid password
 
 PASS  Tests\Feature\Auth\PasswordResetTest
✓ reset password link screen can be rendered
✓ reset password link can be requested
✓ reset password screen can be rendered
✓ password can be reset with valid token
 
 PASS  Tests\Feature\Auth\RegistrationTest
✓ registration screen can be rendered
✓ new users can register
 
 PASS  Tests\Feature\ExampleTest
✓ the application returns a successful response
 
Tests:  17 passed
Time:   0.61s

Testes funcionais comparados com testes unitários e outros

Você viu as subpastas tests/Feature e tests/Unit ?. 

Qual é a diferença entre eles? 

Globalmente, fora do ecossistema Laravel/PHP, existem vários tipos de testes automatizados. Você pode encontrar termos como:

  • Testes unitários
  • Teste de recursos
  • Testes de integração
  • Testes funcionais
  • Teste de ponta a ponta
  • Testes de aptidão
  • Testes de fumaça
  • etcetera.

Parece complicado e as diferenças reais entre esses tipos de testes às vezes são confusas. É por isso que o Laravel simplificou todos esses termos confusos e os agrupou em dois: unidade/recurso.

Simplificando, os testes de recursos tentam executar a funcionalidade real de seus aplicativos: obter a URL, chamar a API, imitar o comportamento exato, como preencher o formulário. Os testes de recursos geralmente executam operações iguais ou semelhantes às que qualquer usuário do projeto faria, manualmente, na vida real.

Os testes unitários têm dois significados. Em geral, você pode descobrir que qualquer teste automatizado é chamado de “teste unitário” e todo o processo pode ser chamado de “teste unitário”. Mas no contexto de funcionalidade versus unidade, esse processo trata de testar uma unidade de código não pública específica, isoladamente. Por exemplo, você tem uma classe Laravel com um método que calcula algo, como o preço total do pedido com parâmetros. Portanto, o teste unitário indicaria se resultados corretos são retornados daquele método (unidade de código), com parâmetros diferentes.

Para gerar um teste de unidade, você precisa adicionar um sinalizador:

php artisan make:test OrderPriceTest --unit

O código gerado é o mesmo do pré-teste unitáriodefiSistema Laravel:

class OrderPriceTest extends TestCase
{
    public function test_example()
    {
        $this->assertTrue(true);
    }
}

Como você pode ver, isso não existe RefreshDatabase, e este é um dos defidefinições mais comuns de testes unitários: não toca no banco de dados, funciona como uma “caixa preta”, isolada da aplicação em execução.

Tentando imitar o exemplo que citei anteriormente, vamos imaginar que temos uma classe de serviço OrderPrice.

app/Services/OrderPriceService.php:

class OrderPriceService
{
    public function calculatePrice($productId, $quantity, $tax = 0.0)
    {
        // Some kind of calculation logic
    }
}

Então, o teste de unidade poderia ser mais ou menos assim:

class OrderPriceTest extends TestCase
{
    public function test_single_product_no_taxes()
    {
        $product = Product::factory()->create(); // generate a fake product
        $price = (new OrderPriceService())->calculatePrice($product->id, 1);
        $this->assertEquals(1, $price);
    }
 
    public function test_single_product_with_taxes()
    {
        $price = (new OrderPriceService())->calculatePrice($product->id, 1, 20);
        $this->assertEquals(1.2, $price);
    }
 
    // More cases with more parameters
}

Na minha experiência pessoal com projetos Laravel, a grande maioria dos testes são testes de recursos, não testes unitários. Primeiro, você precisa testar se seu aplicativo funciona, da mesma forma que pessoas reais o usariam.

A seguir, se você tiver cálculos ou lógica especiais, poderá definire como uma unidade, com parâmetros, você pode criar testes unitários especificamente para isso.

Às vezes, escrever testes requer modificar o próprio código e refatorá-lo para torná-lo mais “testável”: separar as unidades em classes ou métodos especiais.

Quando/como realizar testes?

Qual é o uso real disso php artisan test, quando você deve executá-lo?

Existem diferentes abordagens, dependendo do fluxo de trabalho do seu negócio, mas geralmente você precisa garantir que todos os testes sejam “verdes” (ou seja, livres de erros) antes de enviar as alterações finais do código para o repositório.

Em seguida, você trabalha localmente em sua tarefa e, quando achar que terminou, execute alguns testes para ter certeza de que não quebrou nada. Lembre-se de que seu código pode causar bugs não apenas em sua lógica, mas também quebrar involuntariamente algum outro comportamento no código de outra pessoa escrito há muito tempo.

Se dermos um passo adiante, é possível automatizar muitos coisas. Com várias ferramentas de CI/CD, você pode especificar testes a serem executados sempre que alguém enviar alterações para uma ramificação específica do Git ou antes de mesclar o código na ramificação de produção. O fluxo de trabalho mais simples seria usar o Github Actions, eu tenho um vídeo separado o que prova isso.

O que você deve testar?

Existem diferentes opiniões sobre o tamanho da chamada “cobertura de teste”: tente todas as operações e casos possíveis em cada página ou limite o trabalho às partes mais importantes.

Na verdade, é aqui que concordo com as pessoas que acusam os testes automatizados de levar mais tempo do que de fornecer benefícios reais. Isso pode acontecer se você escrever testes para cada detalhe. Dito isto, pode ser exigido pelo seu projeto: a questão principal é “qual o preço do erro potencial”.

Em outras palavras, você precisa priorizar seus esforços de teste fazendo a pergunta “O que aconteceria se este código falhasse?” Se o seu sistema de pagamento apresentar bugs, isso afetará diretamente o negócio. Portanto, se a funcionalidade de suas funções/permissões for quebrada, isso será um grande problema de segurança.

Gosto de como Matt Stauffer disse em uma conferência: “Você precisa primeiro testar aquelas coisas que, se falharem, farão com que você seja demitido do emprego”. Claro que isso é um exagero, mas essa é a ideia: tente primeiro as coisas importantes. E depois outros recursos, se você tiver tempo.

PEST: nova alternativa ao PHPUnit

Todos os exemplos acima são baseados na ferramenta de pré-teste Laraveldefinoite: PHPUnitName . Mas ao longo dos anos outras ferramentas apareceram no ecossistema e uma das mais recentes e populares é PESTE . Criado por funcionário oficial do Laravel Nuno Maduro , visa simplificar a sintaxe, tornando a escrita de código para testes ainda mais rápida.

Sob o capô, ele corre su PHPUnit, como camada adicional, apenas tentando minimizar algumas partes pré-repetidasdefifinal do código PHPUnit.

Vejamos um exemplo. Lembre-se da aula de teste pré-recursodefinido em Laravel? Eu vou-te lembrar:

namespace Tests\Feature;
 
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
 
class ExampleTest extends TestCase
{
    public function test_the_application_returns_a_successful_response()
    {
        $response = $this->get('/');
 
        $response->assertStatus(200);
    }
}

Você sabe como seria o mesmo teste com o PEST?

test('the application returns a successful response')->get('/')->assertStatus(200);

Sim, UMA linha de código e pronto. Portanto, o objetivo do PEST é remover a sobrecarga de:

  • Criando classes e métodos para tudo;
  • Extensão de caso de teste;
  • Colocando ações em linhas separadas: no PEST você pode encadeá-las.

Para gerar um teste PEST no Laravel, você precisa especificar um sinalizador adicional:

php artisan make:test HomepageTest --pest

No momento em que este livro foi escrito, PEST era bastante popular entre os desenvolvedores do Laravel, mas é sua preferência pessoal usar esta ferramenta adicional e aprender sua sintaxe, bem como uma nota do PHPUnit.

BlogInnovazione.it

Boletim de inovação
Não perca as notícias mais importantes sobre inovação. Cadastre-se para recebê-los por e-mail.

Artigos recentes

Intervenção inovadora em Realidade Aumentada, com visualizador Apple na Policlínica de Catânia

Uma operação de oftalmoplastia usando o visualizador comercial Apple Vision Pro foi realizada na Policlínica Catania…

3 Maio 2024

Os benefícios das páginas para colorir para crianças - um mundo de magia para todas as idades

O desenvolvimento de habilidades motoras finas por meio da coloração prepara as crianças para habilidades mais complexas, como escrever. Colorir…

2 Maio 2024

O futuro está aqui: como a indústria naval está revolucionando a economia global

O setor naval é uma verdadeira potência económica global, que navegou para um mercado de 150 mil milhões...

1 Maio 2024

Editoras e OpenAI assinam acordos para regular o fluxo de informações processadas por Inteligência Artificial

Na segunda-feira passada, o Financial Times anunciou um acordo com a OpenAI. O FT licencia seu jornalismo de classe mundial…

Abril 30 2024