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?
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.
And then in your test, just use it.
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.I think you are looking for something like Implementing Typed Lexical Variables.
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:
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:
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