+ All Categories
Home > Technology > Keeping objects healthy with Object::Exercise.

Keeping objects healthy with Object::Exercise.

Date post: 16-Mar-2018
Category:
Upload: workhorse-computing
View: 119 times
Download: 1 times
Share this document with a friend
29
Object::Exercise: Keep your objects healthy. Steven Lembark Workhorse Computing [email protected]
Transcript

Object::Exercise: Keep your objects healthy.

Steven LembarkWorkhorse [email protected]

Recall that Lazy-ness is a virtue

Hardcoded testing is a pain.

Tests require tests...

Alternative: Data-driven testing.

Declarative specs.

Generated tests.

We have all written this:

Create an object.

Run a command.

Check $@.

Execute a test.

Run a command...

The test cyclemy $obj = $class->new( @argz );

eval{

my $expect = { qw( your structure from hell ) };my $found = $obj->foo( @foo_argz );cmp_deeply $found, $expect, "foo";

};BAIL_OUT "foo fails" if $@;

eval{

my $expect = [ { qw( another structure from hell ) } ];my $found = [ $obj->bar( @bar_argz ) ];cmp_deeply $found, $expect, "bar";

};BAIL_OUT "bar fails" if $@;...

MJD's “Red Flags”

Code is framework, probably updated by cut+paste.

Spend more time hacking framework than module.

Hardwiring the the tests requres testing the tests.

Troubleshooting the tests...

Updating the tests.

Fix: Add a level of abstraction.

Put loops, boilerplate into framework.

Data drives test loop.

Metadata used to generate test data.

Replace hardwired tests with data.

Abstraction: Object::Exercise

A “little language”.

Describe a call, sanity checks.

Data drives the tests.

Array based for simpler processing.

Perl makes this easy

Perl can dispatch $object->$method( ... ).

The $method scalar can be

Text for symbol table dispatch.

Subref for direct dispatch into code.

Text for whitebox, Subref for blackbox.

Planning your exercise.

An array of steps.

Each step is an operation or directive.

Directives are text like “verbose”.

Operations are arrayref's with a

Method + args.

Expect & outcome.

Entering the labrynth

Entry point is a subref.

Avoids namespace collisions with methods:

$object->$exercise( @testz );

initiates testing.

Perly One-Step

Nothing more than a method and arguments:

[

method => arg, ...

]

Executes $obj->$method( arg, … ).

Successful if no exceptions.

Testy Two-Step

Supply fixed return as arrayref:

[

[ method => arg, ... ],

[ compare values ],

]

[ 1 ], [ undef ], [ %struct_from_hell ]

Compared to [ $obj->$method( @argz ) ].

Expecting failure

Testing failure modes raises exceptions.

Explicit undef expects eval { ... } to fail.

Reports successful exception:

[

[ method => @bogus_values ],

undef

]

Saying it your way

Third element is a message:

[

[ $method, @blah_blah ],

[ 42 ],

“$method supplies The Answer”

]

Great expectations

Beyond fixed values: regexen, subrefs.

qr/ ... / $found->[0] =~ $regex ? pass : fail ;

sub { ... } $sub->( @$found ) ? pass : fail ;

Generated regexen, closures simplify testing.

Both can have the optional message.

Giving orders

Directives are text scalars.

Turn on/off verbosity in tests.

Set a breakpoint before calling $method.

Treat input as regex text and compile it.

One-test settings

Text scalars before first arrayref are directives.

Test frobnicate verbosely, rest no-verbose:

noverbose =>

[

verbose =>

[ qw( frobnicate blah blah ) ]

],

One-test breakpoint

Adds $DB::Single = 1 prior to calling $obj->$method.

Simplifies perl -d to check a single method.

[

debug =>

[ qw( foobar bletch blort ) ],

[ 1 ]

]

Regexen in YAML::XS

YAML::XS segfaults handling stored rexen.

“regex” directive compiles expect value as regex:

[

regex =>

[ qw( foobar bletch) ],

'[A-Z]\w+$',

]

$obj->foobar( 'bletch' ) =~ qr/[A-Z]\w+$/;

Result: Declariative Testing

Tests can be stored in YAML, JSON.

Templates can be expanded in code.

Text methods useful for “blackbox” testing.

Validate what method returns.

Coderef's useful for “whitebox” testing.

Investigate internal structure of object.

Reset incremental values for iterative tests.

Example: Adventure Map Test

Minimal map: One locations entry.

name: Empty Map 00namespace : EmptyMap00locations: blackhole: name: BlackHole description : You are standing in a Black Hole. exits : Out : blackhole

Generating tests

Starting from the Black Hole:

Validate the description.

Validate move to exit.

Override the map contents

One way: Store separate mission files.

Every test needs to be replicated.

Two way: Replace the map.

Override the method returning locations.

Init reads the map

"add_locations" processes the map:

sub init { my ($class, $config_path) = @_; die "You must specify a config file to init()" unless defined $config_path; $_config = YAML::LoadFile($config_path); $class->add_items($_config->{items}); $class->add_actors($_config->{actors}); $class->add_locations($_config->{locations}); $class->add_player('player1', $_config->{player});}

Adding locations is a loop

They are added one-by-one.

sub add_locations{ my ($class, $locations) = @_; foreach my $key (keys %{$locations}) { $class->add_location($key, $locations->{$key}); }}

Adding one location

Finally: The root of all evil...

New locations are added with a key and struct:

sub add_location{ my ($class, $key, $config) = @_; my $location = Adventure::Location->new; $location->init($key, $config); $class->locations->{$key} = $location;}

Faking the location

sub install_test_location{

my $package = shift;my $test_struct = shift;*{ qualify_to_ref add_location => $package }= sub{

my ($class, $key ) = @_;$class->locations->{$key}= Adventure::Location->new->init($key, $test_struct ); # hardwired map

};}

Aside: Generic “add” handler

# e.g., install Adventure::Location::add_locationsub add_thingy{

my ( $parent, $thing ) = @_;my $name = ‘add_’ . $thing;my $package = qualify ucfirst(lc $thing), $parent;

*{ qualify_to_ref $name => $package }= sub{

my ($class, $name, $data ) = @_;$class->$type->{$name}= $package->new->init($name, $data );

};}

Summary

Exercising objects is simple, repetitive.

MJD’s “Red Flags”: don’t cut & paste.

Replace hardwired loops with data-driven code.

Object::Exercise standardizes the loops.

Tests are declarative data.


Recommended