Thinking About Testability
I've been thinking a lot about unit tests, lately. In the Bad Old Days I
could var_dump()
my way through a problem and deal with the
consequences, but that isn't good enough any more. Today, I want my
tests to run themselves, and that's forcing me to change the way I
structure my code.
In the past I may have written a user class which was very tightly coupled with a database:
class User {
public function __construct( $id ) {
global $db;
$sql = "SELECT * FROM users WHERE id = ?";
$user = $db->get( $sql, $id );
$this->id = $user->id;
$this->name = $user->name;
}
}
It's easy to identify why this is hard to test: you need a database with predictable data beneath to have any confidence that the code is working as it should. A testable alterative would use Dependency Injection:
class User {
public function __construct( $userstore ) {
$this->userstore = $userstore;
}
public function load( $id ) {
$userdata = $this->userstore->load( $id );
$this->id = $userdata->id;
$this->name = $userdata->name;
}
}
Rather than instantiating a user with new User(12)
, I would instead
say:
$userstore = new UserStore_Database( DB_USER, DB_PASS ); // create db interface
$user = new User( $userstore ); // create user object, connecting to db
$user->load( 12 ); // load user #12
This is way more verbose, but factories can automatic the common use cases:
class User {
// __construct(), load(), plus:
public static function load_by_id( $id ) {
static $userstore = null;
// cache the database interface
if( $userstore === null ) {
$userstore = new UserStore_Database( DB_USER, DB_PASS );
}
$user = new User( $userstore );
$user->load( $id );
return $user;
}
}
Our object is testable, but day-to-day code in production can still use a one-liner:
$user = User::load_by_id( 12 );
When I write my unit tests, I can create a custom UserStore
class that
returns data from an array. TheĀ User::load_by_id()
method is not
testable, but honestly I don't care. I want to test the core
functionality of the user class, and if User
works, then my factory
works.
Most of the information I find is very specific to other languages (e.g. Java), recommending programming constructs that aren't in PHP. My challenge is to apply this to PHP, and not get sidetracked by hard line stances like "never use statics" but rather understand those opinions in context of how they hurt testability.