Posted on: April 01, 2021 07:37 PM
Posted by: Renato
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.xml
arquivo 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.xml
arquivo que contém configurações com as quais phpunit
você executará os testes. Se desejar alterar essas configurações, você pode fazer isso a partir do arquivo ou criar um .env.testing
arquivo.
O phpunit.xml
arquivo 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
sqlite
como banco de dados de teste.
Vamos criar um arquivo de configuração separado para teste. Crie um .env.testing
arquivo 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.sqlite
arquivo:
$ touch database/test.sqlite
Agora, migre e propague o banco de dados de teste:
$ php artisan migrate --seed --env=testing
Adicionar
--env=testing
sinalizador diráartisan
ao laravel para usar as configurações de teste que fizemos no.env.testing
arquivo.
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:
- Produtos, usuários e pedidos podem ser criados, lidos, atualizados e excluídos.
- Um usuário pode fazer pedidos válidos.
- Um usuário não pode fazer um pedido de um produto que não existe.
- Um administrador pode confirmar que um pedido foi enviado.
- 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.php
arquivo, 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 Unauthenticated
có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 ProductTest
classe
[...]
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 factory
helper no Laravel. Ele cria o objeto do usuário e preenche o conteúdo do fillable
array 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 Ok
. També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 UploadFile
classe em nossa classe ProductTest.
[...]
<?php
use Illuminate\Http\UploadedFile;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
[...]
Usaremos a UploadedFile
classe 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 testUpdateProduct
teste funciona, exceto que enviamos uma DELETE
solicitação em vez de uma POST
solicitaçã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.php
arquivo, 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 Ok
. També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_delivered
atributo é true
e tem a mesma id
ordem 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 Ok
. A 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 Ok
. Verificaremos 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 testUpdateOrder
teste funciona, exceto que enviamos uma DELETE
solicitação em vez de uma POST
solicitaçã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
Nota : Em uma máquina Windows, seu resultado será ligeiramente diferente. Você verá um erro relacionado ao
testUploadImage
caso 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
Donate to Site
Renato
Developer