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

I humbly ask the Monks for enlightenment on the subject of testing:

I came across this question when writing a test script for a function that returns an (at the time of the test) unknown (possibly random) number of values (e.g. a list where the list length is random, like in the sample below). I can currently see no way to test this function with Test::More except using the no_plan pragma. The documentation says that using no_plan is generally A Bad Idea(tm).

In this case, you can declare that you have no plan. (Try to avoid using this as it weakens your test.)
A small sample:
# Dyn.pm package Dyn; use strict; use warnings; # Return a list with a random number of elements sub get_list { my @arr = (); push @arr, $_ for (0 .. int(rand(10))); return @arr; } 1; __END__
# Dyn.t use Test::More qw ( no_plan ); use Dyn; my @arr = Dyn::get_list(); if (@arr) { foreach my $elem (@arr) { like($elem, qr/^\d+$/, q{Expect an integer}); } } __END__
Now running the test illustrates the problem of having a random number of tests:
Dyn....ok All tests successful. Files=1, Tests=5, 0 wallclock secs ( .. ) $ prove Dyn.t Dyn....ok All tests successful. Files=1, Tests=10, 0 wallclock secs ( .. ) $ prove Dyn.t Dyn....ok All tests successful. Files=1, Tests=7, 0 wallclock secs ( .. )
How would the enlightened Monks approach this problem? Should I accept having no_plan?
--
Andreas

Replies are listed 'Best First'.
Re: Replace 'no_plan' with fixed number using Test::More when output is random
by Ovid (Cardinal) on Aug 17, 2007 at 08:16 UTC

    Try this:

    use Test::More tests => 1; use Dyn; use Data::Dumper; my @arr = Dyn::get_list(); is scalar(grep { qr/^\d+$/ } @arr), scalar(@arr), 'All elements from get_list() should be integers' or diag Dumper(\@arr);

    Cheers,
    Ovid

    New address of my CGI Course.

      I agree, a random number of tests is almost certainly a bad idea.

      If you have random numbers involved, it might be a good idea to run the same test a fixed number of times, depending on the exact nature of your module.

Re: Replace 'no_plan' with fixed number using Test::More when output is random
by chakram88 (Pilgrim) on Aug 17, 2007 at 13:25 UTC
    How about using the result from get_list to identify how many tests there will be. I've used this technique in situations where a test run on my development machine (and db) would have a different number of tests when run against a more 'production level' DB.

    Something like this (untested)

    # Dyn.t use Test::More use Dyn; my @arr = Dyn::get_list(); my $test_count = scalar @arr; plan tests => $test_count; if (@arr) { foreach my $elem (@arr) { like($elem, qr/^\d+$/, q{Expect an integer}); } }
      chakram88++. Indeed, planning late is better than not planning. This an also be useful when you have a test that you're building dynamically in other ways (e.g., you have one kind of test, but you want to keep adding more cases to it).
      use Test::More; my @items = qw(Foo Bar Baz); # new plugins here, no change to test pla +n plan tests => scalar @items; foreach my $plugin (@items) { use_ok "My::Module::Plugin::$plugin"; }
Re: Replace 'no_plan' with fixed number using Test::More when output is random
by belden (Friar) on Aug 17, 2007 at 16:50 UTC
    If you re-write your code like this:

    # Dyn.pm package Dyn; use strict; use warnings; sub _max { 10 } sub _rand { rand(shift) } sub _range { my $max = _max(); my $rand_max = _rand($max); return (0..int($rand_max)); } sub get_list { my @arr = (); push @arr, $_ for _range(); return @arr; } 1; __END__
    Then you can test get_list() without a random number of tests, like this:

    #!/usr/bin/perl use Test::More tests => 10; use Dyn; use Test::Resub qw(resub); # _max { is( Dyn::_max, 10, 'Got expected max' ); } # _rand { my $max = 1045; srand($$); my $dyn_rand = Dyn::_rand($max); srand($$); my $core_rand = CORE::rand($max); is( $dyn_rand, $core_rand, 'We wrap CORE::rand' ); } # _range runs from 0.._max, returning only integers { my $max = 0; my $rs_max = resub 'Dyn::_max', sub { $max }; my $rs_rand = resub 'Dyn::_rand', sub { return shift }, capture => 1 +; is_deeply( [Dyn::_range], [0] ); is_deeply( $rs_rand->args, [[$max]] ); $max = 1.2; $rs_rand->reset; is_deeply( [Dyn::_range], [0, 1] ); is_deeply( $rs_rand->args, [[$max]] ); $max = 7.4; $rs_rand->reset; is_deeply( [Dyn::_range], [0..7] ); is_deeply( $rs_rand->args, [[$max]] ); } # get_list is our interface to _range { my @range; my $rs_range = resub 'Dyn::_range', sub { @range }; is_deeply( [Dyn::get_list], [] ); @range = ('a'..'f'); is_deeply( [Dyn::get_list], [qw(a b c d e f)] ); } __END__
    --Belden