BEGIN-time Initialization versus Testing

| 1 Comment

Today I came across some undertested code, and Allison and I had a short discussion about the best way to approach it.

If you've read my You're Already Using Dependency Injection, you may approach the problem of testing from the mindset that looks for dependencies—declared and undeclared—and tries to manage them. That's essentially the design goal of automated testing, or at least test-driven development. We want to produce a single system with sufficient decoupling that we can prove small, unique, and isolated assertions about the behavior of our code.

Sometimes Perl makes that easy for us. Sometimes it doesn't.

Good programmers use Perl modules to encapsulate discrete units of behavior. Yet if we're not careful, we can limit our options. Consider this code:

package Project::DB::Connection;

use strict;
use warnings;

use DBI;
use Project::Config;

my $dbh = DBI->connect( Project::Config->get_config( 'database' ) );

...

The desire to make $dbh a singleton is understandable, as is the desire to encapsulate it as a file-scoped lexical. (Assume there's a get_dbh() function exported or available.) Those are very likely advantages.

You can't immediately see the disadvantages, however.

If something else in your system uses this module, Perl puts an implicit BEGIN around all of the code. That means before the use statement has finished executing, this code will already have run. $dbh will be connected.

As well, this code has a dependency on the configuration module. Presumably that gets loaded too, and it may itself run code.

If you need to override any part of the database connection (to use a separate database for testing, if you don't necessarily have access to the correct database on a testing machine, or for whatever reason), you have to hijack something and you have to make that happen before this code runs.

Deciding out what to hijack (do you mock the DBI? override part of the configuration? override get_dbh() and hope the initial connection works well enough in your testing environment?) is sometimes easier than figuring out what loads what else and when so that you can get the implicit order of loading correct. Perhaps even worse, you have to write really clever code to get Perl to do the right things in the right ways in the proper order. (Perl lets you do this, but just because you can do something doesn't make it a good idea.)

Compare that to the dependency management of lazy object attributes as exemplified by Moose.

Again, this may not be important to you until you want your test suite to run in an environment not exactly like the one you're using at the moment. (Shortcuts have a way of coming back to haunt you.) Even if you're perfectly happy in a simple environment you can't reproduce with the push of a button, the day may come when you want to know if your code works anywhere but your development machine.

That's when the degree to which you managed your implicit dependencies will either help or hinder you.

(That's why I make a habit of not running any code in implicit BEGIN blocks; it's cost me rework too many times.)

1 Comment

You showed the problem in a very clear way. Could you also provide a code snippet for a solution (db handle singleton, dependency to many configuration items) which seems "better" in your opinion?

Best regards
McA

Modern Perl: The Book

cover image for Modern Perl: the book

The best Perl Programmers read Modern Perl: The Book.

affiliated with ModernPerl.net

Categories

Pages

About this Entry

This page contains a single entry by chromatic published on October 8, 2012 5:32 PM.

Solitude is a Drawback of Perl Productivity was the previous entry in this blog.

Emulating Dynamic Scope with Lexical Destruction is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.


Sponsored by Blender Recipe Reviews and the Trendshare how to invest guide

Powered by the Perl programming language

what is programming?