简体   繁体   中英

Perl, strings, floats, unit testing and regexps!

OK, as a preface this question potentially is 'stupider' than my normal level of question - however this problem has been annoying me for the last few days so I'll ask it anyway. I'll give a mock example of what my problem is so I can hope to generalize it to my current problem.

#!/usr/bin/perl -w use strict;

use Test::More 'no_plan';

my $fruit_string = 'Apples cost $1.50';
my ($fruit, $price) = $fruit_string =~ /(\w+)s cost \$(\d+\.\d+)/;

# $price += 0; # Uncomment for Great Success
is ($price, 1.50, 'Great Success');

Now when this is run I get the message

#   Failed test 'Great Success'
#          got: '1.50'
#     expected: '1.5'

To make the test work - I either uncomment the commented line, or use is ($price, '1.50', 'Great Success') . Both options do not work for me - I'm testing a huge amount of nested data using Test::Deep and cmp_deeply. My question is, how can you extract a double from a regexp then use it immediately as a double - or if there is a better way altogether let me know - and feel free to tell me to take up gardening or something lol, learning Perl is hard.

You're already using Test::Deep, so you can simply use the num() wrapper to perform a numerical rather than stringwise comparison (it even lets you add in a tolerance, for comparing two inexact floating point values):

cmp_deeply(
    $result,
    {
        foo         => 'foo',
        bar         => 'blah',
        quantity    => 3,
        price       => num(1.5),
    },
    'result hash is correct',
);

For normal comparisons done separately, cmp_ok will work, but num() is still available: cmp_deeply($value, num(1.5), 'test name') still works.

强制$price被解释为数字:

is ( 0 + $price, 1.50, 'Great Success');

Why not use the tried-and-true ok ? You'll be testing the thing that you actually mean to test, and not have to worry about whether is is doing anything too subtle or too clever.

ok($price == 1.5, 'Great Success');

is does provide some additional diagnostics on failure, but that is easy enough to do with ok , too

ok($price == 1.5, 'Great Success') or diag("Expected \$price==1.5, got $price");

Your tests are failing because is($x, $y, $name) is equivalent to cmp_ok($x, 'eq', $y, $name) . The eq forces each of its arguments to be evaluated as strings. Since you want numeric equality, you could write it out with cmp_ok using '==' . You could make things easier by writing your own numeric version of is :

sub is_num {cmp_ok $_[0], '==', $_[1], $_[2]}

But that version is subtly broken, it will report errors on the wrong lines. To make sure that error reporting shows the right lines:

sub is_num {splice @_, 1, 0, '=='; goto &cmp_ok}

The reason for the goto &sub is because cmp_ok uses caller to determine where the error happened. The goto &sub syntax erases the call frame setup for is_num so that cmp_ok thinks its being called from the location that is_num was.

Lastly, a plug of my module Test::Magic which provides syntactic sugar for Test::More :

use Test::Magic 'no_plan';

... # setup code

test 'fruit price',
  is $price == 1.50;

Which is interpreted as cmp_ok( $price, '==', 1.50, 'fruit price')

The reason for this behavior is that is uses eq to do comparisons, which forces stringification of its arguments. 1.50 stringifies to '1.5' and this fails.

Your choices are to live with the behavior (force stringification or numification) or else write your own alternative to is which will compare numerically if both sides look like they are numbers before falling back on eq comparisons. I would personally go with the latter approach.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM