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!

Changelog

Jan 25, 2010
Last update prior to importation to GreyNoise.
Jan 25, 2010
Initial version.