Introduction
I spent a lot of time exploring Test::More in the last step. That’s because I want to start building larger projects, and testing is a vital part of most projects. Another major part is a properly organized workspace with a script that can simplify testing or other tasks.
Creating a Simple Project
A nice Parrot project layout includes a t folder for tests, a lib folder for
library code, and a setup.pir file to drive the whole thing.
$ mkdir simple-pir$ mkdir simple-pir/t$ mkdir simple-pir/lib$ cd simple-pirWhat gets placed in setup.pir? Not much, considering how much it does.
setup.pir takes advantage of the Parrot distutils module for a whole range
of tasks. All I’m concerned about today is testing, so my setup is going to be
rather lightweight.
.sub 'main' :main .param pmc args $S0 = shift args # Ignore my own filename load_bytecode 'distutils.pbc'
# Find out what command the user has issued .local string directive directive = shift args
setup(directive).endThis is not exciting code, but it is enough to see what distutils can give me.
The first command line parameter is shifted onto a dummy register variable,
because I don’t really care about the name of setup.pir from within
setup.pir. Then I load the distutils bytecode so I can get access to the setup subroutine.
This setup.pir will get more complicated as we go on, and you will
definitely see more complex setup.pir files out in the wild, but this will
get us started.
$ parrot setup.pir helpusage: parrot setup.pir [target|--key value]*
Default targets are :
build: Build the library.
test: Run the test suite.
install: Install the library.
uninstall: Uninstall the library.
clean: Basic cleaning up.
update: Update from the repository.
plumage: Output a skeleton for Plumage
sdist: Create a source distribution
bdist: Create a binary distribution
help: Print this help message.What happens when I tell setup.pir that I want to test?
$ parrot setup.pir testFiles=0, Tests=0, 0.000 wallclock secsResult: NOTESTSWell of course it failed. There aren’t any test files, and setup.pir wouldn’t
know how to run them if there were!
I’ll fix the second part first.
.sub 'main' :main .param pmc args $S0 = shift args # Ignore my own filename load_bytecode 'distutils.pbc'
# Find out what command the user has issued .local string directive directive = shift args
# Used by the test mode .local string prove_exec prove_exec = get_parrot()
setup(directive, 'prove_exec' => prove_exec).endParrot allows you to use named parameters for some subroutines, and setup
takes full advantage of that feature. If you’re used to Perl or Ruby,
named parameters look a lot like a hash. That’s close enough for our purposes. A
named parameter generally follows a simple format:
'<key-1>' => '<value-1>'Thankfully, distutils.pir is a well-documented module, and you
can find details about the many options by checking the documentation.
$ perldoc /usr/local/lib/parrot/3.0.0/library/distutils.pirI only care about a single option: prove_exec, which tells setup what program
will be used to run the tests. Why does setup care? Well, Parrot is a VM. Your
tests can be in PIR, NQP, Rakudo, or even a language of your own design.
These Babysteps are about Parrot PIR, so it makes sense that the tests will be in
the same language.
Oh yes, the tests. Let’s write one. I’ll follow the convention I see in the Perl world of a number followed by a description for the test filename, and the test itself will be for a simple area calculating function.
.sub 'main' :main .include 'test_more.pir' .local num radius .local num expected_area, actual_area
plan(1)
radius = 1.0 expected_area = 3.1415926 actual_area = area_of_circle(radius) is(expected_area, actual_area, 'Circle with radius 1 should have area PI', 1e-6).endSo - this should fail, right?
$ parrot setup.pir testt/01-radius.t .. Dubious, test returned 1Failed 1/1 subtests
Test Summary Report-------------------t/01-radius.t (Tests: 0 Failed: 0) Non-zero exit status: 1 Parse errors: Unknown TAP token: "Could not find sub area_of_circle" Unknown TAP token: "current instr.: 'main' pc 40(t/01-radius.t:13)" Bad plan. You planned 1 tests but ran 0.Files=1, Tests=0, 0.021 wallclock secsResult: FAILtest failscurrent instr.: 'setup' pc 883 (runtime/parrot/library/distutils.pir:376)called from Sub 'main' pc 29 (setup.pir:18)Excellent. Parrot didn’t just tell us that the test failed. It also told us
about some unexpected output from our test script. What’s that unexpected
output? Oh, something about not having a subroutine called area_of_circle.
Let’s fix that by adding a new library file called lib/area.pir, and adding
the missing subroutine.
.sub area_of_circle .param num radius .const num PI = 3.1415926 .local num area
area = PI area *= radius area *= radius
.return(area).endThis is code borrowed from step 2 and dropped into a subroutine.
Don’t forget to include this library code from your test file.
.include 'lib/area.pir'
.sub 'main' :main # ....endDid it work?
$ parrot setup.pir testt/01-radius.t .. okAll tests successful.Files=1, Tests=1, 0.016 wallclock secsResult: PASSYay!
Hold on a second. I snuck an extra argument back when I wrote the is assertion. What was that
all about? Well, Jonathan Leto explained to me that is takes an additional argument
for precision, which is useful in the fuzzy world of floating point
math on
a modern computer. The 1e-6 requirement asks Parrot to make sure expected_area
and actual_area look the same down to six places past the decimal point.
This approach of writing the tests before you write the code is called TDD, for Test Driven Development. I like TDD because I’m basically describing the next thing I want my library or application to do. That’s perfect for me, since I’m such a chatty person. Well, I’m chatty when typing at the computer.
You don’t need to follow a test driven approach, but other developers will like you more if you consistently test the code you write. The easiest way to consistently test it is to write the test before you write the code.
Conclusion
Combining what we’ve learned about Test::More with setup.pir allows us to
confidently build more complicated applications, testing as we go along. It is
true that all we know how to do with setup.pir at this point is ask it to run
tests for us, but even that can save a lot of work.
I don’t know about you, but I’m ready to take another look at that star catalog.