Alexander Steshenko | On Software Engineering

Which came first, the code or the test?

Testing the untestable

Is code that is impossible or hard to test necessarily bad? Many unit-tests and TDD evangelists would answer “YES”. I am personally not so sure. Consider this code:

class User
{
    protected $email;
    protected $name;
    protected $passwordHash;

    public function __construct($email, $name, $password)
    {
        $this->email = $email;
        $this->name = $name;
        $this->passwordHash = sha1($password);
    }
}

class UserRegistrator
{
    protected $registeredUsers = array();

    public function register($email, $name) 
    {
        $newGeneratedPassword = substr(md5(rand(1, 1000000)), 0, 8);
        $this->registeredUsers[] = new User($email, $name, $newGeneratedPassword);
    }
}

Here we have a UserRegistrator object, that… registers users. For new users it also generates a password of length 8. Now, the question: how do we test this fact? (which we should: all business rules should be tested!

We have a “new User” hardcoded which we cannot unit-test (not easily, anyway). Does it make this code itself bad? I don’t think so - as it does exactly as per requirement. If I ever want to replace the “new User” with something else I want to have to go in this code and write manually what that other thing would be.

Is there any reason except difficultly of testing why this code may be considered “bad” code? Is there any valid use case for which I’d need this dependency not hardcoded? Not really.

Unit-Tests are important

Unit-tests are a “must have”. If you have time and money to build a robust infrastructure covered with unit-tests and functional tests you must try to test every business rule of your domain logic. What I suggest is not to forget that tests are “helpers” and should only influence your code when there is no other choice.

Whether there is choice depends on many things. PHP developers are not particularly in luck - in Ruby, for instance, that UserRegistrator example could be tested easily. Therefore, one could say that it’s not the code that is bad, it’s the language features and testing utilities that are not good enough.

Choices are:

  1. Never create anything. Never use new in your business logic. Only do that in factories/service locators/dependency injection containers. This is the most popular one, however adding lots of headache.
  2. Use special PHP extension that lets you override all “new” calls. http://sebastian-bergmann.de/archives/885-Stubbing-Hard-Coded-Dependencies.html

For BasicCRM application I’m going to go with the second option.

class MockUser 
{
    public function __construct($name, $email, $password) 
    {
        PHPUnit_Framework_Assert::assertEquals('John', $name);
        PHPUnit_Framework_Assert::assertEquals('john@example.com', $email);
        PHPUnit_Framework_Assert::assertEquals(8, strlen($password));
    } 
}
class UserReigstratorTest extends PHPUnit_Framework_TestCase
{

    public function testCreatesNewUserWithNameAndEmailProvided()
    {
        $userRegistrator = new UserRegistrator();
        set_new_overload(function($classname) {
                return $classname == 'User' ? 'MockUser': $classname;
        });
        $userRegistrator->register('John', 'john@example.com');
        unset_new_overload();
    }
}

More on testing can be found in this post.


Comments and feedback are much appreciated. See contact details here