I've been playing with Ruby lately, and am quite enjoying it. It seems like a very clean language, and has a lot of good features going for it as well, including testing frameworks such as RSpec and Cucumber. Being me, I wondered what a Perl 6 version would look like. Also being me, I decided that the best way to find out, would be to write it.
Now yes, I know that some people don't have a very good opinion of RSpec, and it's unusual syntax, but I thought it was an interesting way of setting up tests. Agree or disagree, many people seem to be using (and liking) RSpec, and I thought it only fair to make something like it available to the Perl 6 community.
Now, before you download this, look at it and scream at me with "hey, this is a tiny, minor subset of RSpec, it's not even close to being a complete port!" I know. I wasn't intending to write a complete Perl 6 version of RSpec. All I wanted to do was write something that would allow you to use a similar syntax to the one found in the example on the front page of the RSpec website. It does some things in really crazy ways, but it's relatively fast and is a fun example of what you can do with Perl 6. It's not meant to be taken too seriously, but if you like it, go ahead and use it for something cool. It's released under the Artistic License 2.0, as used by Perl 6 itself. Since the original version of this article was written, PSpec has gained a lot more functionality, some of which is outlined below.
The Specification Format
So, the original example from the RSpec website looked something like this:
# bowling_spec.rb
require 'bowling'
describe Bowling, "#score" do
it "returns 0 for all gutter game" do
bowling = Bowling.new
20.times { bowling.hit(0) }
bowling.score.should == 0
end
end
Translated to Perl 6 with a bit of artistic license, and a slightly different syntax, this becomes:
# bowling_spec.p6
use PSpec;
use bowling;
describe Bowling, "score", [
"returns 0 for all gutter game" => {
my $bowling = Bowling.new;
20 times { $bowling.hit(0) }
$bowling.score should-be 0;
},
];
So yeah, it looks similar, but there are some significant differences between the two. First of all, instead of the describe statement consisting of a bunch of it statements, it now contains an Array of Pair objects. The key of the Pair object is the name of the test, and the value of the Pair is the closure containing the test itself.
Next up, there was no native .times method in the Int or Num classes, and since method calls on objects require () brackets, I figured the prettiest way to get the equivilant of 20.times { code } was to define an infix operator called times that took an integer on the left hand side, and a code block on the right hand side. Easy cheesy! 20 times { code } was born.
Finally, the syntax of the should call. As mentioned before, without using some grammar modifying rules (which I could have done, but didn't want to) trying to implement a .should method would have meant using () brackets. Not as pretty. So instead, I used multiple dispatch and created the should-be infix operator instead. Given numeric values, it uses the == comparision, given string values it uses eq comparision. Given anything else, it uses the magical Perl 6 entity known as the ~~ smart match operator. If the match is a success, it returns normally. If not, a diagnostic message is output in TAP compliant format to help track down the bug:
# expected: 50 # got: 53
So, I know this is just a silly bit of code to partially emulate a pre-existing package, but hey, it was fun.
A later update I added support for a special pair syntax that allows you to specify a string for the 'expected' value and a closure containing the test you want to run, in a format like:
"five is greater than four" => {
my $five = 2 + 2; ## See the problem here?
$five should-be ( '> 4' => { $^a > 4 } );
}
Still later, I realized that there was an even simpler way to provide support for simple comparisons such as >, < or gt. So, the should-be operator now supports a simple comparison form:
"five is greater than four" => {
my $five = 2 + 2;
$five should-be ( '>', 4 );
}
PickleSandwich: Cucumber stories in PSpec
So, after working on the original release of PSpec, I decided that it would be great if it could also offer a story handling mode like Cucumber. So, I added it. You tell it a story and write rules for how to handle the story. Just for something to give you an idea, here is a story for a simple reverse polish notation calculator:
Feature: Addition
In order to avoid silly mistakes
As a math idiot
I want to be told the sum of two numbers
Scenario: Add two numbers
Given I have entered 50 into the calculator
And I have entered 70 into the calculator
When I press add
Then the result should be 120 on the screen
Scenario: Add five numbers, with implicit add
Given I have entered 10 into the calculator
And I have entered 20 into the calculator
And I have entered 30 into the calculator
And I have entered 40 into the calculator
And I have entered 50 into the calculator
Then the result should be 150 on the screen
Feature: Subtraction
In order to do our bookkeeping
As a total moron
I want to be told the difference between two numbers
Scenario: Subtract two numbers
Given I have entered 50 into the calculator
And I have entered 20 into the calculator
When I press subtract
Then the result should be 30 on the screen
Feature: Multiplication
In order to show this is up with the times
As a calculator of four functions
I want to see it multiply
Scenario: Multiply two numbers
Given I have entered 20 into the calculator
And I have entered 5 into the calculator
When I press multiply
Then the result should be 100 on the screen
Feature: Division
In order to show how modular this is, it must be divided
As a purveyor of bad puns
I want to see the quotient
Scenario: Divide two numbers
Given I have entered 100 into the calculator
And I have entered 4 into the calculator
When I press divide
Then the result should be 25 on the screen
Scenario: Intentionally broken test
Given I have entered 100 into the calculator
And I have entered 25 into the calculator
When I press divide
Then the result should be 5 on the screen
The handler code is fairly simple too, you can test it yourself by running
the ./spec/calc.t specification test. It shows how to dispatch
based on the story, including how to perform tasks, such as clearing the
calculator's memory between each scenario. The handle-story method uses
TAP compliant output, while at the same time, reading off the story.
There is currently no support for Backgrounds or Scenario Outlines,
but the above story works just fine.
Advanced features such as Backgrounds and Scenario Outlines are available,
see the tests in the 't' folder of the source tree for more details.
Hey, as an extra bonus, it comes with a simple RPN calculator object class.
How cool is that ;-)
A big thanks to Carl Masak who suggested a method of implementing a .times method in the Int class. Now you can use either the times infix operator as shown above or you could use:
20.times: { say "hello"; }
Download
Since posting the original version of this article, it has been updated a few times. I have also moved the code to Github instead of keeping it in a tarball. Feel free to download the code for PSpec and play with it. It requires a fairly recent version of Rakudo Perl 6.
Anyway, hope you've enjoyed this bit of Perl 6 frolicking. Enjoy!