Slide 1

Slide 1 text

Unit testing A guide to writing clean, testable code, that will be easy to maintain and extend Monday, September 26, 11

Slide 2

Slide 2 text

• @avalanche123 • github.com/avalanche123 • avalanche123.com Bulat Shakirzyanov Monday, September 26, 11

Slide 3

Slide 3 text

•Unit testing •Dependency injection •Mock objects •Test driven development •Clean code techniques Agenda Monday, September 26, 11

Slide 4

Slide 4 text

•End to end, black box testing - tests are hardest to write and run, tests find bugs that are easiest to fix (bad markup, wrong css rule) Types of tests Monday, September 26, 11

Slide 5

Slide 5 text

•Functional, wiring tests - easier to write tests, find easy to medium bugs (cannot connect to database, because server is misconfigured) Types of tests Monday, September 26, 11

Slide 6

Slide 6 text

Types of tests •Unit tests - easiest to write, fix logic bugs and hard-to-spot design problems (violation of single responsibility principle, hard-coded function calls) Monday, September 26, 11

Slide 7

Slide 7 text

“Unit testing is a method by which individual units of 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

Slide 8

Slide 8 text

Why and why not unit test To test or not to test? Monday, September 26, 11

Slide 9

Slide 9 text

•Debugging is a time consuming process •When new functionality is 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

Slide 10

Slide 10 text

•I never make mistakes •The functionality is trivial •Tests slow me down •Management won't let me Excuses Monday, September 26, 11

Slide 11

Slide 11 text

Facts •I don't know how to test Monday, September 26, 11

Slide 12

Slide 12 text

Properties of a unit test Recognize Monday, September 26, 11

Slide 13

Slide 13 text

•Isolated •Repeatable •Fast •Self-documenting Unit test is... Monday, September 26, 11

Slide 14

Slide 14 text

•No need to build a car to test tires •Testing payment gateway should not affect my monthly statement •It runs faster Test isolation Monday, September 26, 11

Slide 15

Slide 15 text

•Building cars to test tires is a waste of time and money Test isolation Monday, September 26, 11

Slide 16

Slide 16 text

•Tests need to be run by every developer, no matter what stack he/she uses •Tests must not rely on the environment in which they are being run Repeatability Monday, September 26, 11

Slide 17

Slide 17 text

•Lower the project entry costs/ barriers Repeatability Monday, September 26, 11

Slide 18

Slide 18 text

•Time is money, the longer we wait for tests to run, the more money our clients or company loses Speed Monday, September 26, 11

Slide 19

Slide 19 text

•Testable code is clear and easy to follow •No need to explain how a certain component works, they can just look at the test •No need to write documentation Self-documentation Monday, September 26, 11

Slide 20

Slide 20 text

•We usually look for usage examples in documentation anyway Self-documentation Monday, September 26, 11

Slide 21

Slide 21 text

•Isolate •Reproduce •Speed-up •Document TODO Monday, September 26, 11

Slide 22

Slide 22 text

Decouple components Property 1: Isolation Monday, September 26, 11

Slide 23

Slide 23 text

“[dependency injection is] … a technique for decoupling highly dependent software components” wikipedia.org Monday, September 26, 11

Slide 24

Slide 24 text

•Can't use a test version of PaymentGateway •This test will end up in my monthly statement Hidden dependency charge($amount, $this); } } Monday, September 26, 11

Slide 25

Slide 25 text

•PaymentGateway can now be replaced •BankAccount shouldn't be responsible for charging itself Injected dependency charge($amount, $this); } } Monday, September 26, 11

Slide 26

Slide 26 text

•Makes BankAccount class lighter, leaving it only responsibility of storing account balance Single responsibility charge(100, $account); Monday, September 26, 11

Slide 27

Slide 27 text

•Testable code •No hidden dependencies = Better API = Maintainability •All common initialization logic can be extracted (Dependency Injection Container) DI benefits Monday, September 26, 11

Slide 28

Slide 28 text

•Long method call chains DI gotchas Monday, September 26, 11

Slide 29

Slide 29 text

Law Of Demeter Method call chains are bad Monday, September 26, 11

Slide 30

Slide 30 text

“Each unit should have only limited knowledge about other units: 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

Slide 31

Slide 31 text

Bad Good Law Of Demeter getStockItem()->getSku(); getItemSku(); //Product.php class Product { //... public function getItemSku() { return $this->stockItem->getSku(); } } Monday, September 26, 11

Slide 32

Slide 32 text

Change-proof code Monday, September 26, 11

Slide 33

Slide 33 text

Which one makes more sense? getWriter()->getFile()->writeLine('Some Log'); log('Some Log'); Monday, September 26, 11

Slide 34

Slide 34 text

“Programmers should avoid writing code that looks like: dog.getBody().getTail().wag(); … 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

Slide 35

Slide 35 text

•Isolate ✔ •Reproduce •Speed-up •Document TODO Monday, September 26, 11

Slide 36

Slide 36 text

Control the interaction Property 2: Speed Property 3: Repeatability Monday, September 26, 11

Slide 37

Slide 37 text

“… mock objects are simulated objects that mimic the behavior of real objects in controlled ways” wikipedia.org Monday, September 26, 11

Slide 38

Slide 38 text

Test A Mock S S Mock objects Monday, September 26, 11

Slide 39

Slide 39 text

•Test independence •Interface discovery Mock objects Monday, September 26, 11

Slide 40

Slide 40 text

•By mocking out external dependencies, we can achieve faster and more granular tests, e.g. mock filesystem, database, third party api, etc. Test independence Monday, September 26, 11

Slide 41

Slide 41 text

Isolating filesystem •Now we actually test that FileLogger correctly decorates the log string before writing it to file, leaving the actual write to file part to the File class and test. 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

Slide 42

Slide 42 text

Methods, not functions write('content'); Monday, September 26, 11

Slide 43

Slide 43 text

Mocking functions •An example of transforming a function into a method 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

Slide 44

Slide 44 text

•Right now you know the least about your problem domain •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

Slide 45

Slide 45 text

Interface discovery •Had the example been written before the BankAccount class, it would’ve given us better understanding of the problem and could’ve helped design the BankAccount class itself getMock('BankAccount'); $bankAccount ->expects($this->once()) ->method('credit') ->with(100) ; $this->gateway->charge(100, $bankAccount); } } Monday, September 26, 11

Slide 46

Slide 46 text

Interface discovery Test B S Mock T U T Mock U Monday, September 26, 11

Slide 47

Slide 47 text

•So, it turns out, that the best use for mock objects is for iterative interface discovery Interface discovery Monday, September 26, 11

Slide 48

Slide 48 text

Wait, what? Test driven development Monday, September 26, 11

Slide 49

Slide 49 text

•Write a failing test case, use mock objects to represent classes that are not implemented Red Monday, September 26, 11

Slide 50

Slide 50 text

•When the test makes sense and the intent is clear, write method bodies Green Monday, September 26, 11

Slide 51

Slide 51 text

•Now that your tests are green, see what needs to be extracted (create dependencies, remove duplication) Refactor Monday, September 26, 11

Slide 52

Slide 52 text

TDD example Monday, September 26, 11

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Slide 55

Slide 55 text

$ phpunit tests/ PHPUnit 3.5.0RC1 by Sebastian Bergmann. F Time: 0 seconds, Memory: 3.25Mb There was 1 failure: 1) PaymentGatewayTest::testCharge Expectation failed for method name is equal to 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

Slide 56

Slide 56 text

Green credit($amount); } }

Slide 57

Slide 57 text

$ phpunit tests/ PHPUnit 3.5.0RC1 by Sebastian Bergmann. . Time: 0 seconds, Memory: 3.25Mb OK (1 test, 1 assertion) Green Monday, September 26, 11

Slide 58

Slide 58 text

Mock only what you own... And what you don’t own, own Monday, September 26, 11

Slide 59

Slide 59 text

Proxy •Classes change, method names, signature, visibility changes. •What will you do if a class changed one of its public methods to final? •Doctrine MongoDB ODM is built using proxies 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

Slide 60

Slide 60 text

Repeatability •If a class or library that you test relies 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

Slide 61

Slide 61 text

Repeatability markTestSkipped( 'Mongo extension not installed' ); } } }

Slide 62

Slide 62 text

The code evolution Refactoring Monday, September 26, 11

Slide 63

Slide 63 text

“… refactoring is … changing … source code without modifying its external functional behavior in order to improve … the software.” wikipedia.org Monday, September 26, 11

Slide 64

Slide 64 text

Refactoring Monday, September 26, 11

Slide 65

Slide 65 text

•Best test code coverage possible •Design is born out of necessity, not forecast TDD benefits Monday, September 26, 11

Slide 66

Slide 66 text

•Shared fixtures •External dependencies •Test code duplication •Over-mocking TDD gotchas Monday, September 26, 11

Slide 67

Slide 67 text

Premature optimization Take care of speed after the design is done Monday, September 26, 11

Slide 68

Slide 68 text

“Premature optimization is the root of all evil” C. A. 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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

•Isolate ✔ •Reproduce ✔ •Speed-up ✔ •Document TODO Monday, September 26, 11

Slide 72

Slide 72 text

Keep your code clean Self-documentation Monday, September 26, 11

Slide 73

Slide 73 text

•Don’t Repeat Yourself (DRY) - if a piece of code 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

Slide 74

Slide 74 text

•Don’t optimize the code before it’s been written in the most straightforward way possible, forecasts are usually wrong anyway Monday, September 26, 11

Slide 75

Slide 75 text

•Avoid magic, its easier to code when the API is not lying, dependency injection helps to get rid of magic •Avoid boolean parameters, they kill readability Monday, September 26, 11

Slide 76

Slide 76 text

processOrder(int $orderNumber, boolean $emailConfirmation = true); processOrder(int $orderNumber); $service->emailConfirmation(int $orderNumber); Removing booleans Monday, September 26, 11

Slide 77

Slide 77 text

•Use inheritance and polymorphism over conditionals •Use composition over inheritance Monday, September 26, 11

Slide 78

Slide 78 text

The less the better Conditionals Monday, September 26, 11

Slide 79

Slide 79 text

•Its probably not bad to have one if statement Conditional loggerCallable) { $this->log(array( 'sort' => true, 'fields' => $fields, )); } $this->mongoCursor->sort($fields); return $this; } } Monday, September 26, 11

Slide 80

Slide 80 text

loggerCallable) { $this->log(array( 'batchInsert' => true, 'num' => count($a), 'data' => $a )); } $result = $this->mongoCollection->batchInsert($a, $options); return $result; } public function getDBRef(array $reference) { //... if ($this->loggerCallable) { $this->log(array( 'get' => true, 'reference' => $reference, )); } return $dbRef; } } Monday, September 26, 11

Slide 81

Slide 81 text

•Every new method that needs to be logged, will have to add the if statement •There are plenty of if statements in this class Duplication Monday, September 26, 11

Slide 82

Slide 82 text

•The class that has conditionals is much harder to understand 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

Slide 83

Slide 83 text

•LoggableMongoCollection is responsible for logging collection interaction. •The regular MongoCollection class doesn’t need to know about logging Refactoring Monday, September 26, 11

Slide 84

Slide 84 text

mongoCollection->batchInsert($a, $options); return $result; } public function getDBRef(array $reference) { return $dbRef; } } class LoggableMongoCollection extends MongoCollection { public function batchInsert(array &$a, array $options = array()) { $this->log(array( 'batchInsert' => true, 'num' => count($a), 'data' => $a )); return parent::batchInsert($a, $options); } public function getDBRef(array $reference) { $this->log(array( 'get' => true, 'reference' => $reference, )); return parent::getDBRef($reference); } } Monday, September 26, 11

Slide 85

Slide 85 text

•Did my if statements disappear? •No •The conditional was actually 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

Slide 86

Slide 86 text

loggerCallable = $loggerCallable; } public function getMongoCollection() { return isset($this->loggerCallable) ? new LoggableMongoCollection($this->loggerCallable) : new MongoCollection(); } } Monday, September 26, 11

Slide 87

Slide 87 text

•Dependency Injection helps to separate the “initialization” logic from “domain” logic •This allows for greater testability as each concern is tested in a dedicated environment Monday, September 26, 11

Slide 88

Slide 88 text

• Consider the following controller: • It lets you disable, 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

Slide 89

Slide 89 text

•Extract common interface Switches

Slide 90

Slide 90 text

Switches •Create your interface implementations – one implementation per switch condition •Classes can be tested in isolation Monday, September 26, 11

Slide 91

Slide 91 text

Slide 92

Slide 92 text

Polymorphism •Lighter classes •Usage of the true type system •You could also use abstract classes and complicated inheritance trees instead of one interface and orphaned implementations Monday, September 26, 11

Slide 93

Slide 93 text

Composition Extract and inject re-used components instead of inheriting functionality Monday, September 26, 11

Slide 94

Slide 94 text

•There might be cases when to receive an already-implemented functionality 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

Slide 95

Slide 95 text

loggerCallable) { return; } $log['class'] = $this->class->name; $log['db'] = $this->class->db; $log['collection'] = $this->class->collection; call_user_func($this->loggerCallable, $log); } } class MongoCursor extends LoggableMongoCollection implements \Iterator, \Countable { //... public function sort($fields) { if ($this->loggerCallable) { $this->log(array( 'sort' => true, 'fields' => $fields, )); } $this->mongoCursor->sort($fields); return $this; } } Monday, September 26, 11

Slide 96

Slide 96 text

•But what happens if we need the same functionality in 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

Slide 97

Slide 97 text

Composition •The solution is to extract common functionality into a dedicated class Monday, September 26, 11

Slide 98

Slide 98 text

loggerCallable = $loggerCallable; } public function log(array $log) { $log['class'] = $this->class->name; $log['db'] = $this->class->db; $log['collection'] = $this->class->collection; call_user_func($this->loggerCallable, $log); } } Monday, September 26, 11

Slide 99

Slide 99 text

•Create an interface, that would define how the new component will compose the existing ones Composition Monday, September 26, 11

Slide 100

Slide 100 text

Slide 101

Slide 101 text

mongoCursor->sort($fields); return $this; } } mongoCollection->batchInsert($a, $options); return $result; } public function getDBRef(array $reference) { //... return $dbRef; } } Parent classes don’t log Monday, September 26, 11

Slide 102

Slide 102 text

logger = $logger; } //... public function sort($fields) { $this->logger->log(array( 'sort' => true, 'fields' => $fields, )); return parent::sort($fields); } } logger = $logger; } public function batchInsert(array &$a, array $options = array()) { $this->logger->log(array( 'batchInsert' => true, 'num' => count($a), 'data' => $a )); return parent::batchInsert($a, $options); } public function getDBRef(array $reference) { $this->logger->log(array( 'get' => true, 'reference' => $reference, )); return parent::getDBRef($reference); } } Child classes that log Monday, September 26, 11

Slide 103

Slide 103 text

•Don't decompose objects if there is no need for re-use, YAGNI Decomposition Monday, September 26, 11

Slide 104

Slide 104 text

•Isolate ✔ •Reproduce ✔ •Speed-up ✔ •Document ✔ TODO Monday, September 26, 11

Slide 105

Slide 105 text

Resources for self-improvement What's next? Monday, September 26, 11

Slide 106

Slide 106 text

• Google Tech Talks - http://www.youtube.com/user/GoogleTechTalks • Inheritance, Polymorphism, & 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

Slide 107

Slide 107 text

Now is the time to ask them Questions? Monday, September 26, 11