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.
Hey Jose carlos C.
TDD is a technique to develop software. It helps with refactoring because it covers your back. After a refactoring you can run your tests and see if nothing broke. The essence of TDD is to be able to guarantee the behavior of your code, and to develop code that's easier to maintain, change and test.
I strongly recommend you to practice TDD all the time you can, the more you practice the more you will convince yourself about the benefits of it
Cheers!
I am getting this error:
ArgumentCountError: Too few arguments to function App\Entity\Dinosaur::__construct(), 0 passed in /Users/sumeet.gupta/Desktop/Symfony/my_project_name/tests/Entity/DinosaurTest.php on line 20 and exactly 2 expected
Hey Sumeet,
Looks like you forgot to pass a few required arguments to the Dinosaur entity when create it. Please, check your DinosaurTest.php on line 20.
Cheers!
// 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
}
}
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.