Slide 1

Slide 1 text

Detect hidden defects Check your PHP tests using Infection & Pest Photo by CDC on Unsplash © Vincent AMSTOUTZ

Slide 2

Slide 2 text

Vincent Amstoutz ➔ Senior dev @Les-Tilleuls.coop cooperative ➔ OSS contributor ➔ Tech & inspiration readings ➔ Sports and music lover @vinceAmstoutz Photo by Ben Griffiths on Unsplash @vincent-amstoutz

Slide 3

Slide 3 text

70+ experts en API, Web et Cloud ➔ Programming : PHP, JS, Go, Rust… ➔ DevOps & Cloud ➔ Consulting & audits ➔ Trainings ➔ [email protected] 💌 © Vincent AMSTOUTZ

Slide 4

Slide 4 text

Tests usage ± 50% COMPANY PROJECTS ± 90% OSS WELL-KNOWN PROJECTS ± 30% SCHOOL OR PERSONAL PROJECTS © Vincent AMSTOUTZ Simplified chart - Source: blog.jetbrains.com Source: gartener.com

Slide 5

Slide 5 text

© Vincent AMSTOUTZ Are tests useful? "Refactoring is something you do to keep your code clean, while testing is there to tell you if you've broken something." – Martin Fowler "Code without testing isn't clean. No matter how elegant it is, how readable and accessible it is, if it's not tested, it's not clean." – Robert C. Martin aka Uncle Bob

Slide 6

Slide 6 text

© Vincent AMSTOUTZ A game changer, really? NO TESTS (3 days after…) WITH TESTS © Vincent AMSTOUTZ

Slide 7

Slide 7 text

© Vincent AMSTOUTZ ↩ Migrations and technical evolutions 🆕 Functional evolutions Usage evolutions An indispensable asset for 📊

Slide 8

Slide 8 text

© Vincent AMSTOUTZ 🧪 Are they efficients? Why evaluate tests? 📊 Covers which parts?

Slide 9

Slide 9 text

Most used tests KPIs © Vincent AMSTOUTZ Are they executed in the CI?

Slide 10

Slide 10 text

And what if there is a more reliable way? © Vincent AMSTOUTZ

Slide 11

Slide 11 text

© Vincent AMSTOUTZ Mutation testing! 🎉 Tests Executed code Mutation testing check quality of… check quality of…

Slide 12

Slide 12 text

© Vincent AMSTOUTZ The mutant 🕷 "Mutation testing involves modifying a program in small ways. Each mutated version is called a Mutant.”. – Infection

Slide 13

Slide 13 text

© Vincent AMSTOUTZ Our 1st mutant 🕷 public function addition(float $a, float $b): float { - return $a + $b; + return $a - $b; } Operator inversion

Slide 14

Slide 14 text

© Vincent AMSTOUTZ Mutations The mutation testing 🕷 Code source Mutant Mutant Mutant Mutant Infection / Pest Tests Mutator list 1/ Analyze (via the AST) 2/ Generates modifications 3/ Run tests within mutations Result

Slide 15

Slide 15 text

© Vincent AMSTOUTZ Our 1st mutant🕷 public function addition(float $a, float $b): float { - return $a + $b; + return $a - $b; } Escaped mutant public function testAddition(): void { $result = $this->addition(2, 0); self::assertSame(2, $result); }

Slide 16

Slide 16 text

© Vincent AMSTOUTZ Other mutant 🕷 public function addition(float $a, float $b): float { - return $a + $b; + return $a / $b; } Detected mutant

Slide 17

Slide 17 text

© Vincent AMSTOUTZ Principal indicator Score or MSI Ratio of detected mutants on the code base 󰡸

Slide 18

Slide 18 text

And in practice? Photo by AltumCode on Unsplash © Vincent AMSTOUTZ

Slide 19

Slide 19 text

© Vincent AMSTOUTZ Requirements ⚠ A driver for coverage is required Pro tip: activate coverage in your dev & test environments

Slide 20

Slide 20 text

© Vincent AMSTOUTZ Infection https://github.com/infection/infection

Slide 21

Slide 21 text

© Vincent AMSTOUTZ ➔ PHPUnit ⬅ for this conference ➔ PhpSpec ➔ Codeception Choice of tests library Infection supports

Slide 22

Slide 22 text

© Vincent AMSTOUTZ Infection installation { "logs": { "text": "./infection/infection.log", "html": "./infection/infection.html", "summary": "./infection/summary.log", "perMutator": "./infection/per-mutator.md", }, "mutators": { "@default": true }, } © Vincent AMSTOUTZ composer require --working-dir=tools/infection infection/infection

Slide 23

Slide 23 text

© Vincent AMSTOUTZ Tests are required for Infection

Slide 24

Slide 24 text

© Vincent AMSTOUTZ final class UserAdultFilterAge { public function __invoke(array $users): array { return array_filter( $users, fn (User $user) => $user->age >= 18, ); } }

Slide 25

Slide 25 text

© Vincent AMSTOUTZ final class UserAdultFilterAgeTest extends TestCase { public function test_it_filters_adults(): void { $filter = new UserAdultFilterAge(); $users = [ new User(“Tom”, age: 20), new User(“Alicia”, age: 15), ]; self::assertCount(1, $filter($users)); } }

Slide 26

Slide 26 text

© Vincent AMSTOUTZ Code coverage ./bin/phpunit --coverage=text

Slide 27

Slide 27 text

© Vincent AMSTOUTZ Mutation testing with Infection infection

Slide 28

Slide 28 text

© Vincent AMSTOUTZ Mutation testing with Infection

Slide 29

Slide 29 text

© Vincent AMSTOUTZ Mutation testing with Infection

Slide 30

Slide 30 text

© Vincent AMSTOUTZ Corrected public function test_it_filters_adults(): void { $filter = new UserAdultFilterAge(); $users = [ new User(“Tom”, age: 20), new User(“Alicia”, age: 15), new User(“Matéo”, age: 18), ]; self::assertCount(2, $filter($users)); }

Slide 31

Slide 31 text

© Vincent AMSTOUTZ Code coverage ./bin/phpunit --coverage=text

Slide 32

Slide 32 text

© Vincent AMSTOUTZ Mutation testing with Infection infection

Slide 33

Slide 33 text

© Vincent AMSTOUTZ Pest https://github.com/pestphp/pest

Slide 34

Slide 34 text

© Vincent AMSTOUTZ composer require pestphp/pest --dev --with-all-dependencies Pest installation

Slide 35

Slide 35 text

© Vincent AMSTOUTZ Pest configuration Based on PHPUnit configuration ➔ Most options are available via the CLI ➔ Pest.php config file (few options supported)

Slide 36

Slide 36 text

© Vincent AMSTOUTZ Pest mutation no require tests Works with untested code pest --mutate --everything

Slide 37

Slide 37 text

© Vincent AMSTOUTZ Mutation testing with Pest mutates(Foo::class); pest --mutate --everything

Slide 38

Slide 38 text

© Vincent AMSTOUTZ {...} final readonly class IsEven { public function __invoke(int $number): bool { return $number % 2 === 0; } } {...} it('returns true for even numbers', function () { expect(new IsEven()(2))->toBeTrue(); });

Slide 39

Slide 39 text

© Vincent AMSTOUTZ Tests ./vendor/bin/pest --coverage

Slide 40

Slide 40 text

© Vincent AMSTOUTZ Mutation testing with Pest ./vendor/bin/pest --mutate --everything

Slide 41

Slide 41 text

© Vincent AMSTOUTZ {...} it('returns true for even numbers', function () { expect(new IsEven()(2))->toBeTrue(); }); it('returns false for odd numbers', function () { expect(new IsEven()(3))->toBeFalse(); }); Corrected

Slide 42

Slide 42 text

© Vincent AMSTOUTZ Mutation testing with Pest ./vendor/bin/pest --mutate --everything

Slide 43

Slide 43 text

Infection VS Pest Pros ➔ Independent and agnostic framework ➔ Mutators diversity ➔ Custom mutators Cons ➔ Version 0.* since 2017 Pros ➔ The output in the CLI by default ➔ Include additional testing tools Cons ➔ Less mutators than Infection ➔ No extension on mutators ➔ No symfony mutators VS © Vincent AMSTOUTZ

Slide 44

Slide 44 text

© Vincent AMSTOUTZ Integrating mutation testing into our projects

Slide 45

Slide 45 text

© Vincent AMSTOUTZ Integration in existing projects Project without tests Learn how to write tests Adding tests as you go along

Slide 46

Slide 46 text

© Vincent AMSTOUTZ Integration in existing projects 1/ Consolidate covered code ./vendor/bin/pest --mutate --covered-only --parallel infection --only-covered --threads=max Project with tests

Slide 47

Slide 47 text

© Vincent AMSTOUTZ Integration in existing projects 1/ Consolidate covered code 2/ Add it to your CI ./vendor/bin/pest --mutate --min=30 --parallel --covered-only infection --min-covered-msi=30 --threads=max Project with tests

Slide 48

Slide 48 text

© Vincent AMSTOUTZ Integration in existing projects 1/ Consolidate covered code 2/ Add it to your CI 3/ Consolidate covered code (again) Project with tests

Slide 49

Slide 49 text

© Vincent AMSTOUTZ Integration in existing projects 1/ Consolidate covered code 2/ Add it to your CI 3/ Consolidate covered code (again) 4/ Adjust CI requirements ./vendor/bin/pest --mutate --min=60 --parallel --covered-only infection --min-covered-msi=60 --threads=max Project with tests

Slide 50

Slide 50 text

© Vincent AMSTOUTZ Use in dev infection --filter=src/Entity/Foo.php ./vendor/bin/pest --mutate --class=App\Models\Bar.php

Slide 51

Slide 51 text

© Vincent AMSTOUTZ CI Performance ./vendor/bin/pest --mutate --parallel --bail infection --threads=max --git-diff-lines

Slide 52

Slide 52 text

© Vincent AMSTOUTZ More about testing 👩󰘃 ➔ https://fr.linkedin.com/in/valentinajemuovic 󰏅 ➔ https://www.martinfowler.com/ 󰏅 ➔ https://blog.cleancoder.com/ 󰏅 📚 ➔ Clean Code: A Handbook of Agile Software Craftsmanship By Robert C. Martin 󰏅

Slide 53

Slide 53 text

Thank you! ➔ Any questions ? @vincent-amstoutz @vinceAmstoutz