phpunit best practices

34
PHPUNIT BEST PRACTICES Volker Dusch / @_ _edorian

Upload: edorian

Post on 25-Dec-2014

4.437 views

Category:

Technology


0 download

DESCRIPTION

My 25 minutes "PHPUnit - Best practices" talk from the 2013 fosdem.

TRANSCRIPT

Page 1: PhpUnit Best Practices

PHPUNIT BEST PRACTICES

Volker Dusch / @_ _edorian

Page 2: PhpUnit Best Practices

ABOUT MESoftware EngineerPHP since 11 yearsCICleanCodeDevOpsTDDShippingBullet points

Page 3: PhpUnit Best Practices

INSTEAD OF ME

Page 4: PhpUnit Best Practices

WORKING FOR

ResearchGate gives science back to the people who make it happen.

We help researchers build reputation and accelerate scientificprogress.

On their terms.

Page 5: PhpUnit Best Practices

GET IN TOUCHstackoverflow:

Twitter: @__edoriang+: Volker DuschIRC: edorianMail: [email protected]

Page 6: PhpUnit Best Practices

AGENDASome practices I valueYour mileage may varyBy no means complete

Page 7: PhpUnit Best Practices

WRITE TESTSIt's sounds obvious but getting started sometimes is the hardest part!

Page 8: PhpUnit Best Practices

THE FASTEST THING YOU CAN DO

Staging serverTesting your buildsAll without even touching PHPUnit

hits=̀curl -s staging.project.com | grep 'Login:' | wc -l̀;test $hits -eq 1 || echo "Frontpage error!"

data="login=test&passwort=secure&csrf="$csrfTokenhits=̀curl -X POST -d staging.project.com | grep 'Hello, testuser' | wc -l̀;test $hits -eq 1 || echo "Login error!"

Page 9: PhpUnit Best Practices

LET'S GO

Page 10: PhpUnit Best Practices

UPGRADE TO PHPUNIT 3.7EASE INSTALLTION

Page 11: PhpUnit Best Practices

PHAR

or

wget http://pear.phpunit.de/get/phpunit.pharchmod +x phpunit.phar./phpunit.phar --version

wget http://pear.phpunit.de/get/phpunit.pharchmod +x phpunit.pharmv phpunit.phar /usr/local/bin/phpunitphpunit --version

Page 12: PhpUnit Best Practices

COMPOSERThe Dependency Manager for PHP

With the best from zypper, bundler, pip, gem and npm

Page 13: PhpUnit Best Practices

PHPUNIT PER PROJECTcomposer.json{ "require-dev": { "phpunit/phpunit": "3.7.*" }}

composer install./vendor/bin/phpunit --version

Page 14: PhpUnit Best Practices

PHPUNIT GLOBAL INSTALL{ "require": { "phpunit/phpunit": "3.7.*" }, "config": { "bin-dir": "/usr/local/bin/" }}

sudo php composer installlphpunuit --version

Page 15: PhpUnit Best Practices

PEARpear config-set auto_discover 1pear install pear.phpunit.de/PHPUnitphpunit --version

Page 16: PhpUnit Best Practices

USE SPECIFIC ASSERTIONSPHPUnit ships with over 90 assertions.

http://www.phpunit.de/manual/current/en/appendixes.assertions.html

Use them to get pretty and helpful error messages.

Page 17: PhpUnit Best Practices

assertTrue vs assertInstanceOf$foo = new StdClass();$this->assertTrue($foo instanceOf Countable);

“Failed asserting that false is true.”

$foo = new StdClass();$this->assertInstanceOf('Countable', $foo);

“Failed asserting thatstdClass() is an instance of interface 'Countable'.”

Page 18: PhpUnit Best Practices

assertEquals vs assertJsonStringEqualsJsonFileassertEquals

Failed asserting that two strings are equal.--- Expected+++ Actual@@ @@-'{ "Conference": "FOSDEM", "Talk": "PHPUnit", "JSON": "Apparently", "Shoutout": "Jenkins" }'+'{ "Conference": "FOSDEM", "Talk": "PHPUnit", "JSON": "Apparently", "Shoutout": "Hudson" }'

Page 19: PhpUnit Best Practices

assertEquals vs assertJsonStringEqualsJsonFileassertJsonStringEqualsJsonFile

Failed asserting that two objects are equal.--- Expected+++ Actual@@ @@ stdClass Object ( 'Conference' => 'FOSDEM' 'Talk' => 'PHPUnit' 'JSON' => 'Apparently'- 'Shoutout' => 'Jenkins'+ 'Shoutout' => 'Hudson' )

Page 20: PhpUnit Best Practices

HAVE A FAST TEST SUITEIf it takes to long to run your tests you won't do it

Page 21: PhpUnit Best Practices

SEPERATE YOUR TESTShttp://elblinkin.info/2012/03/goldilocks-on-test-sizes/

Page 22: PhpUnit Best Practices

BY FOLDER STRUCTURE.|-- src| ̀-- foo| ̀-- bar| ̀-- Baz.php-̀- tests |-- functional |-- integration |-- unit | ̀-- foo | ̀-- bar | ̀-- BazTest.php ̀-- web

phpunit tests/unit

Page 23: PhpUnit Best Practices

BY CONFIG FILE<testsuites> <testsuite name="Unit" suffix="Test.php"> <directory>tests</directory> </testsuite> <testsuite name="Integration" suffix="Test.Integration.php"> <directory>tests</directory> </testsuite></testsuites>

phpunit --testsuite Unit

Page 24: PhpUnit Best Practices

OR HOWEVER YOU SEE FITUse @groupUse @filter and naming conventions

Page 25: PhpUnit Best Practices

BOOTSTRAP ONLY WHAT YOU NEEDYou can use a test listener:

http://www.phpunit.de/manual/current/en/extending-phpunit.html#extending-phpunit.PHPUnitFrameworkTestListener

public function startTestSuite(PHPUnit_Framework_TestSuite $suite){ // Just an example of what is possible require __DIR__ . $suite->getName() . 'Bootstrap.php';}

Page 26: PhpUnit Best Practices

HOW MANY TESTS?

Web: 7Funtional: One per featureIntegration: One per 3 classesUnit: Find a balance

*totally made up numbers to drive home the point I'm trying to make

Page 27: PhpUnit Best Practices

WEB TESTS?behat (mink) for js-through-the-server testing - Great for testingyour whole stack

Really hard to maintainMink relives some of the pain

Test through your front controller instead of the webserver withbehat or phpunit

Faster, easier once set up

Page 28: PhpUnit Best Practices

TEST CLASSES,NOT METHODS

> Unit testing, in PHP, is about testing the observable behaviors of aclass!

Observable from the outside! Nobody cares about the internal state ofa class if it never changes the outcome of a method call.

Page 29: PhpUnit Best Practices

SAMPLEWhat should we test there?

If we don't call setValue calling execute will throw an exceptionIf we do call setValue calling execute will return the computedresult.So we are testing two behaviors of your class and not the methods inisolation!

public function setValue($value) { $this->value = $value;}

public function execute() { if (!$this->value) { throw new Exception("No Value, no good"); } return $value * 10; // business logic}

Page 30: PhpUnit Best Practices

RELEVANT BEHAVIORSWhat to test then?

return values

method calls to other objects

Global state

public fuction celciusToFarenheit($degreesFarenheit) { return ($degreesFarenheit - 32) * 5 ⁄ 9;}

public fuction stopCar() { $this->handbreak->engage(); $this->engine->shutdown();}

public fuction avoidThisWherePossible($logMessage) { file_put_contents(static::$LOGFILE, $logMessage, FILE_APPEND); $_SESSION['logcalls']++;}

Page 31: PhpUnit Best Practices

DON'T TEST GETTERS AND SETTERSOne test case per behavior

You waste timeYour code coverage reports won't tell you about dead codeIf they don't impact the outcome delete them

Page 32: PhpUnit Best Practices

QUESTIONS?- Miško Hevery

Additional resources

"The Clean Code Talks -- Unit TestingHow to Write Clean, Testable CodeThe Clean Code Talks - Don't Look For Things!Flaw: Brittle Global State & Singletonsstatic considered harmfulThe UNIT in unit testingAn introduction to PHPUnits @covers annotation

“The secret in testing is in writing testable code”

Page 33: PhpUnit Best Practices

THANK YOU

Page 34: PhpUnit Best Practices