Abstraction versus Mock Objects

One of my projects switched persistence mechanisms from KiokuDB to DBIx::Class recently. KiokuDB had the benefit of simplicity—it was very easy to develop the initial project without worrying about managing table schemas and upgrades, but as the project matured, the benefits of DBIC became more apparent.

I like how KiokuDB persists plain old Moose objects. If you understand how your object graph works, there's little effective difference between a Moose object you create yourself or one you retrieve from your object store. Less so DBIC objects, in my experience. This isn't a flaw. It's merely a difference.

The difference became apparent when I started to port a fundamental test file as part of this migration. This test file exercises the document parser. As part of the application, the document parser uses a variant of the Readability algorithm to extract the meaningful portions of HTML documents. The test file itself uses a data-driven approach, where a t/files/documents/ directory holds multiple YAML files containing real data we've encountered along with the expected (correct) results.

#! perl

use Modern::Perl;
use Test::More;
use YAML::XS;

use lib 't/lib';

__PACKAGE__->main( 'App::DocParser', @ARGV );

sub create_docparser
{
    return App::DocParser->new(
        url       => 'http://www.example.com/some_url',
        url_base  => 'http://www.example.com/',
        @_,
    );
}

sub main
{
    my ($test, $module, @files) = @_;
    use_ok( $module ) or exit;

    if (@files)
    {
        $test->run_file_content_tests( $module, @files );
    }
    else
    {
        $test->test_find_content_files( $module );
    }

    done_testing();
}

sub test_find_content_files
{
    run_file_content_tests( @_, glob 't/files/documents/*.yaml' );
}

sub run_file_content_tests
{
    my ($test, $module, @files) = @_;

    for my $file (@files)
    {
        my $example   = YAML::XS::LoadFile( $file );
        my $docparser = create_docparser( %$example );

        like $docparser->content, $example->{content_regex},
        "DocParser should find matching content for $example->{desc}";

        is 0 + @{ $docparser->links }, $example->{link_count},
            '... with the right number of links';
    }
}

The previous version of this code created App::Document objects—instances of the same class persisted into the object graph with KiokuDB—and called functions in the DocParser namespace to find various pieces of context. A straightforward port of this test file to DBIC meant I'd have had to figure out how to instantiate dummy DBIC-alike objects representing documents to pass to the DocParser functions.

I thought about that for a few minutes. I pondered reaching for Test::MockObject::Extends. Then I didn't.

What's important in this test isn't that App::DocParser conforms to a specific interface governed by the persistence layer. That's irrelevant. Its only relevance is how well it identifies and extracts relevant information from given data.

I turned that namespace full of utility functions into a class. I gave that class the specific attributes on which it needs to operate, effectively giving instances of the class the responsibility to manage the data on which they operate. You can see the result: a simple constructor call creates a DocParser object without worrying about setting up test data in an example database or building a mock object framework which behaves sufficiently like a DBIC row object.

I really like the object responsibility pattern which Moose seems to encourage, where you create an object by passing all relevant data to its constructor and then operate on that data as needed (perhaps governed by laziness in attribute accessors). You get the benefit of being able to assume that a constructed object is in a safe and sane state as well as a decoupling of data dependencies.

Once I realized that this approach would lead to better code, it took twenty minutes to Moosify the document parser and one minute to change a couple of lines of code to port the test to the new framework. I count that as a sign of effective design.

Modern Perl: The Book

cover image for Modern Perl: the book

The best Perl Programmers read Modern Perl: The Book.

sponsored by the How to Make a Smoothie guide

Categories

Pages

About this Entry

This page contains a single entry by chromatic published on April 28, 2011 8:34 AM.

Civility Starts with Me was the previous entry in this blog.

Reuse is Merely a Happy Accident is the next entry in this blog.

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


Powered by the Perl programming language

what is programming?