More on 100% Test Coverage

I recently wrote about 100% Test Coverage. I was pointing out that in a personal project, I was working on increasing coverage and everywhere where there wasn't coverage, there were bugs. Here's the flip side: if it's covered, it doesn't mean its bug free. I've talked about this on my old use.perl blog, but it's worth repeating for new readers.

Here's some code:

#!/usr/bin/env perl

use Modern::Perl;

sub reciprocal {
    my $number = shift;
    return 1/$number;
}

And here's a test:

use Test::Most;
is reciprocal 2, .5, 'Basic functionality should work';
done_testing;

Congratulations. You've achieved 100% test coverage. You should be proud!

Now what happens if you pass a string? Or a zero? What if you pass an object which stringifies to a number? Do you want to allow that or is it a happy accident? Do you care about any of these conditions?

Just because something is covered does not mean that it's correct. This is why a QA team of non-developers is important. I mean, it's good if your QA team knows how to program, but they should not be the programmers. It's far too easy to miss edge cases like this. When I've looked at test suites (including my own), there is a clear bias in favor of testing expected behavior. It's tough to test unexpected behavior because it's, well, unexpected. So even if you have superb test coverage, you probably still have bugs.

Remember: programmers focus on making code work. QA teams focus on making code break. It's a completely different, but needed, mindset.

Added bonus: here's the above code in Perl 6.

use v6;

subset NonZero of Num where { $_ != 0 };

sub reciprocal(NonZero $number) {
    return 1/$number;
}

use Test;
is reciprocal(2), .5, 'Basic functionality should work';
done_testing;

It's more correct and it's self-documenting.

Side note: how do we get Moose's rich type system in procedural code? I've written a lot of Moose and most of my code, today, is OO. Sometimes I'd like that type system in procedural code, though. What's best practice today?

6 Comments

From what I know, there is no direct way to invoke class methods in Moose today.

There is MooseX::ClassAttribute which brings your accessors on the class level but not for methods directly (unless you start to misuse accessors for methods).

Best way is probably to use MooseX::Singleton to define a helper class, that allows you to call any method defined there on the singleton object.


package ReciprocalHelper;
use MooseX::Singleton;

method reciprocal {
    my $number = shift;
    return 1/$number;
}

And then in your test, just use it.


use Test::Most;
use ReciprocalHelper;

is ReciprocalHelper->instance->reciprocal(2), .5, "Yeah, it's correct";

Of course this does not look 100% dwimy and simple. But maybe doing some magic like Log::Any uses with use Log::Any qw($log); could improve that.

Nice post, Ovid. This is closely related to a pet peeve of mine: merging code coverage data from integration tests and from unit tests. Yes, the total amount of covered code is a valuable statistic, but test density is usually much, much higher on unit tests, so they aren't really comparable.

There is MooseX::Params::Validate which works with procedural code:

 

#!/usr/bin/perl 
use strict;
use warnings;
use MooseX::Params::Validate;

sub reciprocal {
    my ($number) = pos_validated_list(\@_, { isa => 'Num' });
    return 1/$number;
}

my $num = shift;
print reciprocal($num) . "\n";

Well, there's really nothing stopping you from using Moose types in procedural code, it would just be clunky. You could use use MooseX::Types to export the types into your procedural code and then do:

use MooseX::Types::Moose qw(Str);

sub foo {
  my $value = shift;
  unless ( Str->check($value) ) {
    die("Type constraint check failed: " . Str->get_message($value));
   }
  ...
}

MooseX::Declare looks almost exactly the same as the p6 code, except that only works for objects IIUC. I imagine people would be willing to rebase MX::D or maybe add the MX stuff to signatures.pm

Leave a comment

About Ovid

user-pic Freelance Perl/Testing/Agile consultant and trainer. See http://www.allaroundtheworld.fr/ for our services. If you have a problem with Perl, we will solve it for you. And don't forget to buy my book! http://www.amazon.com/Beginning-Perl-Curtis-Poe/dp/1118013840/