Les tests unitaires
Dans le développement d’applications modernes. Les tests sont primordiaux.
Les bénéfices des tests dans Laravel
Tests unitaires
- Détection précoce des bugs : Les tests unitaires permettent d'identifier les problèmes dans le code avant qu'ils n'atteignent la production.
- Documentation vivante : Ils servent de documentation technique qui reste à jour avec le code, contrairement aux documents classiques.
- Refactoring en toute confiance : Vous pouvez améliorer votre code sans craindre de casser des fonctionnalités existantes.
- Développement plus rapide : Après l'investissement initial, les tests accélèrent le développement en réduisant le temps de débogage.
- Meilleure architecture : Écrire des tests pousse à créer un code plus modulaire et mieux structuré.
Tests de fonctionnalités
- Validation des scénarios d'utilisation : Ils garantissent que les fonctionnalités répondent aux besoins des utilisateurs.
- Tests de l'intégration : Ils vérifient que les différentes parties de l'application fonctionnent correctement ensemble.
- Simulation de l'expérience utilisateur : Dans Laravel, ces tests peuvent simuler des interactions comme la navigation, les clics ou les soumissions de formulaires.
- Protection contre les régressions : Ils s'assurent que les nouvelles fonctionnalités n'affectent pas négativement celles existantes.
Claude.ai
Bien que les développeurs testent continuellement leur code, les tests doivent pouvoir être reproduits facilement et rapidement, c’est pourquoi certains packages ont été développés comme phpUnit ou Pest pour Laravel.
Ils existent plusieurs types de tests :
- Tests unitaires
- Tests d’intégration
- Tests E2E (End to End)
Un exemple de test unitaire
<?php
namespace Tests\Unit;
use App\Services\Calculator;
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase
{
/** @test */
public function it_can_add_two_numbers()
{
// Arrange
$calculator = new Calculator();
// Act
$result = $calculator->add(5, 3);
// Assert
$this->assertEquals(8, $result);
}
/** @test */
public function it_can_subtract_two_numbers()
{
// Arrange
$calculator = new Calculator();
// Act
$result = $calculator->subtract(10, 4);
// Assert
$this->assertEquals(6, $result);
}
/** @test */
public function it_returns_zero_when_dividing_by_zero()
{
// Arrange
$calculator = new Calculator();
// Act
$result = $calculator->divide(10, 0);
// Assert
$this->assertEquals(0, $result);
}
}
Les tests unitaires sont faits pour tester des petits morceaux de code, comme une fonction ou une méthode. Dans l'exemple ci dessus, on test simplement, que la classe calculator fonctionne correctement.
Un exemple de test d’intégration
<?php
namespace Tests\Integration;
use App\Models\User;
use App\Repositories\UserRepository;
use App\Services\UserService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class UserServiceTest extends TestCase
{
use RefreshDatabase;
private UserService $userService;
private UserRepository $userRepository;
protected function setUp(): void
{
parent::setUp();
$this->userRepository = new UserRepository();
$this->userService = new UserService($this->userRepository);
}
/** @test */
public function it_can_create_and_retrieve_a_user()
{
// Arrange
$userData = [
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => 'password123'
];
// Act
$createdUser = $this->userService->createUser($userData);
$retrievedUser = $this->userService->getUserById($createdUser->id);
// Assert
$this->assertInstanceOf(User::class, $retrievedUser);
$this->assertEquals($userData['name'], $retrievedUser->name);
$this->assertEquals($userData['email'], $retrievedUser->email);
}
/** @test */
public function it_can_update_user_information()
{
// Arrange
$user = User::factory()->create();
$updatedData = [
'name' => 'Updated Name',
'email' => 'updated@example.com'
];
// Act
$updatedUser = $this->userService->updateUser($user->id, $updatedData);
// Assert
$this->assertEquals($updatedData['name'], $updatedUser->name);
$this->assertEquals($updatedData['email'], $updatedUser->email);
}
}
Les tests d’intégration vérifient l'interaction correcte entre plusieurs composants. Voici un exemple de test d'intégration pour un service qui utilise un repository.
Un exemple de test E2E
<?php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ProductManagementTest extends TestCase
{
use RefreshDatabase;
/** @test */
public function authorized_users_can_create_products()
{
// Arrange
$user = User::factory()->create(['role' => 'admin']);
$productData = [
'name' => 'Test Product',
'description' => 'This is a test product',
'price' => 19.99,
'stock' => 50
];
// Act
$response = $this->actingAs($user)
->post('/products', $productData);
// Assert
$response->assertStatus(302); // Redirection après création
$response->assertRedirect('/products');
// Vérifier que le produit existe dans la base de données
$this->assertDatabaseHas('products', [
'name' => 'Test Product',
'price' => 19.99
]);
}
/** @test */
public function guests_cannot_create_products()
{
// Arrange
$productData = [
'name' => 'Test Product',
'description' => 'This is a test product',
'price' => 19.99,
'stock' => 50
];
// Act
$response = $this->post('/products', $productData);
// Assert
$response->assertStatus(302); // Redirection vers login
$response->assertRedirect('/login');
// Vérifier que le produit n'existe pas dans la base de données
$this->assertDatabaseMissing('products', [
'name' => 'Test Product'
]);
}
/** @test */
public function users_can_view_product_details()
{
// Arrange
$user = User::factory()->create();
$product = \App\Models\Product::factory()->create([
'name' => 'Detailed Product',
'description' => 'Product with detailed description',
'price' => 29.99
]);
// Act
$response = $this->actingAs($user)
->get("/products/{$product->id}");
// Assert
$response->assertStatus(200);
$response->assertSee('Detailed Product');
$response->assertSee('Product with detailed description');
$response->assertSee('29.99');
}
}
Les tests E2E vérifient le comportement global d'une fonctionnalité du point de vue de l'utilisateur. Dans l’univers Laravel, cela correspond aux tests de fonctionnalités ou features tests.
Vous l’aurez compris, on parle de tests unitaires mais 95% du temps, on écrit des tests de fonctionnalités.
Les tests sont organisés sous la règle des 3 A, prenons un exemple pour démontrer cette régle, je veux tester qu'un utilisateur connecté, peut accéder a sa page de profil :
<?php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ProfileTest extends TestCase
{
use RefreshDatabase;
public function test_profile_page_is_displayed(): void
{
// ARRANGE
$user = User::factory()->create();
// ACT
$response = $this
->actingAs($user)
->get('/profile');
// ASSERT
$response->assertStatus(200);
}
}
- Arrange : je prépare tout ce que j'aurai besoin pour réaliser mon test, dans cette exemple, j'ai besoin de créer un utilisateur
- Act : j'agis, je test, ici je demande a consulter l'url m'amenant à ma page de profil, en étant connecté (actingAs), et j'enregistre la réponse
- Assert : j'affirme que la réponse me renverra un http statut code à 200, soit que la page existe bien
C'est le principe essentiel d'un test. D'un point de vue technique, dans phpUnit, une méthode de test, doit toujours commencer par test.
Il est également toléré, que la méthode soit écrite en snake-case au lieu du camel-case, car c'est plus lisible.
La méthode de test, doit décrire le test, soyez exhaustif, dans notre exemple, "La page de profil est affiché".
https://laravel.com/docs/12.x/testing
Le 14 février 2025
Temps de lecture : 10 min