Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Débusquez les failles cachées: maîtrisez vos te...

Débusquez les failles cachées: maîtrisez vos tests PHP avec Infection PHP & Pest

Slides de la conférence donnée au Meetup AFUP Lyon du 12 novembre 2024.

Plongeons ensemble dans l'univers des tests de mutations et découvrons comment ils exposent les failles invisibles de notre code.

Grâce à l'outil de mutation testing Infection et au framework de test Pest, nous verrons comment nous pouvons renforcer nos tests unitaires pour qu’ils résistent aux erreurs les plus subtiles. Ensemble, nous relèverons le défi de détecter les bugs cachés et d'améliorer la robustesse de nos projets PHP.

Vincent Amstoutz

November 13, 2024
Tweet

Other Decks in Programming

Transcript

  1. Vincent Amstoutz ➔ Coopérateur développeur PHP chez Les-Tilleuls.coop ➔ Contributeur

    open-source dont en dernières contributions : API Platform, API Platform Docs, Symfony Docs, Sulu et AFUP website ➔ Je lis plein de choses sur le Craft @vinceAmstoutz Photo by Ben Griffiths on Unsplash
  2. 70+ experts en API, Web et Cloud ➔ Développement :

    PHP, JS, Go, Rust… ➔ DevOps et Cloud ➔ Consulting et audits ➔ Formations ➔ [email protected] 💌
  3. Les tests ? ± 50% DES PROJETS EN ENTREPRISE Source:

    gartener.com ± 90% DES PROJETS OSS RECONNUS ± 30% DES PROJETS PERSONNELS ET SCOLAIRES 56% API testing 40% Performance testing 45% Integration testing 38% Acceptance testing 33% Load testing
  4. Un game changer, vraiment ? "Le refactor est quelque chose

    que vous faites pour garder votre code sain, tandis que les tests sont là pour vous dire si vous avez cassé quelque chose." – Martin Fowler "Un code sans tests n'est pas propre. Aussi élégant soit-il, aussi lisible et accessible soit-il, s'il n'est pas testé, il n’est pas propre." – Robert C. Martin aka Uncle Bob
  5. ↩ Les migrations et les évolutions techniques Montée de versions,

    changement de framework, changement d’architecture… 🆕 Les évolutions fonctionnelles Nouvelles fonctionnalités ou modification sur les fonctionnalités existantes Les évolutions d’usage Charge plus importante (stress tests, accessibilité…) Un atout principalement pour 📊
  6. KPI tests principaux ➔ Code coverage ➔ Nombre de tests

    comparé au nombre de user stories ➔ Taux de réussite des tests / Taux de faux positifs et faux négatifs ➔ Intégré à la CI/CD
  7. Les tests de mutation ! 🎉 Tests Code source Tests

    de mutation vérifient la qualité des… vérifient la qualité du… Qu’est-ce que sont les tests de mutation ?
  8. MSI indicator Source : InfectionPHP Mutation Score Indicator (MSI) MSI

    is 47%. This means that 47% of all generated mutations were detected (i.e. kills, timeouts or fatal errors). The MSI is the primary Mutation Testing metric. Given the code coverage of 67%, there is a 20% difference so Code Coverage was a terrible quality measurement in this example. Calculation formula:
  9. MCC indicator Source : InfectionPHP Mutation Code Coverage MCC is

    67%. On average it should be within the same ballpark as your normal code coverage. Calculation formula:
  10. CCMI indicator Source : InfectionPHP Covered Code Mutation Score Indicator

    MSI for code that is actually covered by tests was 70% (ignoring not tested code). This shows you how effective the tests really are. Calculation formula:
  11. Configuration PHPUnit <phpunit ...> {...} <testsuites> <testsuite name="Phpunit"> <directory>tests/Phpunit</directory> </testsuite>

    <testsuite name="Pest"> <directory>tests/Pest</directory> </testsuite> </testsuites> {...} </phpunit> ⚠ Nécessite d’installer l’extension PHP XDebug ou Pcov et PHPUnit
  12. Installation d’infection PHP { "$schema": "tools/infection/vendor/infection/infection/resources/schema.json", "testFramework":"phpunit", "testFrameworkOptions": "--testsuite=Phpunit", "source":

    { "directories": ["src/Service"], }, "logs": { "text": "./infection/infection.log", "html": "./infection/infection.html", "summary": "./infection/summary.log", "json": "./infection/infection-log.json", "perMutator": "./infection/per-mutator.md", "summaryJson": "./infection/summary.json" }, "mutators": { "@default": true }, }
  13. final readonly class UserRepository { {...} public function create(string $username,

    #[SensitiveParameter] string $password): User { $passwordHashed = $this->passwordHasher->hash($password); $user = (new User()) ->setUsername($username) ->setPassword($passwordHashed) ; $this->entityManager->persist($user); $this->entityManager->flush(); return $user; } }
  14. final class UseRepositoryTest extends TestCase { private UserRepository $userRepository; private

    EntityManagerInterface&MockObject $entityManager; //bad practice for demo private PasswordHasherInterface&MockObject $passwordHasher; //bad practice for demo protected function setUp(): void {...} public function test_create_user(): void { $this->entityManager ->expects(self::once()) ->method('persist') ->with(self::isInstanceOf(User::class)); $this->passwordHasher->method('hash') ->with('test') ->willReturn('hashed_test_password'); $user = $this->userRepository->create('Alice', 'test'); self::assertInstanceOf(User::class, $user); self::assertSame('Alice', $user->getUsername()); self::assertSame('hashed_test_password', $user->getPassword()); {...}
  15. Avec correction public function test_create_user(): void { $this->entityManager ->expects(self::once()) ->method('persist')

    ->with(self::isInstanceOf(User::class)); $this->entityManager ->expects(self::once()) ->method('flush'); {...}
  16. final readonly class UserRepository { {...} public function create(string $username,

    #[SensitiveParameter] string $password): User { $passwordHashed = $this->passwordHasher->hash($password); $user = (new User()) ->setUsername($username) ->setPassword($passwordHashed) ; $this->entityManager->persist($user); $this->entityManager->flush(); return $user; } }
  17. Tests {...} beforeEach(function (): void { $this->entityManager = Mockery::mock(EntityManagerInterface::class); //bad

    practice for demo $this->passwordHasher = Mockery::mock(PasswordHasherInterface::class); //bad practice for demo $this->userRepository = new UserRepository($this->entityManager, $this->passwordHasher); }); it('creates a user', function (): void { $this->entityManager ->shouldReceive('persist') ->once() ->with(Mockery::type(User::class)); $this->entityManager ->shouldReceive('flush'); $this->passwordHasher ->shouldReceive('hash') ->with('test') ->andReturn('hashed_test_password'); $user = $this->userRepository->create('Alice', 'test'); expect($user)->toBeInstanceOf(User::class); expect($user->getUsername())->toBe('Alice'); expect($user->getPassword())->toBe('hashed_test_password'); });
  18. Tests de mutation {...} mutates(UserRepository::class); beforeEach(function (): void { {...}

    }); it('creates a user', function (): void { $this->entityManager ->shouldReceive('persist') ->once() ->with(Mockery::type(User::class)); $this->entityManager ->shouldReceive('flush'); ->once(); {...}
  19. Infection VS Pest Pros ➔ Indépendant ➔ Plusieurs formats d’output

    ➔ API complète ➔ Compatible sur tout l'écosystème PHP notamment tous les framework de tests Cons ➔ Version 0.* depuis 2017 Pros ➔ Interface console agréable ➔ API complète et qui étend celle de PHPUnit ➔ Exécution parallèle disponible ➔ Inclus des outils de tests avancés Cons ➔ Nécessite un peu de code supplémentaire pour la mutation ➔ Peu de documentation sur la configuration (output) VS
  20. Pour creuser le sujet des tests Leaders sur le sujet

    ➔ https://fr.linkedin.com/in/michael-azerhad (FR) ➔ https://fr.linkedin.com/in/pierre-criulanscy (FR) ➔ https://fr.linkedin.com/in/anthony-cyrille (FR) ➔ https://fr.linkedin.com/in/valentinajemuovic (EN) Livres ➔ Software Craft - TDD, Clean Code & other essential practices By C. Martaire, A. Thiéfaine, D. Bartaguiz, F. Hiegel & H. Fakiih (FR) ➔ Clean Code: A Handbook of Agile Software Craftsmanship By Robert C. Martin (EN)
  21. Merci de votre attention ! ➔ Des questions ? @vincent-amstoutz

    @vinceAmstoutz @vinceAmstoutz Feedbacks appréciés ! 󰚦