source code are tested to determine if they are fit for use. A unit is the smallest testable part of an application.” wikipedia.org Monday, September 26, 11
added, how do we make sure the old one doesn't break •By looking at a unit test, you can see the class in action, which lets you easily understand its intent and proper use •Unit tests are the only real measure of project health and code quality Reasons Monday, September 26, 11
end up in my monthly statement Hidden dependency <?php class BankAccount { //... public function charge($amount) { $gateway = new PaymentGateway(); // or $gateway = PaymentGateway::getInstance(); // or $gateway = Registry::get('payment_gateway'); //... $gateway->charge($amount, $this); } } Monday, September 26, 11
account balance Single responsibility <?php class BankAccount { //... } $gateway = new PaymentGateway(); $account = new BankAccount(); $gateway->charge(100, $account); Monday, September 26, 11
only units "closely" related to the current unit” “Each unit should only talk to its friends; don't talk to strangers” “Only talk to your immediate friends” wikipedia.org Monday, September 26, 11
The solution is … [to] … rewrite our example as: dog.expressHappiness(); and let the implementation of the dog decide what this means.” ThoughtWorks UK Monday, September 26, 11
the log string before writing it to file, leaving the actual write to file part to the File class and test. <?php class FileLoggerTest extends PHPUnit_Framework_TestCase { //... public function testShouldFormatLogString() { $file = $this->getMock('File'); $file ->expects($this->once()) ->method('write') ->with( sprintf( '[ERR] %s some error', date(DATE_ISO8601) ) ) ; $logger = new FileLogger($file); $logger->err('some error'); } } Monday, September 26, 11
method <?php class File { private $handle; public function __construct($path, $mode) { $this->handle = fopen($path, $mode); } public function write($content, $length = null) { fwrite($this->handle, $content, $length); } public function __destruct() { fclose($this->handle); } } Monday, September 26, 11
•Your knowledge of the domain grows as you spend more time working in it •Why write something that you'll throw away if you can mock it and see if it makes sense? Interface discovery Monday, September 26, 11
class, it would’ve given us better understanding of the problem and could’ve helped design the BankAccount class itself <?php class PaymentGatewayTest extends PHPUnit_Framework_TestCase { //... public function testShouldCreditAccountByAmountCharged() { $bankAccount = $this->getMock('BankAccount'); $bankAccount ->expects($this->once()) ->method('credit') ->with(100) ; $this->gateway->charge(100, $bankAccount); } } Monday, September 26, 11
extends PHPUnit_Framework_TestCase { protected $gateway; public function setUp() { $this->gateway = new PaymentGateway(); } public function tearDown() { unset($this->gateway); } public function testShouldCreditAccountByAmountCharged() { $bankAccount = $this->getMock('BankAccount'); $bankAccount ->expects($this->once()) ->method('credit') ->with(100) ; $this->gateway->charge(100, $bankAccount); } } Red Monday, September 26, 11
0 seconds, Memory: 3.25Mb There was 1 failure: 1) PaymentGatewayTest::testCharge Expectation failed for method name is equal to <string:credit> when invoked 1 time(s). Method was expected to be called 1 times, actually called 0 times. FAILURES! Tests: 1, Assertions: 1, Failures: 1. Red Monday, September 26, 11
you do if a class changed one of its public methods to final? •Doctrine MongoDB ODM is built using proxies <?php namespace My; class Mongo { private $mongo; public function __construct(\Mongo $mongo) { $this->mongo = $mongo; } public function selectDB($name) { return new MongoDB($this->mongo->selectDB($name)); } } class MongoDB { private $mongoDb; public function __construct(\MongoDB $mongoDb) { $this->mongoDb = $mongoDb; } } Monday, September 26, 11
on an external component which might not be present during testing and the absence of which might break the tests, make sure to add checks and mark tests as skipped Monday, September 26, 11
function setUp() { if (!class_exists('Mongo')) { $this->markTestSkipped( 'Mongo extension not installed' ); } } } <?php namespace Tests; class MongoTest extends MongoTestCase { public function setUp() { parent::setUp(); //... } } Monday, September 26, 11
R. Hoare, Computer Scientist “"Premature optimization" is a phrase used to describe a situation where a programmer lets performance considerations affect the design of a piece of code.” wikipedia.org Monday, September 26, 11
public function __construct(ArrayCollection $r = null) { $this->reviews = $r ?: new ArrayCollection(); } public function getReviewCount() { return $this->reviewCount; } public function incrementReviewCount() { $this->reviewCount++; } public function decrementReviewCount() { $this->reviewCount--; } public function addReview(Review $review) { $this->reviews->add($review); $this->incrementReviewCount(); } public function removeReview(Review $review) { $this->reviews->remove($review); $this->decrementReviewCount(); } } Monday, September 26, 11
= null) { $this->reviews = $r ?: new ArrayCollection(); } public function getReviewCount() { return $this->reviews->count(); } public function addReview(Review $review) { $this->reviews->add($review); } public function removeReview(Review $review) { $this->reviews->remove($review); } } Monday, September 26, 11
appears more than two times, extract it into a dedicated function, method or class •You ain’t gonna need it (YAGNI) - there is no need to implement ACL if you were asked to write a forum board, there might never be a need Monday, September 26, 11
and test, as there are several possible execution flows. •To test the previous example, you would need to instantiate the class with and without $loggerCallable and test each method twice. Conditionals Monday, September 26, 11
moved to the instantiation part of the application, it is not part of the domain logic anymore •Testing such dedicated classes is much simpler, since there is only one execution flow Monday, September 26, 11
= null) { $this->loggerCallable = $loggerCallable; } public function getMongoCollection() { return isset($this->loggerCallable) ? new LoggableMongoCollection($this->loggerCallable) : new MongoCollection(); } } Monday, September 26, 11
enable or list certain object type. • It uses switch statements to do so • It introduces tons of duplication • Each method will have at least three test case in order to achieve the necessary coverage • Why reinvent the type system if OOP already lets us use types (classes)? Switches <?php class DisablerController extends Controller { public function disableAction($type) { switch ($type) { case 'product': case 'seller': case 'supplier': } } public function enableAction($type) { switch ($type) { case 'product': case 'seller': case 'supplier': } } public function listAction($type) { switch ($type) { case 'product': case 'seller': case 'supplier': } } } Monday, September 26, 11
disableAction() { } public function enableAction() { } public function listAction() { } } <?php class SellerDisablerController extends Controller implements DisablerControllerInterface { public function disableAction() { } public function enableAction() { } public function listAction() { } } <?php class SupplierDisablerController extends Controller implements DisablerControllerInterface { public function disableAction() { } public function enableAction() { } public function listAction() { } } Monday, September 26, 11
and keep it DRY, you extend the class encapsulating it •For example: a MongoCursor, that extends LoggableMongoCollection for its ->log() method Composition Monday, September 26, 11
parallel hierarchies? •Single inheritance doesn't allow it, we end up duplicating the ->log() method in both hierarchies •Composition to the rescue Monday, September 26, 11
Testing • Unit Testing • Blogs • Invisible to the Eye – personal blog of Giorgio Sironi, Software Architect • Books • Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson and John M. Vlissides, Addison-Wesley Professional, 1994 • Refactoring: Improving the Design of Existing Code by Martin Fowler, Kent Beck, John Brant, William Opdyke and Don Roberts, Addison-Wesley Professional, 1999 • Patterns of Enterprise Application Architecture by Martin Fowler, Addison-Wesley Professional, 2002 • xUnit Test Patterns: Refactoring Test Code by Gerard Meszaros, Addison- Wesley, 2007 • Growing Object-Oriented Software Guided by Tests by Steve Freeman and Nat Resources for self- improvement Monday, September 26, 11