Do not speak Portuguese? Translate this site with Google or Bing Translator
Como escrever testes para aplicativos Laravel

Posted on: April 01, 2021 07:37 PM

Posted by: Renato

Categories: Laravel PHP phpunit

Views: 7498

Escrever testes para um aplicativo é uma ótima prática de desenvolvimento de software. Freqüentemente, é a primeira tarefa de codificação a ser executada antes de construir os recursos de um aplicativo. Como a primeira tarefa de codificação, os testes são inicialmente escritos para falhar porque não temos nenhum código para satisfazer os requisitos do teste. Assim que nossos testes falharem, podemos construir nosso aplicativo para passar em nossos testes. Essa prática garante que nosso código atenda aos requisitos de software especificados. Também serve como um guia ao estender nosso aplicativo ou ao refatorar. Essa prática é comumente conhecida como “Test-driven Development” (TDD).

Pré-requisitos

  • Conhecimento básico de PHP e Laravel
  • Tenha o Composer instalado em sua máquina local
  • Tenha o PHP configurado em sua máquina local
  • Git instalado em sua máquina local
  • Um clone deste projeto

Introdução

Existem diferentes tipos de testes que você pode executar em seu aplicativo. Existe o teste de unidade, que se concentra em testar a funcionalidade de uma pequena parte do seu aplicativo, como um punhado de métodos ou uma classe.

Há um teste de recurso que testa se um recurso inteiro realmente funciona. Neste ponto, você pode testar muitas classes e métodos, ou um pacote inteiro, dependendo de como seu aplicativo está estruturado.

Também há um teste de integração que analisa como as diferentes partes de seu aplicativo se combinam. Os testes de integração são sempre importantes ao construir aplicativos em grande escala com muitas unidades funcionais. Esses testes ajudam a garantir que cada parte de seu aplicativo funcione como deveria. Também garante que outras peças que dependem deles não falhem devido ao seu erro.

Este primeiro parágrafo do guia de teste do Laravel diz:

O Laravel foi desenvolvido com os testes em mente. Na verdade, o suporte para teste com PHPUnit está incluído fora da caixa e um phpunit.xmlarquivo já está configurado para seu aplicativo. A estrutura também vem com métodos auxiliares convenientes que permitem que você teste expressivamente seus aplicativos.

Isso mostra que temos a base certa para construir um aplicativo orientado a testes. Vamos aproveitar a provisão do Laravel para testes para configurar um ambiente de teste.

Vamos escrever testes para um aplicativo de e-commerce construído com Laravel e Vue.

Configurando nosso ambiente de teste

O Laravel vem com um phpunit.xmlarquivo que contém configurações com as quais phpunitvocê executará os testes. Se desejar alterar essas configurações, você pode fazer isso a partir do arquivo ou criar um .env.testingarquivo.

phpunit.xmlarquivo contém variáveis ​​de ambiente que definirão como seu aplicativo é executado durante o teste. Você pode definir uma configuração de banco de dados diferente para teste para preservar a integridade dos dados que possui. Você também pode definir configurações diferentes para sessões, cache, filas, e-mails ou mesmo ferramentas de terceiros.

Ao criar aplicativos com dados confidenciais, sempre use um banco de dados diferente para teste. Dependendo das necessidades do seu aplicativo, você pode querer usá-lo sqlitecomo banco de dados de teste.

Vamos criar um arquivo de configuração separado para teste. Crie um .env.testingarquivo e adicione o seguinte código:


    APP_NAME=Laravel
    APP_ENV=testing
    APP_KEY=base64:5CpEFQ9UTR543dbJUsT3araoSSyxuN8NF92gCJJXpk8=
    APP_DEBUG=true
    APP_URL=http://localhost

    LOG_CHANNEL=stack

    DB_CONNECTION=sqlite
    DB_DATABASE=/absolute/path/to/test.sqlite

    BROADCAST_DRIVER=log
    CACHE_DRIVER=array
    SESSION_DRIVER=array
    SESSION_LIFETIME=120
    QUEUE_DRIVER=sync

    MAIL_DRIVER=array

Em seguida, crie o database/test.sqlitearquivo:

    $ touch database/test.sqlite

Agora, migre e propague o banco de dados de teste:

    $ php artisan migrate --seed --env=testing

Adicionar --env=testingsinalizador dirá artisanao laravel para usar as configurações de teste que fizemos no .env.testingarquivo.

Agora, estamos prontos para começar a escrever nossos testes.

Escrevendo nossos casos de teste

Para o aplicativo de e-commerce, estaremos escrevendo testes para cada unidade de nosso aplicativo. Queremos escrever testes para garantir que:

  1. Produtos, usuários e pedidos podem ser criados, lidos, atualizados e excluídos.
  2. Um usuário pode fazer pedidos válidos.
  3. Um usuário não pode fazer um pedido de um produto que não existe.
  4. Um administrador pode confirmar que um pedido foi enviado.
  5. O pedido de um usuário será atualizado quando for enviado.

Estaremos testando todos os endpoints da API para garantir que realmente funcione conforme o esperado. Se tivéssemos observadores ou repositórios que lidassem com lógicas complexas de aplicativos, poderíamos testá-los para garantir que funcionassem conforme o esperado. Isso ocorre porque os erros que podemos encontrar nos terminais da API seriam originados a partir daí.

Como nosso aplicativo de amostra é simples e enxuto, testaremos os endpoints da API.

Testando os endpoints do produto

Para testar os endpoints do produto, execute o seguinte comando em seu terminal para criar a classe de teste:

    $ php artisan make:test ProductTest --unit

Agora, abra o tests/Unit/ProductTest.phparquivo, você deverá ver o modelo criado para que possamos trabalhar.

    <?php

    namespace Tests\Unit;

    use Tests\TestCase;
    use Illuminate\Foundation\Testing\WithFaker;
    use Illuminate\Foundation\Testing\RefreshDatabase;

    class ProductTest extends TestCase
    {
        /**
         * A basic test example.
         *
         * @return void
         */
        public function testExample()
        {
            $this->assertTrue(true);
        }
    }

Vamos substituir o conteúdo do ProductTest por sete métodos de teste diferentes.

testCreateProductWithMiddleware

O primeiro teste que criaremos é um teste que tenta criar um produto sem um token de autenticação. Ele agirá como se um usuário que não está logado tentasse criar um produto. Para que este teste seja aprovado, o teste deve retornar um 401 Unauthenticatedcódigo HTTP e não criará um novo produto. Se esse teste falhar, significa que um usuário não autenticado pode criar um produto neste aplicativo. Saberemos o que corrigir imediatamente. Insira o código abaixo na ProductTestclasse

    [...] 
    public function testCreateProductWithMiddleware()
        {
                $data = [
                        'name' => "New Product",
                        'description' => "This is a product",
                        'units' => 20,
                        'price' => 10,
                        'image' => "https://images.pexels.com/photos/1000084/pexels-photo-1000084.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"
                               ];

            $response = $this->json('POST', '/api/products',$data);
            $response->assertStatus(401);
            $response->assertJson(['message' => "Unauthenticated."]);
        }
    [...]

testCreateProduct

Neste teste, vamos usar factory(\App\User::class)->create()para criar um objeto de usuário falso usando o factoryhelper no Laravel. Ele cria o objeto do usuário e preenche o conteúdo do fillablearray que definimos no modelo do usuário.

Usaremos o usuário falso criado para fazer uma solicitação XHR à nossa API como esta $this->actingAs($user, 'api')->json()e obter um objeto de resposta completa. Verificaremos para garantir que o objeto de resposta contém um código de status HTTP de sucesso 200 Ok.

Também verificaremos se a resposta JSON retornada da solicitação contém alguns argumentos. Depois disso, verificamos se esses argumentos contêm alguns dados usando $response→assertJson().

    public function testCreateProduct()
    {
       $data = [
                        'name' => "New Product",
                        'description' => "This is a product",
                        'units' => 20,
                        'price' => 10,
                        'image' => "https://images.pexels.com/photos/1000084/pexels-photo-1000084.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"
                    ];
            $user = factory(\App\User::class)->create();
            $response = $this->actingAs($user, 'api')->json('POST', '/api/products',$data);
            $response->assertStatus(200);
            $response->assertJson(['status' => true]);
            $response->assertJson(['message' => "Product Created!"]);
            $response->assertJson(['data' => $data]);
      }
    [...]

testGettingAllProducts

Aqui, chamamos a API para devolução de todos os produtos e verificamos se o código de status é 200 OkTambém verificamos para garantir que os dados que ele retorna possuem uma determinada estrutura com certos argumentos. Não sabemos os dados retornados, mas se os dados assumirem uma estrutura específica, temos certeza de que serão precisos.

Esta é uma afirmação importante que é útil quando você está retornando um grande conjunto de dados.

    [...]
    public function testGettingAllProducts()
    {
            $response = $this->json('GET', '/api/products');
            $response->assertStatus(200);

            $response->assertJsonStructure(
                [
                    [
                            'id',
                            'name',
                            'description',
                            'units',
                            'price',
                            'image',
                            'created_at',
                            'updated_at'
                    ]
                ]
            );
        }
    [...]

testUpdateProduct

Para este teste, faremos uma chamada para o endpoint da API de produtos para obter todos os produtos disponíveis. Em seguida, escolhemos o primeiro produto retornado pelo terminal. Esta é uma etapa importante, para garantir que estejamos atualizando um produto real e não tenhamos a API gerando erros.

Depois de escolher um produto real, tentamos atualizar o nome do produto e verificar se a mensagem de resposta que recebemos é a correta para quando o produto é atualizado.

    [...]
    public function testUpdateProduct()
    {
            $response = $this->json('GET', '/api/products');
            $response->assertStatus(200);

            $product = $response->getData()[0];

            $user = factory(\App\User::class)->create();
            $update = $this->actingAs($user, 'api')->json('PATCH', '/api/products/'.$product->id,['name' => "Changed for test"]);
            $update->assertStatus(200);
            $update->assertJson(['message' => "Product Updated!"]);
        } 
    [...]

testUploadImage

Para testar esse endpoint, precisaremos incluir uma UploadFileclasse em nossa classe ProductTest.

    [...]
    <?php
    use Illuminate\Http\UploadedFile;

    use Illuminate\Foundation\Testing\WithFaker;
    use Illuminate\Foundation\Testing\RefreshDatabase;
    [...]

Usaremos a UploadedFileclasse para criar um arquivo falso de imagem enviada e, em seguida, testar se o upload da imagem funciona. Como os dados de resposta são o caminho completo da imagem com base no aplicativo, verificaremos se a resposta retornada não é nula.

    [...]
    public function testUploadImage()
    {
            $response = $this->json('POST', '/api/upload-file', [
                'image' => UploadedFile::fake()->image('image.jpg')
            ]);
            $response->assertStatus(201);
            $this->assertNotNull($response->getData());
    }
    [...]

testDeleteProduct

Este teste funciona da mesma maneira que o testUpdateProductteste funciona, exceto que enviamos uma DELETEsolicitação em vez de uma POSTsolicitação.

    [...]
    public function testDeleteProduct()
    {
            $response = $this->json('GET', '/api/products');
            $response->assertStatus(200);

            $product = $response->getData()[0];

            $user = factory(\App\User::class)->create();
            $delete = $this->actingAs($user, 'api')->json('DELETE', '/api/products/'.$product->id);
            $delete->assertStatus(200);
            $delete->assertJson(['message' => "Product Deleted!"]);
        }
    [...]

Isso conclui todos os testes necessários para os terminais do “Produto”. A seguir, testaremos todos os terminais de “Pedido”.

Testando os endpoints do pedido

Para criar a classe de teste, execute o seguinte comando:

    $ php artisan make:test OrderTest --unit

Agora, abra o tests/Unit/OrderTest.phparquivo, você deverá ver o modelo criado para que possamos trabalhar.

    <?php

    namespace Tests\Unit;

    use Tests\TestCase;
    use Illuminate\Foundation\Testing\WithFaker;
    use Illuminate\Foundation\Testing\RefreshDatabase;

    class OrderTest extends TestCase
    {
        /**
         * A basic test example.
         *
         * @return void
         */
        public function testExample()
        {
            $this->assertTrue(true);
        }
    }

Vamos escrever todas as nossas funções de teste dentro da classe OrderTest.

testCreateOrder

Neste teste, usaremos um usuário falso para fazer uma solicitação de postagem de XHR para a solicitação de postagem de XHR para nossa API. Verificamos se o objeto de resposta possui o código de status HTTP de sucesso 200 Ok.

Também verificaremos se a resposta JSON retornada da solicitação contém alguns argumentos. Também verificaremos para garantir que esses argumentos contenham alguns dados, pois é isso que esperamos. Por fim, verificaremos a estrutura dos dados para ter certeza de que contêm as informações corretas.

    [....]
    public function testCreateOrder()
    {
            $data  = [
                        'product' => 1,
                        'quantity' => 20,
                        'address' => "No place like home"
                    ];
            $user   = factory(\App\User::class)->create();
            $response = $this->actingAs($user, 'api')->json('POST', '/api/orders',$data);
            $response->assertStatus(200);
            $response->assertJson(['status'        => true]);
            $response->assertJson(['message'       => "Order Created!"]);
            $response->assertJsonStructure(['data' => [
                                    'id',
                                    'product_id',
                                    'user_id',
                                    'quantity',
                                    'address',
                                    'created_at',
                                    'updated_at'
                            ]]);
        }
      [...]

testGetAllOrders

Para este teste, chamaremos o endpoint responsável por retornar todos os pedidos e verificaremos se o código de status retornado é 200 OkTambém verificaremos se os dados que ele retorna possuem uma certa estrutura contendo certos argumentos.

    [...]
    public function testGetAllOrders()
    {
            $user             = factory(\App\User::class)->create();
            $response         = $this->actingAs($user, 'api')->json('GET', '/api/orders');
            $response->assertStatus(200);
            $response->assertJsonStructure(
                    [
                            [
                                    'id',
                                    'product_id',
                                    'user_id',
                                    'quantity',
                                    'address',
                                    'created_at',
                                    'updated_at'
                            ]
                    ]
                );
        }
    [...]

testDeliverOrder

Aqui, faremos uma chamada para o endpoint responsável por devolver todos os pedidos disponíveis, em seguida, escolhemos o primeiro pedido. Tentamos entregar o pedido e obter os dados da resposta.

Examinaremos esses dados para garantir que o is_deliveredatributo é truee tem a mesma idordem que realmente tentamos atualizar.

    [...]
    public function testDeliverOrder()
    {
            $user      = factory(\App\User::class)->create();
            $response  = $this->actingAs($user, 'api')->json('GET', '/api/orders');
            $response->assertStatus(200);

            $order     = $response->getData()[0];

            $update    = $this->actingAs($user, 'api')->json('PATCH', '/api/orders/'.$order->id."/deliver");
            $update->assertStatus(200);
            $update->assertJson(['message' => "Order Delivered!"]);

            $updatedOrder = $update->getData('data');
            $this->assertTrue($updatedOrder['data']['is_delivered']);
            $this->assertEquals($updatedOrder['data']['id'], $order->id);
        }
    [...]

testUpdateOrder

Aqui, faremos uma chamada para o endpoint responsável por retornar todos os pedidos e verificar se o código de status é 200 OkA seguir, escolheremos o primeiro pedido e tentaremos alterar sua quantidade. Verificaremos a resposta que recebemos e garantir que ela contenha um código de status de 200 OkVerificaremos também se a mensagem retornou após a atualização do pedido.

    [...]
    public function testUpdateOrder()
    {
            $user      = factory(\App\User::class)->create();
            $response  = $this->actingAs($user, 'api')->json('GET', '/api/orders');
            $response->assertStatus(200);

            $order     = $response->getData()[0];

            $update    = $this->actingAs($user, 'api')->json('PATCH', '/api/orders/'.$order->id,['quantity' => ($order->id+5)]);
            $update->assertStatus(200);
            $update->assertJson(['message' => "Order Updated!"]);
        }

    [...]

testDeleteOrder

Este teste funciona da mesma maneira que o testUpdateOrderteste funciona, exceto que enviamos uma DELETEsolicitação em vez de uma POSTsolicitação. O objetivo é excluir um pedido com sucesso.

    [...]
    public function testDeleteOrder()
        {
            $user     = factory(\App\User::class)->create();
            $response = $this->actingAs($user, 'api')->json('GET', '/api/orders');
            $response->assertStatus(200);

            $order    = $response->getData()[0];

            $update   = $this->actingAs($user, 'api')->json('DELETE', '/api/orders/'.$order->id);
            $update->assertStatus(200);
            $update->assertJson(['message' => "Order Deleted!"]);
        }
    [...]

Executando os testes

Depois de terminar de escrever os testes, execute-o com o seguinte comando em seu terminal:

    $ ./vendor/bin/phpunit

Captura de tela de aprovação de testes

Nota : Em uma máquina Windows, seu resultado será ligeiramente diferente. Você verá um erro relacionado ao testUploadImagecaso de teste. Isso ocorre devido a um problema de permissões apenas do Windows.

 

Fontes:

- https://blog.pusher.com/tests-laravel-applications/

- https://github.com/lucenarenato/laravel8-phpunit-TDD/blob/main/README.md


5

Share

Donate to Site


About Author

Renato

Developer

Add a Comment

Blog Search


Categories

OUTROS (16) Variados (109) PHP (133) Laravel (171) Black Hat (3) front-end (29) linux (114) postgresql (39) Docker (28) rest (5) soap (1) webservice (6) October (1) CMS (2) node (7) backend (13) ubuntu (56) devops (25) nodejs (5) npm (3) nvm (1) git (8) firefox (1) react (7) reactnative (5) collections (1) javascript (7) reactjs (8) yarn (0) adb (1) Solid (2) blade (3) models (1) controllers (0) log (1) html (2) hardware (3) aws (14) Transcribe (2) transcription (1) google (4) ibm (1) nuance (1) PHP Swoole (5) mysql (31) macox (4) flutter (1) symfony (1) cor (1) colors (2) homeOffice (2) jobs (3) imagick (2) ec2 (1) sw (1) websocket (2) markdown (1) ckeditor (1) tecnologia (14) faceapp (1) eloquent (14) query (4) sql (40) ddd (3) nginx (9) apache (4) certbot (1) lets-encrypt (3) debian (12) liquid (1) magento (2) ruby (1) LETSENCRYPT (1) Fibonacci (1) wine (1) transaction (1) pendrive (1) boot (1) usb (1) prf (1) policia (2) federal (1) lucena (1) mongodb (4) paypal (1) payment (1) zend (1) vim (4) ciencia (6) js (1) nosql (1) java (1) JasperReports (1) phpjasper (1) covid19 (1) saude (1) athena (1) cinnamon (1) phpunit (2) binaural (1) mysqli (3) database (42) windows (6) vala (1) json (2) oracle (1) mariadb (4) dev (12) webdev (24) s3 (4) storage (1) kitematic (1) gnome (2) web (2) intel (3) piada (1) cron (2) dba (18) lumen (1) ffmpeg (2) android (2) aplicativo (1) fedora (2) shell (4) bash (3) script (3) lider (1) htm (1) csv (1) dropbox (1) db (3) combustivel (2) haru (1) presenter (1) gasolina (1) MeioAmbiente (1) Grunt (1) biologia (1) programming (22) performance (3) brain (1) smartphones (1) telefonia (1) privacidade (1) opensource (3) microg (1) iode (1) ssh (3) zsh (2) terminal (3) dracula (1) spaceship (1) mac (2) idiomas (1) laptop (2) developer (37) api (5) data (1) matematica (1) seguranca (2) 100DaysOfCode (9) hotfix (1) documentation (1) laravelphp (10) RabbitMQ (3) Elasticsearch (1) redis (2) Raspberry (4) Padrao de design (4) JQuery (1) angularjs (4) Dicas (43) Kubernetes (3) vscode (2) backup (1) angular (3) servers (2) pipelines (1) AppSec (1) DevSecOps (4) rust (1) RustLang (1) Mozilla (1) algoritimo (1) sqlite (1) Passport (2) jwt (5) security (2) translate (1) kube (2) iot (1) politica (2) bolsonaro (1) flow (1) podcast (1) Brasil (1) containers (3) traefik (1) networking (1) host (1) POO (2) microservices (2) bug (1) cqrs (1) arquitetura (3) Architecture (4) sail (3) militar (1) artigo (1) economia (1) forcas armadas (1) ffaa (1) autenticacao (2) autorizacao (2) authentication (4) authorization (3) NoCookies (1) wsl (4) memcached (1) macos (2) unix (2) kali-linux (1) linux-tools (5) apple (1) noticias (2) composer (1) rancher (1) k8s (1) escopos (1) orm (1) jenkins (4) github (5) gitlab (3) queue (1) Passwordless (1) sonarqube (1) phpswoole (1) laraveloctane (1) Swoole (1) Swoole (1) octane (1) Structurizr (1) Diagramas (1) c4 (1) c4-models (1) compactar (1) compression (1) messaging (1) restfull (1) eventdrive (1) services (1) http (1) Monolith (1) microservice (1) historia (1) educacao (1) cavalotroia (1) OOD (0) odd (1) chatgpt (1) openai (3) vicuna (1) llama (1) gpt (1) transformers (1) pytorch (1) tensorflow (1) akitando (1) ia (1) nvidia (1) agi (1) guard (1) multiple_authen (2) rpi (1) auth (1) auth (1) livros (2) ElonMusk (2) Oh My Zsh (1) Manjaro (1) BigLinux (2) ArchLinux (1) Migration (1) Error (1) Monitor (1) Filament (1) LaravelFilament (1) replication (1) phpfpm (1) cache (1) vpn (1) l2tp (1) zorin-os (1) optimization (1) scheduling (1) monitoring (2) linkedin (1) community (1) inteligencia-artificial (2) wsl2 (1) maps (1) API_KEY_GOOGLE_MAPS (1) repmgr (1) altadisponibilidade (1) banco (1) modelagemdedados (1) inteligenciadedados (4) governancadedados (1) bancodedados (2) Observability (1) picpay (1) ecommerce (1) Curisidades (1) Samurai (1) KubeCon (1) GitOps (1) Axios (1) Fetch (1) Deepin (1) vue (4) nuxt (1) PKCE (1) Oauth2 (2) webhook (1) TypeScript (1) tailwind (1) gource (2)

New Articles



Get Latest Updates by Email