The Fearless and Natural Approach to TDD
Eventually, the penny dropped and everything made sense. The results were immediate. The gain was imminent, from the first project up till now. I reduced the number of post-release bugs to almost zero in most of the applications and I reduced the actual working time by around 40%.
I’ve been applying TDD (Test Driven Development) religiously for the past 5 years and now cannot imagine where I would even start from, in any code if it’s not from a test. The advantages TDD brought along with it are numerous and in my case, vital, for working on projects of a certain calibre. Even though the complexity of any code is now irrelevant to me when it comes to TDD as I always apply it. In a form or another, every code I write follows a test I’ve written.
This post is not a tutorial of either Unit Testing nor PhpUnit. The main purpose is to help you adjust your mindset in a way that facilitates TDD. I will do this by highlighting the issues I experienced myself when thinking tests firsts and then I will continue on this series of posts -Developing a Command Bus in PHP with developing some initial features using the TDD approach, to hopefully walk you through how the mindset evolves for TDD to become a natural and obvious way to drive code development.
What is Test Driven Development? A shift in Mindset
Sounds simple right? Well, for me, it wasn’t, at least not at first. I couldn’t make this work in my head. I’ve been exposed to TDD well before the last five years but in all honesty, it always seemed like too much work to learn a testing framework and to install this and that. The reality was that these fears, stopping me from implementing a TDD approach to my work were the easiest to conquer. Learning how to use a new framework such as PHPUnit is extremely easy for most developers. We learn new frameworks every year, not to mention new languages and standards. I was in a point in my career where I was being given opportunities to work for large-scale companies on large-scale projects. I had to make TDD work because the cost of not having automated testing was way too high. I remember this moment in detail because it was, this, a milestone in my software development confidence. I spent all my free time for a week, just writing tests for dummy problems. I learned PHPUnit and I could write tests. But the penny hadn’t dropped yet. I still couldn’t think what to test for and thus how.
The issue I had with the TDD mindset was not with the tests directly but more in breaking down features into adequate units of work. I say “adequate” because it did take me a few attempts at establishing an informal policy of what makes a unit. Units are very easy to test because normally (in SOLID code), they correspond to one method or one public method and a subset of helper internal ones, clear and concise.
Eventually, the penny dropped and everything made sense. The results were immediate. The gain was imminent, from the first project up till now. I reduced the number of post-release bugs to almost zero in most of the applications and I reduced the actual working time by around 40%. This means I gain better opportunities to work on projects with smaller budgets without affecting the quality of the code.
When we think about what something should do, given it is independent of other “somethings”, we can easily establish what to test for by checking if that thing was done. We can also, as easily test what it shouldn’t do.
What is Unit Testing in TDD?
Think about the required feature as to what is the expected result. Then break it down into doable things (units of work) and then test for each thing (unit) one by one. Rinse and repeat.
Remember that there are other types of tests such as Integration Tests which may be formed of several units of work, however, I would say that in this case, the Unit is the actual Integration under test. This may be debatable, so I’ll look forward to your comments 😊
TDD in PHP
Once again, It's important to note that TDD does not necessarily imply unit testing. TDD can be applied to several layers of an application; units, functional, features, behaviours and integrations. It also does not have to be automated, although it does help :)
The most popular testing framework in PHP is PhpUnit. I don't think this statement is debatable but do let me know in the comments below if you disagree. I will not use this article as a PHPUnit tutorial because there's plenty of them out there, one in particular that I enjoyed a while ago is this one. Not to mention that Sebastian Bergmann himself did a very good job in documenting the framework all over the world wide web and offline.
Instead, I'll continue expanding on our Command Bus composer package example from the previous posts to illustrate this new natural and most probably obvious mindset. The same way I learned to TDD every day.
TDD example with PHPUnit
In the previous post PHP Software Development Workflow of this series, we've set up the project and tools. We also already added the PHPUnit dependency to our composer.json file. This goes to show my mindset :) In this post, I'm not going to go into any detail about setting PHPUnit up or even how to use assertions and mocks etc... I simply want us to take a couple of features we need to develop and apply a TDD approach to these requirements. However, as always, please do get in touch whenever you wish for me to elaborate on any detail. I really enjoy answering your questions and it's becoming a daily habit now :)
Let's get to it, shall we
Our Command Bus component needs a
Resolver
class to resolve the CommandHandler
of the Command
and resolve the dependencies a Handler may have such as a Repository or a Specification object. Initially, a resolver will do all this from a provided array, however, we may wish to automatically resolve dependencies when we have an Application Container such as when we use frameworks like Symfony or Laravel. Therefore, for now, I'll start with specifying an interface for a generic Resolver like so:<?php declare(strict_types=1);
namespace SwellPhp\Admiral;
/**
* A contract for command handler resolvers.
*
*/
interface CommandHandlerResolver
{
/**
* Gets the command handler object for the command.
*
* @param object $command
* @return CommandHandler
*/
public function getHandler($command): CommandHandler;
}
The above interface will be implemented by any concrete
CommandHandlerResolver
like our one today, the ArrayResolver
or in the future, a LaravelResolver
. In order to define or establish our units of work we must start with listing the desired functionality, which may become our units of work or a group thereof. What I'd like our Resolver to do is to take an array of Commands and their Handlers. If the Handler has any dependencies, then we pass an array of instantiated dependencies. In the future, other resolver implementations may also resolve bounded interfaces etc.. but for now, we'll just focus on the concrete ArrayResolver.The first requirement is the ability to retrieve a CommandHandler for a given Command. I'll start by writing an extensible
TestCase
to avoid having to bootstrap the dependencies in every test:<?php declare(strict_types=1);
namespace SwellPhp\Admiral\Test;
use SwellPhp\Admiral\ArrayResolver;
use SwellPhp\Admiral\CommandHandlerResolver;
/**
* An extensible test case.
*/
abstract class TestCase extends \PHPUnit\Framework\TestCase
{
/**
* @var array
*/
protected $handlers = [];
/**
* @var CommandHandlerResolver
*/
protected $resolver;
/**
* Sets up the test.
*
*/
public function setUp()
{
$this->handlers = include __DIR__ . '/command_handlers.php';
$this->resolver = new ArrayResolver($this->handlers);
}
}
and then our first test for getting the actual Handler:
namespace SwellPhp\Admiral\Test;
use SwellPhp\Admiral\ArrayResolver;
...
/**
* Tests for the array resolver.
*
*/
class ArrayResolverTest extends TestCase
{
/**
* Tests that it can get the command handler for a given command.
*
* @test
*/
public function it_can_get_command_handler_of_command()
{
$handler = $this->resolver->getHandler(
new DraftNewBlogPost('title', 'content')
);
$this->assertInstanceOf(
\SwellPhp\Admiral\Example\Handler\DraftNewBlogPost::class,
$handler
);
}
}
Needless to say, this test will fail because we don't have any code written, so I'll go ahead and write the code which will make this test pass, hopefully, but if not, we know exactly which part of our software needs to be worked on. The elegance of knowing 😉
<?php declare(strict_types=1);
namespace SwellPhp\Admiral;
/**
* Resolves the command handlers and their dependencies
* from an array.
*
*/
final class ArrayResolver implements CommandHandlerResolver
{
/**
* @var array
*/
protected $handlers = [];
/**
* @var array
*/
protected $handlersDependencies = [];
/**
* ArrayResolver constructor.
*
* @param array $handlers
*/
public function __construct(array $handlers)
{
$this->setHandlers($handlers);
}
/**
* Gets the handler.
*
* @param object $command
* @return CommandHandler
*/
public function getHandler($command): CommandHandler
{
$handler = $this->handlers[get_class($command)];
$handler = new $handler();
return $handler;
}
}
Well that was easy, wasn't it? Next, let's think about what can go wrong with the
getHandler()
method. Okay, so, what if the Command was not added to the array and thus is not registered. We'll develop an exception class CommandNotRegistered
and write a test which expects this exception to be thrown. I developed the exception class first so that I could reference it in the test. This is simply an Exception class:<?php declare(strict_types=1);
namespace SwellPhp\Admiral\Exception;
/**
* Exception for unregistered command.
*
*/
final class CommandNotRegistered extends \Exception
{
}
Then we use the
@expectedException
annotation in our test: /**
* Tests that it throws an exception when attempting
* to retrieve a handler for an unregistered command.
*
* @test
* @expectedException \SwellPhp\Admiral\Exception\CommandNotRegistered
*/
public function it_throws_exception_if_command_is_not_registered()
{
$handlerForNonExistingCommand = $this->resolver->getHandler(
new NotRegisteredCommand()
);
}
Next we make the test pass by writing code that throws an exception when the Command is not registered in our
ArrayResolver
/**
* Asserts the command is registered.
*
* @param string $command
* @throws CommandNotRegistered
*/
protected function assertCommandIsRegistered(string $command): void
{
if (!array_key_exists($command, $this->handlers)) {
throw new CommandNotRegistered($command);
}
}
and change our
getHandler()
method to assert this condition is met.
/**
* Gets the handler.
*
* @param object $command
* @return CommandHandler
*/
public function getHandler($command): CommandHandler
{
$this->assertCommandIsRegistered(get_class($command));
$handler = $this->handlers[get_class($command)];
$handler = new $handler();
return $handler;
}
I'd like to point out that there are several other ways to achieve the same results in PHPUnit, I opted to use dummy classes for testing because they are better in explaining the process and even more important, they expand on this mindset through clear expectations.
The point here is to first break down the task into small and well-understood units of work, then write a test for the desired result and write the code to make the test pass. Following this, think about what can go wrong, what are the exceptions (exceptions in desired outcome or expected input) and test against them. Please also note that these scenarios are not specifically tied to exceptions. They can be varying business rules and specifications among other conditions.
To keep this post from becoming a lot longer than it already is, I'll briefly illustrate another requirement, resolving the
CommandHandler
dependencies, but please note that even in this small example, I have actually tested for other scenarios, such as when CommandHandler
class is not found.The test for our new feature:
/**
* Test that it can resolve command handler's dependency.
*
* @test
*/
public function it_resolve_handler_dependencies_from_instance_of_it()
{
$resolver = new ArrayResolver([
SomeCommand::class => [
SomeCommandWithSingleDependency::class,
[
new ListOfPosts()
]
]
]);
$handler = $resolver->getHandler(new SomeCommand());
$this->assertInstanceOf(
CommandHandler::class,
$handler
);
$this->assertInstanceOf(
ListOfPosts::class,
$handler->getListOfPosts()
);
}
Two main points here, one, you'll notice that I've actually really created a class
ListOfPost
. In practice, you can achieve this by using Mocks or even anonymous classes (as of PHP7) but I need these classes to exist for illustration purposes on here and also for future use in the documentation (2 birds one stone! I love animals and would never throw a stone to one unless it would save my life by doing so - even if I can hit two at the same time 😊 ). The second point is that in reality, we need to be able to resolve dependencies even when there are multiple ones. The Resolver does this and the code is included in the repository (linked below), however, while I was testing and developing along, I had some issues, so I broke this down to two more Units, one to resolve the dependency of a `CommandHandler requiring only a single dependency and then, once all my errors were resolved, I could move on to resolving multiple dependencies with ease. You'll find yourself doing this very often as it aids debugging and understanding the cause of an issue.The final
ArrayResolverTest
class:namespace SwellPhp\Admiral\Test;
use SwellPhp\Admiral\ArrayResolver;
use SwellPhp\Admiral\CommandHandler;
use SwellPhp\Admiral\Example\Command\SomeCommand;
use SwellPhp\Admiral\Exception\CommandNotRegistered;
use SwellPhp\Admiral\Exception\CommandHandlerNotFound;
use SwellPhp\Admiral\Example\Command\DraftNewBlogPost;
use SwellPhp\Admiral\Example\Command\NotRegisteredCommand;
use SwellPhp\Admiral\Example\Command\CommandWithoutHandler;
use SwellPhp\Admiral\Example\Handler\Dependency\ListOfPosts;
use SwellPhp\Admiral\Example\Handler\SomeCommandWithSingleDependency;
use SwellPhp\Admiral\Example\Handler\SomeCommandWithMultipleDependencies;
/**
* Tests for the array resolver.
*
*/
class ArrayResolverTest extends TestCase
{
/**
* Tests that it can get the command handler for a given command.
*
* @test
*/
public function it_can_get_command_handler_of_command()
{
$handler = $this->resolver->getHandler(
new DraftNewBlogPost('title', 'content')
);
$this->assertInstanceOf(
\SwellPhp\Admiral\Example\Handler\DraftNewBlogPost::class,
$handler
);
}
/**
* Tests that an exception is thrown when a handler is not found.
*
* @test
* @expectedException \SwellPhp\Admiral\Exception\CommandHandlerNotFound
*/
public function it_throws_exception_when_handler_is_not_found()
{
$handler = $this->resolver->getHandler(
new CommandWithoutHandler()
);
}
/**
* Tests that an exception is thrown when a command handler
* does not implement to the handler contract.
*
* @test
* @expectedException \TypeError
*/
public function it_throws_exception_if_handler_does_not_implement_contract()
{
$handler = $this->resolver->getHandler(
new SomeCommand()
);
}
/**
* Tests that it throws an exception when attempting
* to retrieve a handler for an unregistered command.
*
* @test
* @expectedException \SwellPhp\Admiral\Exception\CommandNotRegistered
*/
public function it_throws_exception_if_command_is_not_registered()
{
$handlerForNonExistingCommand = $this->resolver->getHandler(
new NotRegisteredCommand()
);
}
/**
* Test that it can resolve command handler's dependency.
*
* @test
*/
public function it_resolve_handler_dependencies_from_instance_of_it()
{
$resolver = new ArrayResolver([
SomeCommand::class => [
SomeCommandWithSingleDependency::class,
[
new ListOfPosts()
]
]
]);
$handler = $resolver->getHandler(new SomeCommand());
$this->assertInstanceOf(
CommandHandler::class,
$handler
);
$this->assertInstanceOf(
ListOfPosts::class,
$handler->getListOfPosts()
);
}
/**
* Tests that it can resolve resolve multiple command handler dependencies.
*
* @test
*/
public function it_resolves_multiple_dependencies()
{
$resolver = new ArrayResolver([
SomeCommand::class => [
SomeCommandWithMultipleDependencies::class,
[
new ListOfPosts(),
'string',
100
]
]
]);
$handler = $resolver->getHandler(
new SomeCommand()
);
$this->assertInstanceOf(
CommandHandler::class,
$handler
);
$this->assertInstanceOf(
ListOfPosts::class,
$handler->getPostList()
);
$this->assertEquals(
'string',
$handler->getStringDependency()
);
$this->assertEquals(
100,
$handler->getNumericDependency()
);
}
}
and the actual
ArrayResolver
:<?php declare(strict_types=1);
namespace SwellPhp\Admiral;
use SwellPhp\Admiral\Exception\CommandNotRegistered;
use SwellPhp\Admiral\Exception\CommandHandlerNotFound;
/**
* Resolves the command handlers and their dependencies
* from an array.
*
*/
final class ArrayResolver implements CommandHandlerResolver
{
/**
* @var array
*/
protected $handlers = [];
/**
* @var array
*/
protected $handlersDependencies = [];
/**
* ArrayResolver constructor.
*
* @param array $handlers
*/
public function __construct(array $handlers)
{
$this->setHandlers($handlers);
}
/**
* Gets the handler.
*
* @param object $command
* @return CommandHandler
*/
public function getHandler($command): CommandHandler
{
$this->assertCommandIsRegistered(get_class($command));
$handler = $this->handlers[get_class($command)];
$this->assertCommandHandlerExists($handler);
$dependencies = [];
if ($this->hasDependencies($handler)) {
$dependencies = $this->resolveDependencies($handler);
$handler = new $handler(...$dependencies);
}
$handler = new $handler(...$dependencies);
return $handler;
}
/**
* Resolves the dependencies.
*
* @param string $commandHandler
* @return array
*/
protected function resolveDependencies(
string $commandHandler
): array {
$dependencies = [];
foreach (
$this->handlersDependencies[$commandHandler] as $handlerDependencies
) {
$dependencies[] = $handlerDependencies;
}
return $dependencies;
}
/**
* Checks if a handler has dependencies.
*
* @param string $commandHandler
* @return bool
*/
protected function hasDependencies(string $commandHandler) : bool
{
return array_key_exists($commandHandler, $this->handlersDependencies);
}
/**
* Asserts the command is registered.
*
* @param string $command
* @throws CommandNotRegistered
*/
protected function assertCommandIsRegistered(string $command): void
{
if (!array_key_exists($command, $this->handlers)) {
throw new CommandNotRegistered($command);
}
}
/**
* Asserts that the command handler exists.
*
* @param string $commandHandler
* @throws CommandHandlerNotFound
*/
protected function assertCommandHandlerExists(string $commandHandler) : void
{
if (! class_exists($commandHandler)) {
throw new CommandHandlerNotFound($commandHandler);
}
}
/**
* Sets the Handlers.
*
* @param array $handlers
*/
protected function setHandlers(array $handlers)
{
$registeredHandlers = [];
foreach ($handlers as $command => $handler) {
if (! is_array($handler)) {
$registeredHandlers[$command] = $handler;
} else {
$registeredHandlers[$command] = $handler[0];
$this->setHandlersDependencies($handler[0], $handler[1]);
}
}
$this->handlers = $registeredHandlers;
}
/**
* Sets the HandlersDependencies.
*
* @param string $commandHandler
* @param array $dependencies
*/
protected function setHandlersDependencies(
string $commandHandler,
array $dependencies
) {
$handlerDependencies = [];
foreach ($dependencies as $dependency) {
$handlerDependencies[] = $dependency;
}
$this->handlersDependencies[$commandHandler] = $handlerDependencies;
}
}
You can find all of this code and the related features on GitHub.com.
With the
Resolver
feature of our Command Bus component completed, we have seven tests and eleven assertions passing as shown in the screenshot below. I also included the broken down tasks I followed from start to finish of this feature to extend on the previous post about my PHP Software Development Workflow.TDD is an obvious choice
I hope that this article persuades all non-testers to convert to TDD and existing testers to challenge how my mind works and thinks. Test Driven Development is easy once we change our mindset. Unfortunately, there are still several companies which as much as I used to, still think that TDD is costly and not as important as its made out to be. This is definitely wrong in web application development and most other applications of software. The learning time-frame, when approached correctly, is only of one or two weeks and the benefits are immediate. Now go and tell your boss to read this article, the two weeks you'll spend learning TDD will be gained within the same project you're learning along. You will save more time straight away and if for any reason, you get stuck, then simply get in touch. I'll be happy to help.