If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeLet's put TDD into practice!
I want to add a new getSpecification()
method to the Dinosaur
class that will return a string description - like:
Velociraptor carnivorous dinosaur is 5 meters long
TDD says: write that test first! Add public function testReturnsFullSpecificationOfDinosaur()
. Create a new dinosaur, but don't set any data on it. Let's test the default string: Unknown non-carnivorous dinosaur is 0 meters long
should equal $dinosaur->getSpecification()
.
... lines 1 - 7 | |
class DinosaurTest extends TestCase | |
{ | |
... lines 10 - 29 | |
public function testReturnsFullSpecificationOfDinosaur() | |
{ | |
$dinosaur = new Dinosaur(); | |
$this->assertSame( | |
'The Unknown non-carnivorous dinosaur is 0 meters long', | |
$dinosaur->getSpecification() | |
); | |
} | |
} |
Test done! Step 2 is to write the minimum amount of code to get this test to pass. In Dinosaur
, add public function getSpecification()
. So... what's the smallest amount of code we can write? We can just return a hardcoded string!
... lines 1 - 10 | |
class Dinosaur | |
{ | |
... lines 13 - 27 | |
public function getSpecification(): string | |
{ | |
return 'The Unknown non-carnivorous dinosaur is 0 meters long'; | |
} | |
} |
Genius! Ok, try the test!
./vendor/bin/phpunit
Ha! It passes! The third step to TDD is refactor... which I don't think is needed in this case.
Wait, what? You don't like my hardcoded string? Looks like you're missing the last boat back to the mainland. Just kidding ... I know, returning a hardcoded string is silly... and I don't do this in real life. But it shows off an important thing with TDD: keep your code simple. Don't make it unnecessarily fancy or cover unnecessary use-cases. If you do have an edge-case that you want to cover, add the test first, and then code for it.
Actually, let's do that now: add a new test method: testReturnsFullSpecificationForTyrannosaurus
. I want each Dinosaur
to have a genus - like Tyrannosaurus - and a flag that says whether or not it likes to eat people... I mean whether or not it's carnivorous. These will be used in getSpecification()
. Create a new Dinosaur()
and pass Tyrannosaurus
, and true
for carnivorous... because T-Rex's love to eat people.
Set its length to 12. This time, the specification should be:
Tyrannosaurus carnivorous dinosaur is 12 meters long
... lines 1 - 7 | |
class DinosaurTest extends TestCase | |
{ | |
... lines 10 - 39 | |
public function testReturnsFullSpecificationForTyrannosaurus() | |
{ | |
$dinosaur = new Dinosaur('Tyrannosaurus', true); | |
$dinosaur->setLength(12); | |
$this->assertSame( | |
'The Tyrannosaurus carnivorous dinosaur is 12 meters long', | |
$dinosaur->getSpecification() | |
); | |
} | |
} |
Test done! Let's write some code! Start in Dinosaur
: I'll add a __construct()
method with a $genus = 'Unknown'
argument and $isCarnivorous = false
. Add these two properties to the class. I'll go to the Code menu and then to Generate - or press Command+N on a Mac - select "ORM Annotations" to add annotations above each method. We don't technically need those right now... but it'll save time later.
... lines 1 - 10 | |
class Dinosaur | |
{ | |
... lines 13 - 17 | |
/** | |
* @var string | |
* @ORM\Column(type="string") | |
*/ | |
private $genus; | |
/** | |
* @var bool | |
* @ORM\Column(type="boolean") | |
*/ | |
private $isCarnivorous; | |
public function __construct(string $genus = 'Unknown', bool $isCarnivorous = false) | |
{ | |
... lines 32 - 33 | |
} | |
... lines 35 - 54 | |
} |
Down in the constructor, set both properties to their values. The default values for each argument match our first test.
... lines 1 - 10 | |
class Dinosaur | |
{ | |
... lines 13 - 29 | |
public function __construct(string $genus = 'Unknown', bool $isCarnivorous = false) | |
{ | |
$this->genus = $genus; | |
$this->isCarnivorous = $isCarnivorous; | |
} | |
... lines 35 - 54 | |
} |
In getSpecification()
, we can't really fake things anymore. Return sprintf()
and the original string, but replace the variable parts with %s
, %s
and %d
.
Then pass $this->genus
, $this->isCarnivorous
to print carnivorous
or non-carnivorous
, and then $this->length
.
... lines 1 - 10 | |
class Dinosaur | |
{ | |
... lines 13 - 45 | |
public function getSpecification(): string | |
{ | |
return sprintf( | |
'The %s %s dinosaur is %d meters long', | |
$this->genus, | |
$this->isCarnivorous ? 'carnivorous' : 'non-carnivorous', | |
$this->length | |
); | |
} | |
} |
Perfect! Find your terminal and run the tests!
./vendor/bin/phpunit
Passing! Now to step 3... refactor! And this time... I will! Let's include the word carnivorous
in the string. Then below, just print non-
if needed. I don't even need to think about whether or not I made any mistakes. Just run the tests!
... lines 1 - 10 | |
class Dinosaur | |
{ | |
... lines 13 - 45 | |
public function getSpecification(): string | |
{ | |
return sprintf( | |
'The %s %scarnivorous dinosaur is %d meters long', | |
... line 50 | |
$this->isCarnivorous ? '' : 'non-', | |
... lines 52 - 53 | |
} | |
} |
./vendor/bin/phpunit
I love it! Testing gives you confidence!
Next! Let's create a DinosaurFactory
- because that sounds awesome.
// composer.json
{
"require": {
"php": "^7.0, <7.4",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/doctrine-bundle": "^1.6", // 1.10.3
"doctrine/orm": "^2.5", // v2.7.2
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"sensio/distribution-bundle": "^5.0.19", // v5.0.21
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.28
"symfony/monolog-bundle": "^3.1.0", // v3.1.2
"symfony/polyfill-apcu": "^1.0", // v1.6.0
"symfony/swiftmailer-bundle": "^2.3.10", // v2.6.7
"symfony/symfony": "3.3.*", // v3.3.13
"twig/twig": "^1.0||^2.0" // v2.4.4
},
"require-dev": {
"doctrine/data-fixtures": "^1.3", // 1.3.3
"doctrine/doctrine-fixtures-bundle": "^2.3", // v2.4.1
"liip/functional-test-bundle": "^1.8", // 1.8.0
"phpunit/phpunit": "^6.3", // 6.5.2
"sensio/generator-bundle": "^3.0", // v3.1.6
"symfony/phpunit-bridge": "^3.0" // v3.4.30
}
}