Created: Mon, 22 Dec 2014 00:00:00 GMT
Time to read: 9 minutes
Kata are a series of movements which, when repeated, migrate mindful action into muscle memory. An example kata is performed by Rika Usami of Japan during the 2012 World Karate Championship in Paris.
Kata can be thought of in terms of playing an instrument or a craft. During the warmup before a concert, listen to the musicians. They are playing their scales and odd passages, they are practicing their kata to get their brains and fingers in the right place to pull off something difficult and awesome.
Kata, like scales, are temporary in nature. They are to be practiced, then discarded, then practiced again.
Programming also requires practice; kata are a great way to formalize that practice.
Test-Driven Development (aka "TDD") is the practice of writing a test before any other code is written. Write a test that fails, then write the minimum of code to pass that test, then write another test that fails, etc. When there are no more tests to write, then the program is complete. In theory.
The blessing of TDD is that each test is like a public contract: when X is applied, Y shall happen, but not Z. If a change is made sometime later and the tests still pass, then relief can be sighed; otherwise, the failed tests right away point out that the recent change was not appropriate.
The curse of TDD is that it's hard, it takes too much time, quite a few of the tests are rendered useless over time, and if you start out a project in TDD, changing direction is rather difficult. DHH lobbed an anti-TDD bomb in April 2014 with "TDD is dead. Long live testing."
These are all valid points, but as Martin Fowler and Kent Beck also said in Is TDD Dead? [Part V & VI], TDD forces one to approach a hard problem one small step at a time; once the hugely difficult problem is reduced to smaller, easier to understand problems, the difficult bits are easier to approach and figure out. In the same video above, even DHH says that TDD is the gateway drug to better practices. Similarly, I have read elsewhere and have heard from several mentors that TDD is also an important step to take in learning good programming skills. For instance, learning TDD also forces one to learn and practice SOLID principals.
I will admit that at work, our codebase is not test-driven. Yet, we do utilize unit tests for core features and try to ensure 100% code coverage for those tests. This helps us feel better that changes we make to the core code will not break the code (or at least not as much) as compared to anxiety we would fee if we did not have tests in place.
Given that Kata help drive difficult movement into muscle memory and that TDD is beneficial to learning good programming practices, I try to incorporate TDD Kata into my weekly routine.
I use TDD Kata to help me internalize new skills. My latest TDD kata have helped
me learn to use Composer, PSR-4, PHPUnit, and basic
Some interesting TDD Kata to attempt can be found at the Cyber Dojo.
When starting a new PHP project, I like to start out with a fresh composer.json file. Composer lets me easily add dependencies and utilize namespace autoloading in that project. If the project uses PHPUnit, the PHPUnit dependencies get installed via Composer. (note: If you don't already have Composer installed, download the latest version and install it from the official Composer site.)
For the purposes of this TDD Kata, we will be using PHPUnit. Therefore, make sure you add at least the require-dev
bits to your composer.json file.
composer.json
:
{
"name": "ericpoe/fizzbuzz",
"description": "A Quick TDD FizzBuzz",
"license": "MIT",
"authors": [
{
"name": "Eric Poe",
"email": "eric@ericpoe.com"
}
],
"require": {},
"require-dev": {
"phpunit/phpunit": "~4.4"
}
}
Install via composer install
. You should now have a "vendors" directory in your working directory.
Then we create the test skeleton.
FizzBuzzTest.php
:
<?php
include ('vendor/autoload.php');
class FizzBuzzTest extends PHPUnit_Framework_TestCase
{
}
Each test we create is a promise we are making with the program we are building. Inside of each test is an assertion that verifies that the promise is steadfast and true.
Since we are testing a class, we will need to instantiate that class, even if the class doesn't exist yet. We can derive implementation details from this test.
FizzBuzzTest.php
<?php
include ('vendor/autoload.php');
class FizzBuzzTest extends PHPUnit_Framework_TestCase
{
public function test3sAreFizz()
{
$fb = new FizzBuzz();
$this->assertEquals('Fizz', $fb->analyze(3));
}
}
When we run this test, we should expect it to fail.
php vendor/phpunit/phpunit/phpunit FizzBuzzTest.php
returns a big scary error:
PHPUnit 4.4.1 by Sebastian Bergmann.
PHP Fatal error: Class 'FizzBuzz' not found in /Users/eric/dev/temp/fizzbuzz/FizzBuzzTest.php on line 8
PHP Stack trace:
PHP 1. {main}() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/phpunit:0
PHP 2. PHPUnit_TextUI_Command::main() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/phpunit:62
PHP 3. PHPUnit_TextUI_Command->run() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/TextUI/Command.php:138
PHP 4. PHPUnit_TextUI_TestRunner->doRun() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/TextUI/Command.php:186
PHP 5. PHPUnit_Framework_TestSuite->run() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:423
PHP 6. PHPUnit_Framework_TestCase->run() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestSuite.php:751
PHP 7. PHPUnit_Framework_TestResult->run() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestCase.php:722
PHP 8. PHPUnit_Framework_TestCase->runBare() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestResult.php:643
PHP 9. PHPUnit_Framework_TestCase->runTest() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestCase.php:766
PHP 10. ReflectionMethod->invokeArgs() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestCase.php:881
PHP 11. FizzBuzzTest->test3sAreFizz() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestCase.php:881
Fatal error: Class 'FizzBuzz' not found in /Users/eric/dev/temp/fizzbuzz/FizzBuzzTest.php on line 8
Call Stack:
0.0002 230768 1. {main}() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/phpunit:0
0.0023 559256 2. PHPUnit_TextUI_Command::main() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/phpunit:62
0.0023 559880 3. PHPUnit_TextUI_Command->run() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/TextUI/Command.php:138
0.0265 2104448 4. PHPUnit_TextUI_TestRunner->doRun() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/TextUI/Command.php:186
0.0324 2437144 5. PHPUnit_Framework_TestSuite->run() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:423
0.0341 2454376 6. PHPUnit_Framework_TestCase->run() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestSuite.php:751
0.0341 2455920 7. PHPUnit_Framework_TestResult->run() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestCase.php:722
0.0388 2536744 8. PHPUnit_Framework_TestCase->runBare() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestResult.php:643
0.0416 2648608 9. PHPUnit_Framework_TestCase->runTest() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestCase.php:766
0.0416 2649368 10. ReflectionMethod->invokeArgs() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestCase.php:881
0.0416 2649456 11. FizzBuzzTest->test3sAreFizz() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestCase.php:881
The important part of that error message is the error line before the huge stack trace: PHP Fatal error: Class 'FizzBuzz' not found in /Users/eric/dev/temp/fizzbuzz/FizzBuzzTest.php on line 8
In other words, we need to create the 'FizzBuzz' class. We will create just enough of the 'FizzBuzz' class to pass this test.
FizzBuzz.php
:
<?php
class FizzBuzz
{
}
Then we tell 'FizzBuzzTest.php' where to find this class.
FizzBuzzTest.php
:
<?php
include ('vendor/autoload.php');
include ('FizzBuzz.php'); // The new line
class FizzBuzzTest extends PHPUnit_Framework_TestCase
{
public function test3sAreFizz()
{
$fb = new FizzBuzz();
$this->assertEquals('Fizz', $fb->analyze(3));
}
}
And run the test again. php vendor/phpunit/phpunit/phpunit FizzBuzzTest.php
Another big, scary, error message. This time, it's a little different.
PHPUnit 4.4.1 by Sebastian Bergmann.
PHP Fatal error: Call to undefined method FizzBuzz::analyze() in /Users/eric/dev/temp/fizzbuzz/FizzBuzzTest.php on line 10
PHP Stack trace:
PHP 1. {main}() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/phpunit:0
PHP 2. PHPUnit_TextUI_Command::main() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/phpunit:62
PHP 3. PHPUnit_TextUI_Command->run() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/TextUI/Command.php:138
PHP 4. PHPUnit_TextUI_TestRunner->doRun() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/TextUI/Command.php:186
PHP 5. PHPUnit_Framework_TestSuite->run() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:423
PHP 6. PHPUnit_Framework_TestCase->run() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestSuite.php:751
PHP 7. PHPUnit_Framework_TestResult->run() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestCase.php:722
PHP 8. PHPUnit_Framework_TestCase->runBare() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestResult.php:643
PHP 9. PHPUnit_Framework_TestCase->runTest() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestCase.php:766
PHP 10. ReflectionMethod->invokeArgs() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestCase.php:881
PHP 11. FizzBuzzTest->test3sAreFizz() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestCase.php:881
Fatal error: Call to undefined method FizzBuzz::analyze() in /Users/eric/dev/temp/fizzbuzz/FizzBuzzTest.php on line 10
Call Stack:
0.0002 230768 1. {main}() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/phpunit:0
0.0026 559256 2. PHPUnit_TextUI_Command::main() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/phpunit:62
0.0026 559880 3. PHPUnit_TextUI_Command->run() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/TextUI/Command.php:138
0.0110 2105472 4. PHPUnit_TextUI_TestRunner->doRun() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/TextUI/Command.php:186
0.0127 2438208 5. PHPUnit_Framework_TestSuite->run() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:423
0.0137 2455936 6. PHPUnit_Framework_TestCase->run() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestSuite.php:751
0.0137 2457480 7. PHPUnit_Framework_TestResult->run() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestCase.php:722
0.0145 2537800 8. PHPUnit_Framework_TestCase->runBare() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestResult.php:643
0.0152 2649632 9. PHPUnit_Framework_TestCase->runTest() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestCase.php:766
0.0152 2650392 10. ReflectionMethod->invokeArgs() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestCase.php:881
0.0152 2650480 11. FizzBuzzTest->test3sAreFizz() /Users/eric/dev/temp/fizzbuzz/vendor/phpunit/phpunit/src/Framework/TestCase.php:881
Once again, the important line is before all of the stack traces. PHP Fatal error: Call to undefined method FizzBuzz::analyze() in /Users/eric/dev/temp/fizzbuzz/FizzBuzzTest.php on line 10
In other words, our new 'FizzBuzz' class doesn't have an 'analyze' method.
Let's add just enough to the 'FizzBuzz' class to make it pass this test.
FizzBuzz.php
:
<?php
class FizzBuzz
{
/* new lines */
public function analyze()
{
}
}
And run the test again. php vendor/phpunit/phpunit/phpunit FizzBuzzTest.php
This time, our output is not a bunch of scary error messages, just a short message to say that a test failed.
PHPUnit 4.4.1 by Sebastian Bergmann.
F
Time: 18 ms, Memory: 3.25Mb
There was 1 failure:
1) FizzBuzzTest::test3sAreFizz
Failed asserting that null matches expected 'Fizz'.
/Users/eric/dev/temp/fizzbuzz/FizzBuzzTest.php:10
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
The failing test was in testing that 3s are Fizz. Whatever was returned from 'FizzBuzz' was a null, not a 'Fizz' string.
That's an easy enough problem to resolve. Again, we just edit enough of 'FizzBuzz' to pass the tests.
FizzBuzz.php
:
<?php
class FizzBuzz
{
public function analyze()
{
return 'Fizz'; // new line
}
}
Run our test: php vendor/phpunit/phpunit/phpunit FizzBuzzTest.php
PHPUnit 4.4.1 by Sebastian Bergmann.
.
Time: 19 ms, Memory: 3.00Mb
OK (1 test, 1 assertion)
And we pass!
This will continue later...
This site is built using Gatsby, TailwindCSS, and a whole bunch of other fun stuff.
Corrections or curious to see how this was put together? Check out the latest version of this site at its github repo.