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 SubscribeBefore we move on to the last step in TDD, I think we need to add a couple more size description tests for medium and small dinosaurs.
In our DinosaurTest::class
copy our testDino10MetersOrGreaterIsLarge
method and rename it to testDinoBetween5And9MetersIsMedium()
. Inside, change the length
of our $dino
from 10
to 5
, use Medium
for the expected value, and update the message to Medium
as well. Finally, paste the method again for our small dino test, using the name testDinoUnder5MetersIsSmall()
. Set the length to 4
, assert that Small
is identical to getSizeDescription()
and also update the message.
... lines 1 - 7 | |
class DinosaurTest extends TestCase | |
{ | |
... lines 10 - 24 | |
public function testDino10MetersOrGreaterIsLarge(): void | |
{ | |
$dino = new Dinosaur(name: 'Big Eaty', length: 10); | |
self::assertSame('Large', $dino->getSizeDescription(), 'This is supposed to be a large Dinosaur'); | |
} | |
public function testDinoBetween5And9MetersIsMedium(): void | |
{ | |
$dino = new Dinosaur(name: 'Big Eaty', length: 5); | |
self::assertSame('Medium', $dino->getSizeDescription(), 'This is supposed to be a medium Dinosaur'); | |
} | |
public function testDinoUnder5MetersIsSmall(): void | |
{ | |
$dino = new Dinosaur(name: 'Big Eaty', length: 4); | |
self::assertSame('Small', $dino->getSizeDescription(), 'This is supposed to be a small Dinosaur'); | |
} | |
} |
Back in our terminal, run the tests again:
./vendor/bin/phpunit --testdox
And... they're failing! But not because our method returns the wrong result. They're failing due to a type error on getSizeDescription()
:
The return value must be of type string and none is returned.
Do you remember earlier we ran our large dinosaur test before writing the method and we didn't see our "this is supposed to be a large dino" message? Well, we don't see it here either... That's because PHP threw an error... and so the getSizeDescription()
message explodes before PHPUnit can run the assertSame()
method. It's no big deal and we can still use the stack trace to see exactly where things went wrong.
Alrighty, back to the Dinosaur
class. Lets fix these tests by adding if ($this->length)
is less than 5
, return 'Small'
:
... lines 1 - 4 | |
class Dinosaur | |
{ | |
... lines 7 - 39 | |
public function getSizeDescription(): string | |
{ | |
if ($this->length >= 10) { | |
return 'Large'; | |
} | |
if ($this->length < 5) { | |
return 'Small'; | |
} | |
... lines 49 - 52 | |
} | |
} |
And if ($this->length)
is less than 10
, return 'Medium'
... lines 1 - 4 | |
class Dinosaur | |
{ | |
... lines 7 - 39 | |
public function getSizeDescription(): string | |
{ | |
if ($this->length >= 10) { | |
return 'Large'; | |
} | |
if ($this->length < 5) { | |
return 'Small'; | |
} | |
if ($this->length < 10) { | |
return 'Medium'; | |
} | |
} | |
} |
Back to our terminal, run the test again:
./vendor/bin/phpunit --testdox
And... alright alright alright... they're passing.
So let's move on to the last step of TDD... and a fun one! Refactoring our code.
Looking at our getSizeDescription()
method, I think we can clean this up a bit. And the great news is that, because we've covered our method with tests, if we mess something up during refactoring, the tests will tell us! We get to be reckless! It also means that we didn't really need to worry about writing perfect code earlier. We just needed to make our tests pass. NOW we can improve things...
Let's change this middle condition to if ($this->length)
is greater than or equal to 5
, return Medium
. We can get rid of this last conditional altogether and just return Small
:
... lines 1 - 4 | |
class Dinosaur | |
{ | |
... lines 7 - 39 | |
public function getSizeDescription(): string | |
{ | |
if ($this->length >= 10) { | |
return 'Large'; | |
} | |
if ($this->length >= 5) { | |
return 'Medium'; | |
} | |
return 'Small'; | |
} | |
} |
I like that! To see if we messed up, move back to the terminal and run our tests again.
./vendor/bin/phpunit --tesdox
And... we've done it! That's TDD - write the test, see the test fail, write simple code to see the test pass, then refactor our code. Rinse and repeat.
TDD Is interesting because, by writing our test first, it forces us to think about exactly how a feature should work... Instead of just blindly writing code and seeing what comes out. It also helps us focus on what we need to code... Without making things too fancy. Yes, I'm guilty of that too... Get your tests to pass, then refactor... Nothing more is needed.
And now that we have our fancy new method - built via the powers of TDD - let's celebrate by using it on the site!
Close up our terminal and move to our template: templates/main/index.html.twig
. Instead of showing the dino's with dino.length
, change this to dino.sizeDescription
. Save it, go back to our browser and... refresh.
... lines 1 - 3 | |
<div class="container volcano mt-4" style="flex-grow: 1;"> | |
... line 5 | |
<div class="dino-stats-container mt-2 p-3"> | |
<table class="table table-striped"> | |
... lines 8 - 15 | |
<tbody> | |
{% for dino in dinos %} | |
<tr> | |
... lines 19 - 20 | |
<td>{{ dino.sizeDescription }}</td> | |
<td>{{ dino.enclosure }}</td> | |
</tr> | |
{% endfor %} | |
</tbody> | |
</table> | |
</div> | |
</div> | |
... lines 29 - 51 |
Awesome. We have large, medium, and small for the dinosaur's size instead of a number. No way Bob will accidentally wander into the T-Rex enclosure again!
We've just used TDD to make our app a bit more human-friendly. Coming up next, we'll use some of the TDD principles we've learned here to clean up our tests with PHPUnit's data providers!
"Houston: no signs of life"
Start the conversation!
// composer.json
{
"require": {
"php": ">=8.1.0",
"ext-ctype": "*",
"ext-iconv": "*",
"symfony/asset": "6.1.*", // v6.1.0
"symfony/console": "6.1.*", // v6.1.4
"symfony/dotenv": "6.1.*", // v6.1.0
"symfony/flex": "^2", // v2.2.3
"symfony/framework-bundle": "6.1.*", // v6.1.4
"symfony/http-client": "6.1.*", // v6.1.4
"symfony/runtime": "6.1.*", // v6.1.3
"symfony/twig-bundle": "6.1.*", // v6.1.1
"symfony/yaml": "6.1.*" // v6.1.4
},
"require-dev": {
"phpunit/phpunit": "^9.5", // 9.5.23
"symfony/browser-kit": "6.1.*", // v6.1.3
"symfony/css-selector": "6.1.*", // v6.1.3
"symfony/phpunit-bridge": "^6.1" // v6.1.3
}
}