Top Banner
Object::Exercise: Keep your objects healthy. Steven Lembark Workhorse Computing [email protected]
29

Keeping objects healthy with Object::Exercise.

Mar 16, 2018

Download

Technology

Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Keeping objects healthy with Object::Exercise.

Object::Exercise: Keep your objects healthy.

Steven LembarkWorkhorse [email protected]

Page 2: Keeping objects healthy with Object::Exercise.

Recall that Lazy-ness is a virtue

Hardcoded testing is a pain.

Tests require tests...

Alternative: Data-driven testing.

Declarative specs.

Generated tests.

Page 3: Keeping objects healthy with Object::Exercise.

We have all written this:

Create an object.

Run a command.

Check $@.

Execute a test.

Run a command...

Page 4: Keeping objects healthy with Object::Exercise.

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 $@;...

Page 5: Keeping objects healthy with Object::Exercise.

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.

Page 6: Keeping objects healthy with Object::Exercise.

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.

Page 7: Keeping objects healthy with Object::Exercise.

Abstraction: Object::Exercise

A “little language”.

Describe a call, sanity checks.

Data drives the tests.

Array based for simpler processing.

Page 8: Keeping objects healthy with Object::Exercise.

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.

Page 9: Keeping objects healthy with Object::Exercise.

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.

Page 10: Keeping objects healthy with Object::Exercise.

Entering the labrynth

Entry point is a subref.

Avoids namespace collisions with methods:

$object->$exercise( @testz );

initiates testing.

Page 11: Keeping objects healthy with Object::Exercise.

Perly One-Step

Nothing more than a method and arguments:

[

method => arg, ...

]

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

Successful if no exceptions.

Page 12: Keeping objects healthy with Object::Exercise.

Testy Two-Step

Supply fixed return as arrayref:

[

[ method => arg, ... ],

[ compare values ],

]

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

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

Page 13: Keeping objects healthy with Object::Exercise.

Expecting failure

Testing failure modes raises exceptions.

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

Reports successful exception:

[

[ method => @bogus_values ],

undef

]

Page 14: Keeping objects healthy with Object::Exercise.

Saying it your way

Third element is a message:

[

[ $method, @blah_blah ],

[ 42 ],

“$method supplies The Answer”

]

Page 15: Keeping objects healthy with Object::Exercise.

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.

Page 16: Keeping objects healthy with Object::Exercise.

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.

Page 17: Keeping objects healthy with Object::Exercise.

One-test settings

Text scalars before first arrayref are directives.

Test frobnicate verbosely, rest no-verbose:

noverbose =>

[

verbose =>

[ qw( frobnicate blah blah ) ]

],

Page 18: Keeping objects healthy with Object::Exercise.

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 ]

]

Page 19: Keeping objects healthy with Object::Exercise.

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+$/;

Page 20: Keeping objects healthy with Object::Exercise.

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.

Page 21: Keeping objects healthy with Object::Exercise.

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

Page 22: Keeping objects healthy with Object::Exercise.

Generating tests

Starting from the Black Hole:

Validate the description.

Validate move to exit.

Page 23: Keeping objects healthy with Object::Exercise.

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.

Page 24: Keeping objects healthy with Object::Exercise.

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});}

Page 25: Keeping objects healthy with Object::Exercise.

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}); }}

Page 26: Keeping objects healthy with Object::Exercise.

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;}

Page 27: Keeping objects healthy with Object::Exercise.

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

};}

Page 28: Keeping objects healthy with Object::Exercise.

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 );

};}

Page 29: Keeping objects healthy with Object::Exercise.

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.