DEV Community

Cover image for Test coverage in practice
Elizabeth Mattijsen
Elizabeth Mattijsen

Posted on

4 1

Test coverage in practice

This is part 4 in the "Towards more coverage" blog series.

The first blog post showed the basic usage of the Test::Coverage distribution. But it can do more!

All code covered?

If you have a situation where all of the test in a distribution cover all of the executable code, you can simplify the test file to:

use Test::Coverage;

must-be-complete;
Enter fullscreen mode Exit fullscreen mode

This will set the plan to 1 test, and mark it ok if indeed all code is covered.

Should that fail for some reason, then the report and source-with-coverage subroutines will be called, providing the developer with feedback on which lines were not covered.

If you want to be clear to yourself what the goal is for coverage testing, you could mark this subroutine as "todo":

use Test::Coverage;

todo "needs more tests!";
must-be-complete;
Enter fullscreen mode Exit fullscreen mode

This will make sure that on the one hand the failure of coverage testing will not stop you from uploading a new release with e.g. App::Mi6, but will make it clear to any current or future maintainer that the intent was to cover all code in tests.

Personally, I wouldn't do this though. Coverage testing feels too much like bondage to me then, and programming really should be fun!

Are all code paths really covered?

Even if the tests say that all lines of code are covered, does that guarantee that all code paths are covered? Probably not. Why is that?

Well, that's because the granularity of code coverage is source line based. And a lot can happen in one line of Raku source code! For example, take this simple ternary:

my $a = <foo frobnicate>.pick;  # either "foo" or "frobnicate"
my $b = $a eq "foo" ?? "bar" !! "baz";
Enter fullscreen mode Exit fullscreen mode

From a code coverage point of view, it doesn't matter whether $a had the value "foo" or "frobnicate" in the assignment to $b. But for the execution of your code, that might really have different results!

One way around that, might be to split that ternary over multiple lines:

my $a = <foo frobnicate>.pick;  # either "foo" or "frobnicate"
my $b = $a eq "foo"
          ?? "bar"
          !! "baz"
;
Enter fullscreen mode Exit fullscreen mode

But that also feels a bit icky: adapting your coding style just to satisfy any coverage testing.

It's probably better to realize that 100% coverage testing is still no guarantee that all possible code paths are tested. And that you should always remain vigilant with regards to testing your code: find new ways to break it, and make tests to spot such breakage!

Status update

Of the 225 distributions that I am currently personally maintaining, 108 of them now have coverage testing enabled. 32 of them have complete coverage testing. So that's still a lot of modules not being tested, and definitely a lot not fully covered. After the initial push to test the coverage testing itself, I did about 100 of them. That gave me a good idea of all of the corner cases.

Now I add coverage testing only if a distribution needs updating. And any new distribution gets coverage testing, of course!

What about RakuAST?

The new Slang::Lambda is a very small distribution that allows one to use λ as a synonym for the pointy block starter -> in Raku source code. This is a really small module, so coverage testing should be easy, one would think.

The problem is that one piece of code is executed when the legacy grammar is used, and the other piece of code is executed if the new Raku grammar (RakuAST) is used. And one can only be executing code in one or the other grammar!

Fortunately, the Test::Coverage distribution allows one to handle that case as well, by being a little more verbose. The test file looks like:

use Test::Coverage;

default-coverage-setup;

run;

%*ENV<RAKUDO_RAKUAST>=1;
run;

must-be-complete;
Enter fullscreen mode Exit fullscreen mode

Note that now we explicitely run the default-coverage-setup and run subroutines (which would normally be run under the hood by the test routines).

Just before the second time the run subroutine is called, the RAKUDO_RAKUAST environment variable is set: this will cause the raku executable to use the new Raku grammar when parsing code. In this case, it will mark the RakuAST specific code in the Lambda slang as executed.

The call to must-be-complete will then see that all is ok, and mark the test is ok.

Conclusion

Coverage testing will help you determine which parts of the code in a distribution are not tested yet. But is no guarantee that really all possible code path have been tested.

Adding coverage testing at a later stage is a lot of work. Make sure that you add it during development of a distribution, as you would with any other tests.

The Test::Coverage distribution allows for further flexibility in a lot of corner cases as well, not just for the simple, straightforward testing cases.

This concludes this series of blog posts for now. New episodes may be added at a later stage: never say never!

If you like what I'm doing, committing to a small sponsorship would mean a great deal to me!

Image of Docusign

Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more