Perl 6 By Example: Testing the Say Function

This blog post is part of my ongoing project
to write a book about Perl 6.

If you’re interested, please sign up for the mailing list at the bottom of
the article, or here. It will be
low volume (less than an email per month, on average).


Testing say()

In the previous installment I changed some code so that it wouldn’t produce
output, and instead did the output in the MAIN sub, which conveniently went untested.

Changing code to make it easier to test is a legitimate practice.
But if you do have to test code that produces output by calling say, there’s a
small trick you can use. say works on a file handle, and you can swap out the
default file handle, which is connected to standard output. Instead, you can put a
dummy file handle in its place that captures the lower-level commands issued
to it, and record this for testing.

There’s a ready-made module for that,
IO::String, but for the sake of
learning we’ll look at how it works:

use v6;

# function to be tested
sub doublespeak($x) {
    say $x ~ $x;
}

use Test;
plan 1;

my class OutputCapture {
    has @!lines;
    method print(s) {
        @!lines.push(s);
    }
    method captured() {
        @!lines.join;
    }
}

my $output = do {
    my $*OUT = OutputCapture.new;
    doublespeak(42);
    $*OUT.captured;
};

is $output, "4242n", 'doublespeak works';

The first part of the code is the function we want to test, sub
doublespeak
. It concatenates its argument with itself using the ~ string
concatenation operator. The result is passed to say.

Under the hood, say does a bit of formatting, and then looks up the variable
$*OUT. The * after the sigil marks it as a dynamic variable. The lookup
for the dynamic variable goes through the call stack, and in each stack frame
looks for a declaration of the variable, taking the first it finds. say then
calls the method print on that object.

Normally, $*OUT contains an object of type
IO::Handle, but the say function
doesn’t really care about that, as long as it can call a print method on
that object. That’s called duck typing: we don’t really care about the type of
the object, as long as it can quack like a duck. Or in this case, print like a
duck.

Then comes the loading of the test
module
, followed by the
declaration of how many tests to run:

use Test;
plan 1;

You can leave out the second line, and instead call done-testing after your
tests. But if there’s a chance that the test code itself might be buggy, and
not run tests it’s supposed to, it’s good to have an up-front declaration of
the number of expected tests, so that the Test module or the test harness
can catch such errors.

The next part of the example is the declaration of type which we can use to
emulate the IO::Handle:

my class OutputCapture {
    has @!lines;
    method print(s) {
        @!lines.append(s);
    }
    method captured() {
        @!lines.join;
    }
}

class introduces a class, and the my prefix makes the name lexically
scoped, just like in a my $var declaration.

has @!lines declares an attribute, that is, a variable that exists
separately for each instance of class OutputCapture. The ! marks it as an
attribute. We could leave it out, but having it right there means you
always know where the name comes from when reading a larger class.

The attribute @!lines starts with an @, not a $ as other variables we
have seen so far. The @ is the sigil for an array variable.

You might be seeing a trend now: the first letter of a variable or attribute
name denotes its rough type (scalar, array, & for routines, and later we’ll
learn about % for hashes), and if the second letter is not a letter, it
specifies its scope. We call this second letter a twigil. So far
we’ve seen * for dynamic variables, and ! for attributes. Stay tuned for
more.

Then penultimate block of our example is this:

my $output = do {
    my $*OUT = OutputCapture.new;
    doublespeak(42);
    $*OUT.captured;
};

do { ... } just executes the code inside the curly braces and returns the
value of the last statement. Like all code blocks in Perl 6, it also
introduces a new lexical scope.

The new scope comes in handy in the next line, where my $*OUT declares a new dynamic
variable $*OUT, which is however only valid in the scope of the block. It is
initialized with OutputCapture.new, a new instance of the class declared
earlier. new isn’t magic, it’s simply inherited from OutputCapture‘s
superclass. We didn’t declare one, but by default, classes get type
Any
as a superclass, which provides (among
other things) the method new as a constructor.

The call to doublespeak calls say, which in turn calls $*OUT.print. And
since $*OUT is an instance of OutputCapture in this dynamic scope, the
string passed to say lands in OutputCapture‘s attribute @!lines, where
$*OUT.captured can access it again.

The final line,

is $output, "4242n", 'doublespeak works';

calls the is function from the Test module.

In good old testing tradition, this produces output in the TAP format:

1..1
ok 1 - doublespeak works

Summary

We’ve seen that say() uses a dynamically scoped variable, $*OUT, as its
output file handle. For testing purposes, we can substitute that with an
object of our making. Which made us stumble upon the first glimpses of how
classes are written in Perl 6.

This full article can be read at Perl 6 By Example: Testing the Say Function.

Advertisement
Boost your Website Traffic with iNeedHits Today! Shop Now!


Random Article You May Like

Leave a Reply

Your email address will not be published. Required fields are marked *

*
*