Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling
 
PerlMonks  

Diagnostic messages in Test::Deep

by KeighleHawk (Scribe)
on Jul 20, 2015 at 23:25 UTC ( #1135506=perlquestion: print w/replies, xml ) Need Help??

KeighleHawk has asked for the wisdom of the Perl Monks concerning the following question:

Related (sorta) to my post in case insensitive deep comparison, I switched a test comparison from Test::More->is_deeply() to Test::Deep->cmp_deeply(). I did this because part of the structure I am comparing includes an array of hashes. Naturally, the array can be returned in random order. Initially, I was using a sort on one of the hash fields, but as my data set increased, I started to hit return values with different hash structures so my sorting column needed to change.

However, the Test::Deep->bag() method alleviates the need for this by comparing arrays that can be in random order which was far better (I thought) than trying to sort the arrays in the first place. The problem is the messages returned on a failed test. is_deeply would return the exact key/value pair returned and the one expected. cmp_deeply for the same test returned:

Missing: 1 reference
Extra: 1 reference

Not so helpful. Now, I can deal with this by printing out my own messages on fail using Test::More->diag(), but before I go through my code to add all that, I wanted to make sure I was not missing something obvious, especially since Test::Deep touted it's useful diagnostic messages.

Robert Kuropkat

Replies are listed 'Best First'.
Re: Diagnostic messages in Test::Deep
by Athanasius (Bishop) on Jul 21, 2015 at 07:01 UTC

    Hello KeighleHawk,

    Naturally, the array can be returned in random order.

    Actually, the elements of an array are always in a fixed order — that’s the nature of arrays. I think you mean the array will be populated in a random order?

    ...part of the structure I am comparing includes an array of hashes.... Initially, I was using a sort on one of the hash fields, but as my data set increased, I started to hit return values with different hash structures so my sorting column needed to change.

    I don’t understand this. If you are comparing an array of hashes with an unordered list of hashes, it seems to me that you have to proceed in two steps:

    1. Pair the hashes;
    2. For each pair, the hashes are equal if and only if they contain exactly the same key/value pairs (in any order).

    From which (I think) it follows that a generic procedure is all that’s required:

    #! perl use strict; use warnings; use Test::More tests => 2; use Test::Deep; my @x = ( { j => 12, a => 1 }, { b => [1, 11] }, { c => 3 } ); my @y = ( { c => 3 }, { b => [0, 11] }, { a => 1, j => 12 } ); cmp_deeply( \@x, bag(@y), 'Deep' ); print '-' x 20, "\n"; is_deeply( sort_hash(@x), sort_hash(@y), 'More' ); sub sort_hash { return [ sort { (sort keys %$a)[0] cmp (sort keys %$b)[0] } @_ ]; }

    Output:

    16:56 >perl 1314_SoPW.pl 1..2 not ok 1 - Deep # Failed test 'Deep' # at 1314_SoPW.pl line 22. # Comparing $data as a Bag # Missing: 1 reference # Extra: 1 reference -------------------- not ok 2 - More # Failed test 'More' # at 1314_SoPW.pl line 24. # Structures begin differing at: # $got->[1]{b}[0] = '1' # $expected->[1]{b}[0] = '0' # Looks like you failed 2 tests of 2. 16:56 >

    Clearly, Test::More::is_deeply returns a much more informative message on failure than does Test::Deep::cmp_deeply — as you have stated. But in what way does my sub sort_hash fall short of the functionality provided by Test::Deep::bag for your purposes? Please clarify by providing sample input for which the comparison with Test::Deep::bag will work, but a generic comparison based on hash keys will not.

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      yes, the array can be populated in random order :-).

      The data set is slightly different than your example. The keys for each hash within the test run are the same. They differ between runs/tests.

      Test 1 sample.

      $got = [ { key_1 => "A", key_2 => "CC"} , { key_1 => "B", key_2 => "CC"} ] $expected = [ { key_1 => "B", key_2 => "CC"} , { key_1 => "A", key_2 => "CC"} ]

      I want these to evaluate as equal, and they do with code like this (similar to yours):

      return is deeply ( $got ? [ sort {$a->{key_1} cmp $b->{key_1}} $got ] : [] , $expected ? [ sort {$a->{key_1} cmp $b->{key_1}} $expected ] : [] , "Test array of hashes"

      (the ternary operator is there because "no data returned" is also a valid/possible response).

      The problem arises with Test 2 which has the following data:

      $got = [ { key_3 => "A", key_2 => "CC"} , { key_3 => "B", key_2 => "CC"} ] $expected = [ { key_3 => "B", key_2 => "CC"} , { key_3 => "A", key_2 => "CC"} ]

      The problem is the sorting key name has changed from 'key_1' to 'key_3' so my sort:

      sort {$a->{key_1} cmp $b->{key_1}}

      fails on the second data set. I could pass in the key names as variables, but was hoping to avoid that, especially since Test::Deep->bag() does exactly what I want.

      The working Test::Deep code looks like this:

      return = cmp_deeply ( $got ? [ $got ] : [] , bag ( $expected ? $expected : () ) );

      (different brackets on bag due to return type, I need to double check that...)

      So Test::Deep->bag() works for me because it does magic I don't have to when deciding how to sort the second array.

      Robert Kuropkat

        OK, I think I understand now; I hadn’t realised that separate hashes within the one array could legitimately contain the same keys with different values.

        I still think a generic procedure will work, it just needs a comprehensive sorting algorithm:

        #! perl use strict; use warnings; use Test::More tests => 2; use Test::Deep; my @x = ( { key_3 => 'A', key_2 => 'CC', key_4 => 'E', key_5 => 0 }, { key_3 => 'A', key_2 => 'DD', key_4 => 'F' }, ); my @y = ( { key_3 => 'A', key_2 => 'DD', key_4 => 'F' }, { key_5 => 0, key_3 => 'A', key_2 => 'CC', key_4 => 'E' }, ); cmp_deeply( \@x, bag(@y), 'Deep' ); print '-' x 20, "\n"; is_deeply( my_sort(@x), my_sort(@y), 'More' ); sub my_sort { my $f = sub { my @keys_a = sort keys %$a; my @keys_b = sort keys %$b; return -1 if @keys_a < @keys_b; return 1 if @keys_a > @keys_b; my $result = 0; $result ||= $keys_a[$_] cmp $keys_b[$_] || $a->{$keys_a[$_]} cmp $b->{$keys_b[$_]} for 0 .. $#k +eys_a; return $result; }; return [ sort $f @_ ]; }

        (For the basic idea, see perlfaq4#How-do-I-sort-an-array-by-anything.) Output:

        14:38 >perl 1314a_SoPW.pl 1..2 ok 1 - Deep -------------------- ok 2 - More 14:38 >

        Disclaimer: I’ve tested this only minimally; there may be corner cases I haven’t found. But I think this should allow you to return to using Test::More::is_deeply to get the benefit of its superior failure messages.

        Hope that helps,

        Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1135506]
Approved by marinersk
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others scrutinizing the Monastery: (12)
As of 2019-10-18 14:31 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Notices?