29
Fundamentals of unit testing in PHP using PHPUnit Nicolas A. Bérard-Nault 5 May 2011

Unit Testing Presentation

  • Upload
    nicobn

  • View
    696

  • Download
    1

Embed Size (px)

Citation preview

Page 1: Unit Testing Presentation

Fundamentals of unit testing in PHP using PHPUnit

Nicolas A. Bérard-Nault5 May 2011

Page 2: Unit Testing Presentation

Some of the goals of test automation

For the stakeholders:- Improving external quality by reducing defect density- Improving internal quality by increasing maintainability- Reduced short and long-term risk

For the programmers:- Facilitating defect localization- Use tests as documentation- Use tests as a specification

Page 3: Unit Testing Presentation

The automated testing continuum

« Black box »$$$SlowFunctionality

« White box »$FastCode

Functional tests Unit testsIntegration tests

QA is (usually) responsible for these tests

Programmers are definitely responsible for these tests

Page 4: Unit Testing Presentation

PHPUnit and the xUnit family

- Written by Sebastian Bergmann- Member of the xUnit family- Direct descendant of sUnit written by Kent Beck

and jUnit, written by Beck and Erich Gamma

Website: http://www.phpunit.deCode coverage is provided using the xDebug extension: http://www.xdebug.org

Page 5: Unit Testing Presentation

Your first PHPUnit test (1)

function rot13($text){ $len = strlen($text); for ($i = 0; $i < $len; $i++) { $text{$i} = chr((ord($text{$i}) - ord('a') + 13) % 26 + ord('a')); } return $text;}

System under test:

Initial naïve approach: Formulate expected behavior

Goal: test an implementation of ROT13

Page 6: Unit Testing Presentation

Your first PHPUnit test (2)

class Rot13Test extends PHPUnit_Framework_TestCase{ public function testWord() { $this->assertEquals('cheryl', rot13('purely')); }}

Class name has Test suffixBase class for tests

Test method has test prefix

Post-condition verification

Page 7: Unit Testing Presentation

Your first PHPUnit test (3)

nicobn@nicobn-laptop:~$ phpunit rot13.phpPHPUnit 3.5.5 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 3.50Mb

OK (1 test, 1 assertion)

The test passes but have we really covered all of our bases ?

Page 8: Unit Testing Presentation

Preconditions, invariants and postconditions

function rot13($text){ $len = strlen($text); for ($i = 0; $i < $len; $i++) { $text{$i} = chr((ord($text{$i}) - ord('a') + 13) % 26 + ord('a')); } return $text;}

Precondition: $text must be a string

Precondition: $text must be lower case

Invariant: non-alpha characters must remain unchanged

Postcondition: each alpha character must be moved 13 positions

We screwed up ! How many more tests do we need ?

Page 9: Unit Testing Presentation

NEWFLASH: It’s not a question of how awesomely awesome of a 

programmer you are, but a question of methodology !

Sad panda is sad

Page 10: Unit Testing Presentation

Test first ! (1)“Applying ROT13 to a piece of text merely requires examining its alphabetic characters and replacing  each  one  by  the  letter  13  places  further  along in the alphabet, wrapping back to the beginning if necessary. A becomes N, B becomes O, and so on up to M, which becomes Z, then the sequence continues at the beginning of the alphabet: N becomes A, O becomes B, and so on to Z, which becomes M. Only those letters which occur in the English alphabet are affected; numbers, symbols, whitespace, and all other characters are left unchanged. Because there are 26 letters in the English alphabet and 26 = 2 × 13, the ROT13 function is its own inverse.”(Wikipedia)

Input Output

A N

B O

M Z

N A

O B

Z M

$, 0, #, … $, 0, #, …

Page 11: Unit Testing Presentation

Test first ! (2a)function rot13($text){ }

class Rot13Test extends PHPUnit_Framework_TestCase{ public function test_A_Lower() { $this->assertEquals('n', rot13('a')); }}

There was 1 failure:

1) Rot13Test::test_A_LowerFailed asserting that two strings are equal.--- Expected+++ Actual@@ @@-n+

Page 12: Unit Testing Presentation

Test first ! (2b)function rot13($text){ return ‘n’;}

class Rot13Test extends PHPUnit_Framework_TestCase{ public function test_A_Lower() { $this->assertEquals('n', rot13('a')); }}

PHPUnit 3.5.5 by Sebastian Bergmann.

.

Time: 1 second, Memory: 3.50Mb

OK (1 test, 1 assertion)

Page 13: Unit Testing Presentation

Test first ! (2c)function rot13($text){ return chr(ord($text{0}) + 13);}

class Rot13Test extends PHPUnit_Framework_TestCase{ public function test_A_Lower() { $this->assertEquals('n', rot13('a')); }}

PHPUnit 3.5.5 by Sebastian Bergmann.

.

Time: 1 second, Memory: 3.50Mb

OK (1 test, 1 assertion)

Page 14: Unit Testing Presentation

Test first ! (3a)function rot13($text){ return chr(ord($text{0}) + 13);}

class Rot13Test extends PHPUnit_Framework_TestCase{ public function test_A_Lower() { $this->assertEquals('n', rot13('a')); } public function test_N_Lower() { $this->assertEquals(‘a', rot13(‘n')); }}

FAIL !

Page 15: Unit Testing Presentation

Test first ! (3b)function rot13($text){ return chr((ord($text{0}) - ord('a') + 13) % 26 + ord('a'));}

class Rot13Test extends PHPUnit_Framework_TestCase{ public function test_A_Lower() { $this->assertEquals('n', rot13('a')); } public function test_N_Lower() { $this->assertEquals(‘a', rot13(‘n')); }}

PASS !

Page 16: Unit Testing Presentation

Test first ! (4a)function rot13($text){ return chr((ord($text{0}) - ord('a') + 13) % 26 + ord('a'));}

class Rot13Test extends PHPUnit_Framework_TestCase{ public function test_A_Lower() { $this->assertEquals('n', rot13('a')); } public function test_N_Lower() { $this->assertEquals(‘a', rot13(‘n')); } public function test_Symbol() { $this->assertEquals('$', rot13('$')); }}

FAIL !

Page 17: Unit Testing Presentation

Test first ! (4b)function rot13($text){ if (!ctype_alnum($text{0})) { return $text{0}; } return chr((ord($text{0}) - ord('a') + 13) % 26 + ord('a'));}

class Rot13Test extends PHPUnit_Framework_TestCase{ public function test_A_Lower() { $this->assertEquals('n', rot13('a')); } public function test_N_Lower() { $this->assertEquals(‘a', rot13(‘n')); } public function test_Symbol() { $this->assertEquals('$', rot13('$')); }}

PASS !

Page 18: Unit Testing Presentation

Test first ! (5a)function rot13($text){ if (!ctype_alnum($text{0})) { return $text{0}; } return chr((ord($text{0}) - ord('a') + 13) % 26 + ord('a'));}

class Rot13Test extends PHPUnit_Framework_TestCase{ /* […] */ public function test_N_Upper() { $this->assertEquals('A', rot13('N')); }}

FAIL !

Page 19: Unit Testing Presentation

Test first ! (5b)function rot13($text){ if (!ctype_alnum($text{0})) { return $text{0}; } if (ctype_upper($text{0})) { $delta = ord('A'); } else { $delta = ord('a'); } return chr((ord($text{0}) - $delta + 13) % 26 + $delta);}

class Rot13Test extends PHPUnit_Framework_TestCase{ /* […] */ public function test_N_Upper() { $this->assertEquals('A', rot13('N')); }}

PASS !

Page 20: Unit Testing Presentation

Test first ! (6a)function rot13($text){ if (!ctype_alnum($text{0})) { return $text{0}; } if (ctype_upper($text{0})) { $delta = ord('A'); } else { $delta = ord('a'); } return chr((ord($text{0}) - $delta + 13) % 26 + $delta);}

class Rot13Test extends PHPUnit_Framework_TestCase{ /* […] */ public function test_N_Purely() { $this->assertEquals(‘$purely$', rot13(‘$cheryl$')); }}

FAIL !

Page 21: Unit Testing Presentation

Test first ! (6b)function rot13($text){ $str = ''; $len = strlen($text); for ($i = 0; $i < $len; $i++) { if (!ctype_alnum($text{$i})) { $str .= $text{$i}; } else { if (ctype_upper($text{$i})) { $delta = ord('A'); } else { $delta = ord('a'); } $str .= chr((ord($text{$i}) - $delta + 13) % 26 + $delta); } } return $str;}

PASS !

Page 22: Unit Testing Presentation

REDGREENREFACTOR

Test-driven development

Page 23: Unit Testing Presentation

PHPUnit assertions

Function Assertion

assertEquals($a, $b) $a == $b

assertSame($a, $b) $a === $b

assertContains($a, $b) $a in $b (element in array, sub-string in string)

assertCount($a, $b) $a == count($b)

assertInstanceOf($a, $b) $b instanceof $a

assertEmpty($a) empty($a)

assertTrue($a) $a

Note: most assertions have a assertNotXXXX() counterpart.

Page 24: Unit Testing Presentation

Test statusStatus Code

Success .

Failure F

Error E

Skipped S

Incomplete I

Do yourself and your colleagues a favor and mark tests as skipped or incomplete when relevant !

Status Trigger

Incomplete markTestIncomplete($reason)

Skipped markTestSkipped($reason)

Page 25: Unit Testing Presentation

Anatomy of aunit test

SETUP: Create the fixture

EXERCISE: Execute the system under test (SUT)

VERIFY: Check expectations (state mutations, output, invariants, etc.)

TEAR DOWN: Clean the fixture

Page 26: Unit Testing Presentation

What is a good tests

- Deterministic- FULLY automated- Self-checking- Simple, robust- Expressive- Should not harm the development process in the

long run

Tests, if used incorrectly, can be detrimental to a project

Page 27: Unit Testing Presentation

Principles of test automation

1) INDEPENDANCE: A test should not interact with another test (shared mutable state).

2) NO TEST CODE IN PRODUCTION: Ban if ($test). Don’t even think about it.

3) ISOLATION: Each SUT must be independant. State mutations during exercise should only occur in and by the SUT.

4) D.R.Y.: Do not repeat yourself. Valid for fixture setup as well as test overlap.

5) CLEAR INTENTIONS: A test should be simple and clearly communicate its scope.

6) MINIMIZE UNTESTABLE CODE: Sorry, please replace minimize by eliminate.

7) TEST FIRST: Prevents most smells, ensures high coverage, provides immediate feedback.

8) DO NOT TEST YOUR PRIVATES: The need to test a private method is a symptom of a deeper problem.

Page 28: Unit Testing Presentation

Symfony2’s routing component:https://github.com/symfony/symfony/blob/master/tests/Symfony/Tests/Component/Routing/Matcher/UrlMatcherTest.php

Example of a feeble test (object creation code repeated), long test (testMatch).

Symfony2’s DOM Crawler:https://github.com/symfony/symfony/blob/master/tests/Symfony/Tests/Component/DomCrawler/CrawlerTest.php

Example of utility methods (at the bottom), @covers, SUT factory method (createTestCrawler).

Symfony2’s HttpKernel component:https://github.com/symfony/symfony/blob/master/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php

Example of test doubles.

Exempli gratia

Page 29: Unit Testing Presentation

Resources

The seminal book on unit testing. Written by Gerard Meszaros.

ISBN-13: 978-0131495050