Slide 1

Slide 1 text

@pelshoff Welcome Testing absolute units

Slide 2

Slide 2 text

@pelshoff @pelshoff Photo by Moises Alex on Unsplash

Slide 3

Slide 3 text

@pelshoff @pelshoff Photo by Stephen Andrews on Unsplash

Slide 4

Slide 4 text

@pelshoff @pelshoff Photo by Stephen Andrews on Unsplash Uncertainty Distrust Opaqueness High heart rate

Slide 5

Slide 5 text

@pelshoff @pelshoff Consistency Confidence Transparency Low heart rate Photo by Stephen Andrews on Unsplash

Slide 6

Slide 6 text

@pelshoff Knowing Is the key

Slide 7

Slide 7 text

@pelshoff @pelshoff Photo by Daniel Mingook Kim on Unsplash

Slide 8

Slide 8 text

@pelshoff @pelshoff Learning “In software development learning is not a big part of the job. It is the job.” – Woody Zuill https://twitter.com/WoodyZuill/status/1502366061890473985 Photo by Daniel Mingook Kim on Unsplash

Slide 9

Slide 9 text

@pelshoff @pelshoff Confidence “Testing is all about confidence. When I'm working, I need to be confident that I haven't broken anything, and that the code that I just wrote works as expected. When I'm working, that's ALL I need to test for.” – Allen Holub https://twitter.com/allenholub/status/1538561251868954624 Photo by Daniel Mingook Kim on Unsplash

Slide 10

Slide 10 text

@pelshoff @pelshoff Consider... What is there to do? What is left to do? How should the system behave? How should the component behave? How should the component internals work? Photo by Daniel Mingook Kim on Unsplash

Slide 11

Slide 11 text

@pelshoff @pelshoff Create... Features Feature Scenario Decomposition Implementation Photo by Daniel Mingook Kim on Unsplash

Slide 12

Slide 12 text

@pelshoff @pelshoff Transform … to natural language spec … to executable spec … to integration test … to unit test … to code Photo by Daniel Mingook Kim on Unsplash

Slide 13

Slide 13 text

@pelshoff @pelshoff Purity Photo by Daniel Mingook Kim on Unsplash / Joke by XKCD on https://xkcd.com/435/

Slide 14

Slide 14 text

@pelshoff @pelshoff Natural language spec User stories, story map, example map… Photo by Daniel Mingook Kim on Unsplash

Slide 15

Slide 15 text

@pelshoff Executable spec Agree with three “it works, for me”

Slide 16

Slide 16 text

@pelshoff @pelshoff /tests/features/Policy/Adjustment/nonSchemeMta.feature Feature: non-scheme mid-term adjustments In order to match my insurance policy to my evolved situation As an insured party I want to adjust my policy mid-term Scenario: Annualised (full term) mta Given a non-scheme policy And the start date is "2019-01-01" And the premium is 365 GBP And the policy is bound And the policy is opened When the effective date is "2019-07-01" And the mta is 375 GBP annualised Then delta gross premium should be 5.04 GBP Scenario: Pro rated mta ... Photo by Osman Rana on Unsplash

Slide 17

Slide 17 text

@pelshoff Insurance We sell it

Slide 18

Slide 18 text

@pelshoff @pelshoff What is a non-scheme mid term adjustment? Photo by Osman Rana on Unsplash ● Scheme: We sell an insurance product on behalf of an insurance company (underwriter) ● Non-scheme: We sell an insurance product the insurance company sells (broker) ● Policy: A contract between a customer and us that said customer is covered for certain perils pertaining exposures for certain periods of time ● Term: One of a certain period of time, usually either a month or a year ● Mid term adjustment: an adjustment while a term is ongoing ● Effective date: The date this “version” if the policy goes live ● Premium: The fee a customer pays... ● Term premium: for all transactions combined ● Current premium: for the latest transaction of a would be term ● Delta premium: for the latest transaction..?

Slide 19

Slide 19 text

@pelshoff @pelshoff What is a non-scheme mid term adjustment? Photo by Osman Rana on Unsplash ● Non-scheme: We don’t decide the premium ● Mid term adjustment: for this adjustment to the contract ● Premium: and insurance companies might give us current ór delta ● Current premium: where current is the price for a full term (and for the next term upon renewal) ● Delta premium: and delta is ehm

Slide 20

Slide 20 text

@pelshoff @pelshoff What is a delta premium? Photo by Osman Rana on Unsplash ● You pay 365GBP for a one year policy ● You change to a 375GBP premium half-way through ● The delta is NOT 10GBP ● You’ve paid 365 / ~2 too much ● You’ve paid 375 / ~2 too little ● 375 / ~2 - 365 / ~2 = ? ● ~187.5 - ~182.5 = 5.04

Slide 21

Slide 21 text

@pelshoff @pelshoff /tests/features/Policy/Adjustment/nonSchemeMta.feature Feature: non-scheme mid-term adjustments In order to match my insurance policy to my evolved situation As an insured party I want to adjust my policy mid-term Scenario: Annualised (full term) mta Given a non-scheme policy And the start date is "2019-01-01" And the premium is 365 GBP And the policy is bound And the policy is opened When the effective date is "2019-07-01" And the mta is 375 GBP annualised Then delta gross premium should be 5.04 GBP Photo by Osman Rana on Unsplash

Slide 22

Slide 22 text

@pelshoff @pelshoff .../nonSchemeMta.feature | .../NonSchemeMtaContext.php And the start date is "2019-01-01" Photo by Osman Rana on Unsplash /** * @Given /^the start date is "2019\-01\-01"$/ */ public function theStartDateIs ($arg1) { throw new PendingException() ; } /** * @Given the start date is :aDate */ public function theStartDateIs ($aDate) { throw new PendingException() ; }

Slide 23

Slide 23 text

@pelshoff @pelshoff behat.yml default: suites: Policy: contexts: - TestingAbsoluteUnits\Tests\Features\Policy\Adjustment\NonSchemeMtaContext paths: - ./tests/Features/Policy Photo by Osman Rana on Unsplash

Slide 24

Slide 24 text

@pelshoff @pelshoff .../nonSchemeMta.feature | .../NonSchemeMtaContext.php And the start date is "2019-01-01" Photo by Osman Rana on Unsplash /** * @Given the start date is :aDate */ public function theStartDateIs ($aDate) { $this->policy->updateStartDate ( LocalDate:: parse( trim( $aDate, '"') ) ); }

Slide 25

Slide 25 text

@pelshoff @pelshoff .../nonSchemeMta.feature | .../NonSchemeMtaContext.php Then delta gross premium should be 5.04 GBP Photo by Osman Rana on Unsplash ** * @Then delta gross premium should be :amount :currency */ public function deltaGrossPremiumShouldBe ( $amount, $currency ){ $this->policy->assertRecorded ( new PremiumTransacted( Money:: GBP(5_04) ) ); }

Slide 26

Slide 26 text

@pelshoff @pelshoff Terminal $ vendor/bin/behat Feature: non-scheme mid-term adjustments In order to match my insurance policy to my evolved situation As an insured party I want to adjust my policy mid-term Scenario: Annualised (full term) mta Given a non-scheme policy TODO: write pending definition And the start date is "2019-01-01" And the premium is 365 GBP And the policy is bound And the policy is opened When the effective date is "2019-07-01" And the mta is 375 GBP annualised Then delta gross premium should be 5.04 GBP 1 scenario (1 pending) 8 steps (1 pending, 7 skipped) Photo by Osman Rana on Unsplash

Slide 27

Slide 27 text

@pelshoff @pelshoff Terminal $ vendor/bin/behat 1 scenario (1 failed) 8 steps (7 passed, 1 failed) Photo by Osman Rana on Unsplash

Slide 28

Slide 28 text

@pelshoff @pelshoff Terminal $ vendor/bin/behat 1 scenario (1 passed) 8 steps (8 passed) Photo by Osman Rana on Unsplash

Slide 29

Slide 29 text

@pelshoff I’m done! and I can prove it Photo by Osman Rana on Unsplash

Slide 30

Slide 30 text

@pelshoff @pelshoff /tests/Features/Policy/Adjustment/NonSchemeMtaTest.php Photo by Osman Rana on Unsplash /** * In order to match my insurance policy to my evolved situation * As an insured party * I want to adjust my policy mid-term */ public function it_calculates_non_scheme_mta_annualised (): void { Policy::fake() ->givenNonSchemePolicy () ->givenStartDateIs (2019, 1, 1) ->givenPremiumIs (365, 'GBP') ->givenPolicyIsBound () ->givenPolicyIsOpened () ->whenEffectiveDateIs (2019, 7, 1) ->whenMtaIs(375, 'GBP', 'annualised') ->thenDeltaGrossPremiumShouldBe (5.04, 'GBP'); }

Slide 31

Slide 31 text

@pelshoff @pelshoff Heuristics Don’t put too many (unimportant) details in feature tests Keep the tests high-level Photo by Osman Rana on Unsplash

Slide 32

Slide 32 text

@pelshoff Design Driven mad by tests

Slide 33

Slide 33 text

@pelshoff @pelshoff Top 5 challenges with tests 5. Scope mix-up 4. Mocks 3. Too much, inconsistent or non-deterministic setup 2. No tests 1. Other devs Photo by Matt Cramblett on Unsplash

Slide 34

Slide 34 text

@pelshoff @pelshoff Top 5 challenges with tests 5. Scope mix-up 4. Mocks 3. Too much, inconsistent or non-deterministic setup 2. No tests 1. Other devs Design of the “Unit” Photo by Matt Cramblett on Unsplash

Slide 35

Slide 35 text

@pelshoff Unit of decomposition Photo by Matt Cramblett on Unsplash

Slide 36

Slide 36 text

@pelshoff @pelshoff class PremiumCalculation { public function act(PremiumCalculationAction $action): static { $action->assertActionCanBeApplied ($this->transactions, $this->period); return $this->withTransaction ($action->transact($this->transactions, $this->period)); } public function correct(PremiumCalculationCorrection $correction): static { $this->transactions->assertThereAreTransactions (); $correction->assertCorrectionCanBeApplied ($this->transactions, $this->period); $correctedCalculation = $this->withTransaction ( LastTransactionPremiumReversal:: for($this->transactions) ); return $correctedCalculation ->withTransaction ( CorrectionTransaction:: fromTransaction( $correction->transact($correctedCalculation ->transactions, $this->period) ) ); } } src/Premium/PremiumCalculation.php Photo by Matt Cramblett on Unsplash

Slide 37

Slide 37 text

@pelshoff @pelshoff /** @test */ public function can_adjust() { $bindPremium = new SinglePremium(Money:: GBP(365_00), ...); $adjustPremium = new SinglePremium(Money:: GBP(375_00), ...); $deltaPremium = new SinglePremium(Money:: GBP(5_04), ...); $expected = Transactions:: of([ new PremiumTransaction(LocalDate:: of(2019, 1, 1), $bindPremium, $bindPremium), new PremiumTransaction(LocalDate:: of(2019, 7, 1), $adjustPremium , $deltaPremium), ]); $actual = Transactions:: of( PremiumCalculation:: for(TermPeriod::of(...)) ->act(new CurrentPremiumBind( $bindPremium)) ->act(new CurrentPremiumAdjustment(LocalDate:: of(2019, 7, 1), $adjustPremium )) ->newTransactions () ); $this->assertEquals($expected, $actual); } tests/Premium/PremiumCalculationTest.php Photo by Matt Cramblett on Unsplash

Slide 38

Slide 38 text

@pelshoff What’s special about this? Photo by Matt Cramblett on Unsplash

Slide 39

Slide 39 text

@pelshoff @pelshoff class PremiumCalculationTest extends TestCase { public function is_immutable(){} public function wont_bind_when_there_are_already_transactions (){} public function can_bind(){} public function needs_existing_transactions_before_adjusting (){} public function can_adjust(){} public function can_bind_then_adjust_multiple_times (){} public function can_correct_a_bind (){} public function wont_correct_with_a_new_bind_if_an_adjustment_happened (){} public function can_correct_an_adjustment (){} } tests/Premium/... Photo by Matt Cramblett on Unsplash

Slide 40

Slide 40 text

@pelshoff @pelshoff class PremiumCalculationTest extends TestCase { public function is_immutable(){} public function can_bind_then_adjust_multiple_times (){} public function can_correct_a_bind (){} public function wont_correct_with_a_new_bind_if_an_adjustment_happened (){} public function can_correct_an_adjustment (){} } class CurrentPremiumAdjustmentTest extends TestCase { public function needs_existing_transactions (){} public function can_only_append_transactions (){} public function must_be_within_term_bounds (){} public function can_adjust_to_a_higher_current_premium (){} public function can_adjust_to_a_lower_current_premium (){} public function can_adjust_on_the_first_day (){} public function can_adjust_on_the_final_day (){} } class TransactionsTest extends TestCase { public function can_determine_term_premium (){} } tests/Premium/... Photo by Matt Cramblett on Unsplash

Slide 41

Slide 41 text

@pelshoff @pelshoff $ vendor/bin/phpunit --testdox Premium Calculation (Tests\...\PremiumCalculation) ✔ Is immutable ✔ Can bind then adjust multiple times ✔ Can correct a bind ✔ Wont correct with a new bind if an adjustment happened ✔ Can correct an adjustment Current Premium Adjustment (Tests\...\CurrentPremiumAdjustment) ✔ Needs existing transactions ✔ Can only append transactions ✔ Must be within term bounds ✔ Can adjust to a higher current premium ✔ Can adjust to a lower current premium ✔ Can adjust on the first day ✔ Can adjust on the final day Transactions (Tests\...\Transactions) ✔ Can determine term premium Terminal Photo by Matt Cramblett on Unsplash

Slide 42

Slide 42 text

@pelshoff @pelshoff 1. Fix the feature test 2. Fix the integration test 3. Fix the unit test 4. Fix the integration test 5. Fix the feature test 1. Make it work 2. Make it beautiful 3. (Make it fast) Executable to-do list Photo by Matt Cramblett on Unsplash

Slide 43

Slide 43 text

@pelshoff @pelshoff /** @test */ public function can_adjust() { $bindPremium = new SinglePremium(Money:: GBP(365_00), ...); $adjustPremium = new SinglePremium(Money:: GBP(375_00), ...); $deltaPremium = new SinglePremium(Money:: GBP(5_04), ...); $expected = Transactions:: of([ new PremiumTransaction(LocalDate:: of(2019, 1, 1), $bindPremium, $bindPremium), new PremiumTransaction(LocalDate:: of(2019, 7, 1), $adjustPremium , $deltaPremium), ]); $actual = Transactions:: of( PremiumCalculation:: for(TermPeriod::of(...)) ->act(new CurrentPremiumBind( $bindPremium)) ->act(new CurrentPremiumAdjustment(LocalDate:: of(2019, 7, 1), $adjustPremium )) ->newTransactions () ); $this->assertEquals($expected, $actual); } tests/Premium/PremiumCalculationTest.php Photo by Matt Cramblett on Unsplash

Slide 44

Slide 44 text

@pelshoff @pelshoff Executable to-do list Error : Class "TestingAbsoluteUnits\Tests\Unit\PremiumCalculation" not found Error : Call to undefined method TestingAbsoluteUnits\Premium\PremiumCalculation::forTerm() Error : Call to undefined method TestingAbsoluteUnits\Premium\PremiumCalculation::act(), etc. TypeError : TestingAbsoluteUnits\Premium\PremiumCalculation::act(): Argument #1 ($param) must be of type TestingAbsoluteUnits\Tests\Unit\CurrentPremiumAdjustment, TestingAbsoluteUnits\Premium\Adjustment\CurrentPremiumAdjustment given PremiumCalculation::newTransactions(): Return value must be of type array, null returned Failed asserting that two objects are equal. Photo by Matt Cramblett on Unsplash

Slide 45

Slide 45 text

@pelshoff @pelshoff /** @test */ public function can_adjust() { $bindPremium = new SinglePremium(Money:: GBP(365_00), ...); $adjustPremium = new SinglePremium(Money:: GBP(375_00), ...); $deltaPremium = new SinglePremium(Money:: GBP(5_04), ...); $expected = Transactions:: of([ new PremiumTransaction(LocalDate:: of(2019, 1, 1), $bindPremium, $bindPremium), new PremiumTransaction(LocalDate:: of(2019, 7, 1), $adjustPremium , $deltaPremium), ]); $actual = Transactions:: of( PremiumCalculation:: for(TermPeriod::of(...)) ->act(new CurrentPremiumBind( $bindPremium)) ->act(new CurrentPremiumAdjustment(LocalDate:: of(2019, 7, 1), $adjustPremium )) ->newTransactions () ); $this->assertEquals($expected, $actual); } tests/Premium/PremiumCalculationTest.php Photo by Matt Cramblett on Unsplash

Slide 46

Slide 46 text

@pelshoff @pelshoff Executable to-do list [] vs. [0 => PremiumTransaction Object (), 1 => PremiumTransaction Object ()] [...] vs. [..., 1 => PremiumTransaction Object ()] Error : Call to undefined method TestingAbsoluteUnits\Premium\Adjustment\CurrentPremiumAdjustment::transact() [..., ...37500...] vs. [..., ...504...] The form of the results is now the same. What’s next? Photo by Matt Cramblett on Unsplash

Slide 47

Slide 47 text

@pelshoff What’s hard about this? Photo by Matt Cramblett on Unsplash

Slide 48

Slide 48 text

@pelshoff @pelshoff /** @test */ public function can_adjust_to_a_higher_current_premium () { $bindPremium = new SinglePremium(Money:: GBP(365_00), ...); $adjustPremium = new SinglePremium(Money:: GBP(375_00), ...); $deltaPremium = new SinglePremium(Money:: GBP(5_04), ...); $bindDate = LocalDate::of(2019, 1, 1); $adjustDate = LocalDate::of(2019, 7, 1); $adjustment = new CurrentPremiumAdjustment( $adjustDate, $adjustPremium ); $expected = new PremiumTransaction( $adjustDate, $adjustPremium , $deltaPremium); $actual = $adjustment->transact( Transactions:: fromBind($bindDate, $bindPremium), TermPeriod::fullYearOf($adjustDate) ); $this->assertEquals($expected, $actual); } tests/Premium/Calculation/Adjustment/CurrentPremiumAdjustmentTest.php Photo by Matt Cramblett on Unsplash

Slide 49

Slide 49 text

@pelshoff @pelshoff public function transact(Transactions $transactions, TermPeriod $period): Transaction { // Given the number of days we have left // Take what we've paid so far // Subtract what we've paid too much for the old policy // Add what we pay for the new policy $effectiveNumberOfDaysForNewCurrentPremium = $this->effectiveDate-> daysUntil ($period->exclusiveEndDate ()); $deltaPremium = $transactions->termPremium() ->multiply($period->fullTermDays()) ->subtract( $transactions ->currentPremium () -> multiply($effectiveNumberOfDaysForNewCurrentPremium ) ) ->add($this->premium->multiply($effectiveNumberOfDaysForNewCurrentPremium )) ->divide($period->fullTermDays()) ->subtract($transactions->termPremium()); return new PremiumTransaction( $this->effectiveDate, $this->premium, $deltaPremium); } src/Premium/Calculation/Adjustment/CurrentPremiumAdjustment.php Photo by Matt Cramblett on Unsplash

Slide 50

Slide 50 text

@pelshoff @pelshoff Heuristics Photo by Matt Cramblett on Unsplash A unit is a component you can test in isolation; Write tests with isolation and no mocks to create units Fight the urge to automate and/or abstract test set-up; if it’s hard make it smaller Large units are hard to TDD; if it’s hard make it smaller Keep your suite fast and Run The Tests Use test failures as a to-do list Highlight what’s special Highlight what’s hard

Slide 51

Slide 51 text

@pelshoff Test quality Is your code writing checks that your tests can’t cache?

Slide 52

Slide 52 text

@pelshoff @pelshoff sum.php | sumTest.php function sum(int $x, int $y) { return $x + $y; } $this->assertEquals(4, sum(2, 2)); Photo by Maksym Kaharlytskyi on Unsplash

Slide 53

Slide 53 text

@pelshoff @pelshoff divide.php | divideTest.php function divide(int $x, int $y): float { return $x / $y; } $this->assertEquals(2, divide(4, 2)); Photo by Maksym Kaharlytskyi on Unsplash

Slide 54

Slide 54 text

@pelshoff @pelshoff divide.php | divideTest.php function divide(int $x, int $y): float { if ($y === 0) { throw new DivisorFail(); } return $x / $y; } $this->expectException ( DivisorFail:: class ); divide(1, 0); Photo by Maksym Kaharlytskyi on Unsplash

Slide 55

Slide 55 text

@pelshoff @pelshoff Transactions.php | TransactionsTest.php public function sum(): int { if (count($this->transactions) <= 0) { return 0; } return array_reduce( $this->transactions, fn(int $sum, int $transaction) => $sum + $transaction, 0 ); } public function it_sums_to_zero (): void { $transactions = new Transactions([]) ; $this->assertEquals( 0, $transactions ->sum() ); } public function it_sums_good(): void { $transactions = new Transactions( [3,4,5] ); $this->assertEquals( 12, $transactions ->sum() ); } Photo by Maksym Kaharlytskyi on Unsplash

Slide 56

Slide 56 text

@pelshoff @pelshoff Terminal $ XDEBUG_MODE=coverage vendor/bin/infection .: killed, M: escaped, U: uncovered, E: fatal error, X: syntax error, T: timed out, S: skipped, I: ignored M........ (9 / 9) 9 mutations were generated: 8 mutants were killed 0 mutants were configured to be ignored 0 mutants were not covered by tests 1 covered mutants were not detected 0 errors were encountered 0 syntax errors were encountered 0 time outs were encountered 0 mutants required more time than configured Metrics: Mutation Score Indicator (MSI): 88% Mutation Code Coverage: 100% Covered Code MSI: 88% Please note that some mutants will inevitably be harmless (i.e. false positives). Photo by Maksym Kaharlytskyi on Unsplash

Slide 57

Slide 57 text

@pelshoff @pelshoff infection.log Escaped mutants: ================ 1) Transactions.php:17 [M] LessThanOrEqualTo --- Original +++ New @@ @@ } public function sum() : int { - if (count($this->transactions) <= 0) { + if (count($this->transactions) < 0) { return 0; } return array_reduce($this->transactions, fn(int $sum, int $transaction) => $sum + $transaction, 0); } } Photo by Maksym Kaharlytskyi on Unsplash

Slide 58

Slide 58 text

@pelshoff @pelshoff Transactions.php | TransactionsTest.php public function sum(): int { if (count($this->transactions) <= 0) { return 0; } return array_reduce( $this->transactions, fn(int $sum, int $transaction) => $sum + $transaction, 0 ); } public function it_sums_to_zero (): void { $transactions = new Transactions([]) ; $this->assertEquals( 0, $transactions ->sum() ); } public function it_sums_good(): void { $transactions = new Transactions( [3,4,5] ); $this->assertEquals( 12, $transactions ->sum() ); } Photo by Maksym Kaharlytskyi on Unsplash

Slide 59

Slide 59 text

@pelshoff @pelshoff infection.json { "$schema": "vendor/infection/infection/resources/schema.json" , "testFrameworkOptions": "--testsuite=Infection", "source": { "directories": [ "app" ] }, "mutators": { "@default": true, "@cast": false }, "logs": { "text": "infection.log" } } $ XDEBUG_MODE=coverage vendor/bin/infection --only-covered Photo by Maksym Kaharlytskyi on Unsplash

Slide 60

Slide 60 text

@pelshoff Production code Tests Mutations

Slide 61

Slide 61 text

@pelshoff @pelshoff Infection Mutation testing lib for PHP Analyses source tree and runs your suite for each applied mutation Prefer regular PHPUnit TestCase for performance reasons Run on entire suite or on changed files only © Markus Schirp (https://twitter.com/_m_b_j_) Photo by Maksym Kaharlytskyi on Unsplash

Slide 62

Slide 62 text

@pelshoff @pelshoff Infection On my M1 Macbook 86 test cases Infection running with --only-covered for 128 mutants \PHPUnit\TestCase: 0.057s Infection: 14s \Illuminate\Foundation\Testing\TestCase: 4.712s Infection: 48s Photo by Maksym Kaharlytskyi on Unsplash

Slide 63

Slide 63 text

@pelshoff @pelshoff divide.php | divideTest.php function divide(int $x, int $y): float { return $x / $y; } $this->assertEquals(2, divide(4, 2)); Photo by Maksym Kaharlytskyi on Unsplash

Slide 64

Slide 64 text

@pelshoff @pelshoff divide.php | divideTest.php function divide(int $x, int $y): float { return $x / $y; } use Eris\Generators ; use Eris\TestTrait ; $this->forAll( Generators:: int(), Generators:: int() ) ->then(fn(int $x, int $y) => $this->assertIsNumeric ( divide( $x, $y)) ); OutOfBoundsException : Evaluation ratio 0.01 is under the threshold 0.5 Caused by DivisionByZeroError: Division by zero Reproduce with: ERIS_SEED=1646663840606414 vendor/bin/phpunit --filter '...' Photo by Maksym Kaharlytskyi on Unsplash

Slide 65

Slide 65 text

@pelshoff @pelshoff Eris Property testing lib for PHP “Brute forces” towards edge cases Prefer regular PHPUnit TestCase for performance reasons Photo by Maksym Kaharlytskyi on Unsplash

Slide 66

Slide 66 text

@pelshoff @pelshoff Photo by Stephen Andrews on Unsplash

Slide 67

Slide 67 text

@pelshoff |u|

Slide 68

Slide 68 text

@pelshoff

Slide 69

Slide 69 text

@pelshoff Pim Elshoff twitter.com/pelshoff [email protected] speakerdeck.com/pelshoff Testing Absolute Units