Data Composition

     

As the data structures your code uses become more complex, so will your tests. It's important to verify what actually makes up a data structure instead of simply comparing it to an existing structure. You could iterate through each level of a complex nested hash of arrays, checking each and every element. Fortunately, the Test::Deep module neatens up code testing complicated data structures and provides sensible error messages.

How do I do that?

Save the following as cmp_deeply.t :

 use Test::More tests => 1;     use Test::Deep;     my $points =     [         { x => 50, y =>  75 },         { x => 19, y => -29 },     ];     my $is_integer = re('^-?\d+$');     cmp_deeply( $points,       array_each(         {           x => $is_integer,           y => $is_integer,         }       ),             'both sets of points should be integers' ); 

Now run cmp_deeply.t from the command line with prove . It will show one successful test:

 $  prove cmp_deeply.t  cmp_deep....ok     All tests successful.     Files=1, Tests=1,  0 wallclock secs ( 0.06 cusr +  0.00 csys =  0.06 CPU) 

What just happened ?

cmp_deeply( ) , like most other testing functions, accepts two or three arguments: the data structure to test, what you expect the structure to look like, and an optional test description. The expected data, however, is a special test structure with a format containing special Test::Deep functions.

The test file begins by creating a regular expression using re( ) , a function exported by Test::Deep . re( ) declares that the data must match the given regular expression. If you use a regular expression reference instead, Test::Deep believes you expect the data to be a regular expression instead of matching the data against it.


Note: re( ) also lets you perform checks on the data it matches. See the Test::Deep documentation for details .

Test::Deep 's array_each( ) function creates the main test structure for the test. To pass the test, $points must be an array reference. Every element of the array must validate against the test structure passed to array_each( ) .

Passing a hash reference as the test structure declares that every element must be a hash reference and the values of the given hash must match the values in the test structure's hash. In cmp_deeply.t , the hash contains only two keys, x and y , so the given hash must contain only those keys. Additionally, both values must match the regular expression created with re( ) .

Test::Deep 's diagnostics are really useful with large data structures. Change $points so that the y value of the first hash is the letter " Q ", which is invalid according to the provided test structure. Save it as cmp_deeply2.t :

 use Test::More tests => 1;     use Test::Deep;     my $points =     [         { x => 50, y =>  75 },         { x => 19, y =>  'Q'  },     ];     my $is_integer = re('^-?\d+$');     cmp_deeply( $points,       array_each(         {           x => $is_integer,           y => $is_integer,         }       )     ); 

Now run cmp_deeply2.t with prove -v . The cmp_deeply( ) function will fail with the following diagnostic:

 $  prove -v cmp_deeply2.t  cmp_deep2....#     Failed test (cmp_deep2.t at line 11)     # Using Regexp on $points->[1]{"y"}     #    got : 'Q'     # expect : (?-xism:^-?\d+$)     # Looks like you failed 1 tests of 1.     dubious         Test returned status 1 (wstat 256, 0x100)     DIED. FAILED test 1         Failed 1/1 tests, 0.00% okay     Failed 1/1 test scripts, 0.00% okay. 1/1 subtests failed, 0.00% okay.     Failed Test Stat Wstat Total Fail  Failed  List of Failed     ----------------------------------------------------------------------------     cmp_deep2.t    1   256     1    1 100.00%  1 

The failure diagnostic shows the exact part of the data structure that failed and explains that the value Q doesn't match the regular expression $is_integer .

What about...

Q:

What if some values in the data structure may change?

A:

To ignore a specific value, use the ignore( ) function in place of the regular expression. The following example still ensures that each hash in the array has both x and y keys, but doesn't check the value of y :

 array_each(       {         x => $is_integer,         y => ignore(  ),       }     ); 

Q:

What if some keys in the data structure may change?

A:

Suppose that you want to make sure that each hash contains at least the keys x and y . The superhashof( ) function ensures that the keys and values of the structure's hash appear in the given hash, but allows the given hash to contain other keys and values:

 array_each(       superhashof(         {           x => $is_integer,           y => ignore(  ),         }       )     ); 


Note: Think of sets, supersets, and subsets .

Similarly, Test::Deep 's subhashof( ) function ensures that a given hash may contain some or all of the keys given in the test structure's hash, but no others.

Q:

How do I check the contents of an array when I can't predict the order of the elements?

A:

Test::Deep provides a bag( ) function that does exactly this. Save the following as bag.t :

 use Test::More tests => 1;     use Test::Deep;     my @a = ( 4, 89, 2, 7, 1 );     cmp_deeply( \@a, bag( 1, 2, 4, 7, 89 ) ); 

Run bag.t to see that it passes the test. The bag( ) function is so common in test files that Test::Deep provides a cmp_bag( ) function. You can also write bag.t as follows :

 use Test::More tests => 1;     use Test::Deep;     my @a = ( 4, 89, 2, 7, 1 );  cmp_bag( \@a, [ 1, 2, 4, 7, 89 ] );  

Where to learn more

This section is only a brief overview of the Test::Deep module, which provides further comparison functions for testing objects, methods , sets (unordered arrays with unique elements), booleans, and code references. For more information, see the Test::Deep documentation.



Perl Testing. A Developer's Notebook
Perl Testing: A Developers Notebook
ISBN: 0596100922
EAN: 2147483647
Year: 2003
Pages: 107

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net