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

Hi all,
i wrote a test with

my $score = result_of_a_function(); cmp_ok($score, '==', 0, 'scores 0 in blablabla context');
But am quite annoyed, as the test passes when the (unimplemented) subroutine returns 'undef'.
It is annoying, as when i want to display my score :
print $score;
it doesn't display anything. If it displayed 0, i would probably be quite happy.

undef==0, it's a feature of the language, i have to accept it and adapt. Which is very simple: when i want to test that a value is 0, i can just use

is($score, 0, 'scores 0 in blablabla context');
There are good reasons for this choice, it is sometimes very handy to use strings or numbers in each other's place.
But i still feel slightly annoyed right now (^c^)

EDIT : the problem was wider than i thought :

1>undef
also returns true. Which means that any
cmp_ok($got, '<', $expected)
statement runs into the same risk / problem.

Replies are listed 'Best First'.
Re: 0==undef, that's annoying. Is it?
by tobyink (Canon) on Jul 15, 2012 at 21:56 UTC

    Or:

    ok( defined $score && $score==0, "score is 0", );

    Or (because undef stringifies to the empty string) use string comparison:

    cmp_ok( $score => eq => '0', "score is 0", );
    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'

      These work, but I don't like either of them (i'm being a bit too perfectionist there).

      The first one is not clear, it's a lot too complex to read, for such a simple assertion. The second one is exactly equivalent to my

      is($score, 0)
      assertion, but just harder to read.
      And i still prefer using cmp_ok() for numbers, as '==' indicates clearly that it's numbers. And
      is('1','1.0')
      returns 0, while
      cmp(1, '==', 1.0)
      is true.
      So... I'd like to use cmp_ok() for numbers, but i'll have to be careful to not compare zeros in there.

        Perhaps another alternative to consider is Test::NoWarnings.

        >perl -wMstrict -le "use Test::More 'no_plan'; use Test::NoWarnings; ;; cmp_ok(0, '==', undef, 'is 0 really undefined?'); " ok 1 - is 0 really undefined? not ok 2 - no warnings # Failed test 'no warnings' # at C:/strawberry/5.14/perl/vendor/lib/Test/NoWarnings.pm line 38. # There were 1 warning(s) # (lots and lots of discussion about the warning elided) # 1..2 # Looks like you failed 1 test of 2.

        These work, but I don't like either of them (i'm being a bit too perfectionist there).

        :) No, not perfectionist, merely neurotic (overanxious) :) See, the language is what it is, learn its ways and go with the flow :) don't get annoyed at things outside of your control, control yourself :) serenity now

Re: 0==undef, that's annoying. Is it?
by GrandFather (Saint) on Jul 16, 2012 at 02:57 UTC

    If you were being fussy with your testing you'd turn on fatal warnings in which case the warning you get comparing undef to a number would cause a very obvious failure in your test.

    True laziness is hard work

      I hadn't even noticed it produced a warning. Which was at the very start of the TAP output, while the end of the TAP output indicated that all tests passed.

      Test::NoWarnings does the trick, than you :o)
      I tried to turn warnings to fatal with

      use warnings qw(FATAL all);
      as indicated here using FATAL warnings, but it didn't work. It might/could be because of Windows, who doesn't like signals? I'm guessing that FATAL is maybe catching the WARN signal and making it die. But in Windows i found i couldn't catch any signals. It doesn't really matter for me right now : Test::NoWarnings works. Cheers!

      PS : the problem was wider than i thought : 1>undef also returns true. Which means that any cmp_ok($got, '<', $expected) statement runs into the same risk / problem.
      I'll add this to the first post.

        Just as a note for other people :
        Test::NoWarnings does the job, but it means that no warnings will be printed until the end of the tests. Which means that you cannot use Smart::Comments, and cannot use warn() to debug.

Re: 0==undef, that's annoying. Is it?
by Marshall (Canon) on Jul 16, 2012 at 06:42 UTC
    Well, if (undef ==0) will cause "Use of uninitialized value in numeric eq (==)" if warnings are turned on. So undef and zero are not exactly the same.

    I guess this is just a bit off topic, but there are several ways to differentiate between "undef" and numeric zero, but you would want to test for "truthfulness". Here are two ways of returning something that is logically "true", but numeric "0" (undef and a normal "0" are both logically false):

    1. return the string "0E0" - this is what the DBI does when the operation succeeded but no rows were altered. 0 * 10**0 = 0*1=0. If the operation fails for some reason, the DBI returns undef. Since "0E0" is a completely valid number, you can do math on it (like add to some counter).

    2. return the string "0 but true" - This is a special case coded into Perl. That is also logically true, but weirdly enough this is also a valid numeric string! Even with warnings, you can do math on that string without generating a warning. A string like "0 xyzzy" cannot be used in numeric operations without generating a warning.

    The two above techniques are useful to make a single return value do "double duty" for two separate concepts (the "did it work?" flag and the numeric return). In other languages you would need two variables for these.

    I actually find being able to test an undefined value to be a great thing and not annoying at all. This is a weird concept to get used to if you are used to working in other languages. Also perhaps worthy of mention is that in Perl 5.10, the // operator was introduced. $var //=12; sets $var to 12 if $var is undefined (no effect if $var is 0).

Re: 0==undef, that's annoying. Is it?
by AnomalousMonk (Archbishop) on Jul 15, 2012 at 22:38 UTC

    If you're trying to test unimplemented stub functions with Test::More, see the discussion of the  SKIP and  TODO blocks therein.

      No, it's not that.
      It's just that i wrote my test first, then created the function so that it would compile, and was surprised to see the test pass while the function wasn't returning anything.

      Thank you though :o)