TDD in Practice
Keep on Learning!
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.
Adding another Case
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() | |
); | |
} | |
} |
Finishing 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.
I don't understand TDD, really what it does is test, the only difference I see, is you finally have to think about refactoring. But from that I don't see any other difference. Because the first steps are the ones always used, use less code to complete the test. I guess I need more example to compare. You can help me understand better.