Getting Started With TDD
Eric Hogue - @ehogue
Confoo - 2014-02-27
TDD
Where should I
Start?
1. Unit tests
2. Test Driven Development
3. What’s next?
Unit Tests
Unit Test
a method by which individual units of source
code [...] are tested to determine if they are fit
for use
http://en.wikipedia.org/wiki/Unit_testing
Don’t Cross boundaries
Tools
●
●
●
●

SimpleTest
atoum
PHPT
PHPUnit
Installation - Phar
$ wget
https://phar.phpunit.de/phpunit.phar
$ chmod +x phpunit.phar
$ mv phpunit.phar /usr/local/bin/phpunit
Installation - Pear
pear config-set auto_discover 1
pear install pear.phpunit.de/PHPUnit
Installation - Composer
# composer.json
{
"require-dev": {
"phpunit/phpunit": "3.7.*"
}
}
$ composer install
PHPUnit
FactorialTest.php
<?php
class FactorialTest extends
PHPUnit_Framework_TestCase {
}
public function testSomething() {
}
/** @test */
public function somethingElse() {
}
● Arrange
● Act
● Assert
Arrange
/** @test */
public function factOf1() {
$factorial = new Factorial;
}
Act
/** @test */
public function factOf1() {
$factorial = new Factorial;
$result = $factorial->fact(1);
}
Assert
/** @test */
public function factOf1() {
$factorial = new Factorial;
$result = $factorial->fact(1);
$this->assertSame(1, $result);
}
PHPUnit Assertions
●
●
●
●
●
●
●

$this->assertTrue();
$this->assertEquals();
$this->assertSame();
$this->assertContains();
$this->assertNull();
$this->assertRegExp();
...
Preparing For Your Tests
setup() -> Before every tests
teardown() -> After every tests
setUpBeforeClass() + tearDownAfterClass()
Once per test case
phpunit.xml
<phpunit bootstrap="bootstrap.php"
colors="true"
strict="true"
verbose="true"
>
...
</phpunit>
phpunit.xml
<phpunit>
<testsuites>
<testsuite name="My Test Suite">
<directory>path</directory>
<file>path</file>
<exclude>path</exclude>
</testsuite>
</testsuites>
</phpunit>
TDD
Red - Green - Refactor
Red
Write a failing test
Red - Green - Refactor
Green
Make it pass
Red - Green - Refactor
Refactor
Fix any shortcuts you took
/** @test */
public function create() {
$this->assertNotNull(new Factorial);
}
class Factorial {
}
/** @test */
public function factOf1() {
$facto = new Factorial;
$this->assertSame(1,
$facto->fact(1));
}
public function fact($number) {
return 1;
}
Duplication
public function create() {
$this->assertNotNull(new Factorial);
}
public function factOf1() {
$facto = new Factorial;
...
public function setup() {
$this->facto = new Factorial;
}
/** @test */
public function factOf1() {
$this->assertSame(1,
$this->facto->fact(1));
}
/** @test */
public function factOf2() {
$this->assertSame(2,
$this->facto->fact(2));
}
public function fact($number) {
return $number;
}
More duplication
/** @test */
public function factOf1() {
$this->assertSame(1,
$this->facto->fact(1));
}
/** @test */
public function factOf2() {
$this->assertSame(2,
$this->facto->fact(2));
}
public function factDataProvider() {
return array(
array(1, 1),
array(2, 2),
);
}
/**
* @test
* @dataProvider factDataProvider
*/
public function factorial($number,
$expected) {
...
…
$result =
$this->facto->fact($number);
$this->assertSame($expected,
$result);
}
public function factDataProvider() {
…
array(2, 2),
array(3, 6),
...
public function fact($number) {
if ($number < 2) return 1;
return $number *
$this->fact($number - 1);
}
It’s a lot of
work
Dependencies
Problems
class Foo {
public function __construct() {
$this->bar = new Bar;
}
}
Dependency Injection
Setter Injection
class Foo {
public function setBar(Bar $bar) {
$this->bar = $bar;
}
public function doSomething() {
// Use $this->bar
}
}
Constructor Injection
class Foo {
public function __construct(
Bar $bar) {
$this->bar = $bar;
}
public function doSomething() {
// Use $this->bar
}
}
Pass the dependency directly
class Foo {
public function doSomething(
Bar $bar) {
// Use $bar
}
}
File System
vfsStream
Virtual Files System
composer.json
"require-dev": {
"mikey179/vfsStream": "*"
},
Check if a folder was created
$root = vfsStream::setup('dir');
$parentDir = $root->url('dir');
//Code creating sub folder
$SUT->createDir($parentDir, 'test');
$this->assertTrue(
$root->hasChild('test'));
Reading a file
$struct = array(
'subDir' => array('test.txt'
=> 'content')
);
$root = vfsStream::setup('root',
null, $struct);
$parentDir = $root->url('root');
...
Reading a file
…
$content = file_get_contents(
$parentDir . '/subDir/test.txt');
$this->assertSame('content',
$content);
Databases
Mocks
Replaces a dependency
● PHPUnit mocks
● Mockery
● Phake
Creation
$mock = $this->getMock('NSClass');
Creation
$mock = $this->getMock('NSClass');
Or
$mock = $this->getMockBuilder
('NamespaceClass')
->disableOriginalConstructor()
->getMock();
$mock->expects($this->once())
->method('methodName')
$mock->expects($this->once())
->method('methodName')
->with(1, 'aa', $this->anything())
$mock->expects($this->once())
->method('methodName')
->with(1, 'aa', $this->anything())
->will($this->returnValue(10));
Mocking PDO
$statement = $this->getMockBuilder
('PDOStatement')
->getMock();
$statement->expects($this->once())
->method('execute')
->will($this->returnValue(true));
...
...
$statement->expects($this->once())
->method('fetchAll')
->will(
$this->returnValue(
array(array('id' => 123))
)
);
...
$this->getMockBuilder('PDO')
->getMock();
…
$pdo = $this->getMockBuilder(
'stdClass')
->setMethods(array('prepare'))
->getMock();
$pdo->expects($this->once())
->method('prepare')
->will(
$this->returnValue($statement));
class PDOMock extends PDO {
public function __construct() {}
}
$pdo = $this->getMockBuilder
('PDOMock')
->getMock();
mysql_*
DbUnit Extension
extends
PHPUnit_Extensions_Database_TestCase
public function getConnection() {
$pdo = new PDO('sqlite::memory:');
return $this->
createDefaultDBConnection(
$pdo, ':memory:');
}
public function getDataSet() {
return $this->
createFlatXMLDataSet('file');
}
API
● Wrap all call into a class
○ ZendHttp
○ Guzzle
○ Simple class that uses curl

● Mock the class
○ Return the wanted xml/json
Pros and Cons
Pros
● Less regressions
Pros
● Less regressions
● Trust
Pros
● Less regressions
● Trust
● Low coupling
Pros
●
●
●
●

Less regressions
Trust
Low coupling
Simple Design
Cons
● Takes longer
“If it doesn't have to
work, I can get it done a
lot faster!”
- Kent Beck
Cons
● Takes longer
● Can be hard to sell to managers
Cons
● Takes longer
● Can be hard to sell to managers
● It’s hard
Prochaines étapes?
Continuous Testing - Guard
Continuous Integration
Continuous Integration
● Run your tests automatically
○
○
○
○

Unit Tests
Acceptance Tests
Performance Tests
...
Continuous Integration
● Run your tests automatically
○
○
○
○

Unit Tests
Acceptance Tests
Performance Tests
…

● Check Standards
○ phpcs
Continuous Integration
● Run your tests automatically
○
○
○
○

Unit Tests
Acceptance Tests
Performance Tests
…

● Check Standards
○ phpcs

● Check for "code smells"
○ phpcpd
○ PHP Depend
○ PHP Mess Detector
Questions
Twitter:
@ehogue
Blog:
http://erichogue.ca/
Slides: http://www.
slideshare.net/EricHogue
Credits
●
●
●
●
●
●
●
●
●
●
●
●
●
●
●

Paul - http://www.flickr.com/photos/pauldc/4626637600/in/photostream/
JaseMan - http://www.flickr.com/photos/bargas/3695903512/
mt 23 - http://www.flickr.com/photos/32961941@N03/3166085824/
Adam Melancon - http://www.flickr.com/photos/melancon/348974082/
Zhent_ - http://www.flickr.com/photos/zhent/574472488/in/faves-96579472@N07/
Ryan Vettese - http://www.flickr.com/photos/rvettese/383453435/
shindoverse - http://www.flickr.com/photos/shindotv/3835363999/
Eliot Phillips - http://www.flickr.com/photos/hackaday/5553713944/
World Bank Photo Collection - http://www.flickr.com/photos/worldbank/8262750458/
Steven Depolo - http://www.flickr.com/photos/stevendepolo/3021193208/
Deborah Austin - http://www.flickr.com/photos/littledebbie11/4687828358/
tec_estromberg - http://www.flickr.com/photos/92334668@N07/11122773785/
nyuhuhuu - http://www.flickr.com/photos/nyuhuhuu/4442144329/
Damián Navas - http://www.flickr.com/photos/wingedwolf/5471047557/
Improve It - http://www.flickr.com/photos/improveit/1573943815/

Getting started with TDD - Confoo 2014