Porting Test::Builder to Perl 6

Porting Test::Builder to Perl 6
by chromatic |

What Went Wrong

Writing the base for all (or at least many) possible test modules is tricky. In this case, it was trebly so. Not only was this the first bit of practical OO Perl 6 code I'd written, but I had no way to test it, either by hand (how I tested the Perl 5 version, before Schwern and I worked out a way to write automated tests for it), or with automated tests. Pugs didn't even have object support when I wrote this, though checking in this code pushed OO support higher on the schedule.

Infinite Loops in Construction

Originally, I thought all test classes would inherit from Test::Builder::Test. As Damian Conway pointed out, my technique created an infinite loop. (He suggested that "Don't make a façade factory class an ancestor of the instantiable classes" is a design mistake akin to "Don't get involved in a land war in Asia" and mumbled something else about battles of wits and Sicilians.) The code looked something like:

  class Test::Builder::Test

  {

      my Test::Builder::Test $:singleton is rw;



      has Bool $.passed;

      has Int  $.number;

      has Str  $.diagnostic;

      has Str  $.description;



      method new (Test::Builder::Test $class, *@args)

      {

          return $:singleton if $:singleton;

          $:singleton = $class.create( @args );

          return $:singleton;

      }



      method create(

          $number, 

          $passed       =  1,

          ?$skip        =  0,

          ?$todo        =  0,

          ?$reason      = '',

          ?$description = '',

      )

      {

          return Test::Builder::Test::TODO.new(

              description => $description, reason => $reason, passed => $passed,

          ) if $todo;



          return Test::Builder::Test::Skip.new(

              description => $description, reason => $reason, passed => 1,

          ) if $skip;



          return Test::Builder::Test::Pass.new(

              description => $description, passed => 1,

          ) if $passed;



          return Test::Builder::Test::TODO.new(

              description => $description, passed => 0,

          ) if $todo;

      }

  }



  class Test::Builder::Test::Pass is Test::Builder::Test {}

  class Test::Builder::Test::Fail is Test::Builder::Test {}

  class Test::Builder::Test::Skip is Test::Builder::Test { ... }

  class Test::Builder::Test::TODO is Test::Builder::Test { ... }



  # ...

Why is this a singleton? I have no idea; I typed that code into the wrong module and continued writing code a few minutes later, thinking that I knew what I was doing. The infinite loop stands out in my mind very clearly now. Because all of the concrete test classes inherit from Test::Builder::Test, they inherit its new() method; none of them override it. Thus, they'll all call create() again (and none of them override that either).

Confusing Initialization

I also struggled with the various bits and pieces of creating and building objects in Perl 6. There are a lot of hooks and overrides available, making the object system very flexible. However, without any experience or examples or guidance, choosing between new(), BUILD(), and BUILDALL() is difficult.

I realized I had no idea how to handle the singleton in Test::Builder. At least, when I realized that (for now) Test::Builder could remain a singleton, I didn't know how or where to create it.

I finally settled on putting it in new(), with code much like that in the broken version of Test::Builder::Test previously. new() eventually allocates space for, creates, and returns an opaque object. BUILD() initializes it. This led me to write code something like:

  class Test::Builder;



  # ...



  has Test::Builder::Output   $.output;

  has Test::Builder::TestPlan $.plan;



  has @:results;



  submethod BUILD ( Test::Builder::Output ?$output, ?$TestPlan )

  {

      $.plan   = $TestPlan if $TestPlan;

      $.output = $output ?? $output :: Test::Builder::Output.new();

  }

There's a difference here because most uses of Test::Builder set the test plan explicitly later, after receiving the Test::Builder object. I added a plan() method, too:

  method plan ( $self:, Str ?$explanation, Int ?$num )

  {

      die "Plan already set!" if $self.plan;



      if ($num)

      {

          $self.plan = Test::Builder::TestPlan.new( expect => $num );

      }

      elsif $explanation ~~ 'no_plan'

      {

          $self.plan = Test::Builder::NullPlan.new();

      }

      else

      {

          die "Unknown plan";

      }



      $self.output.write( $self.plan.header() );

  }

There are some stylistic errors in the previous code. First, when declaring an invocant, there's a colon but no comma. Second, fail is much better than die (an assertion Damian made that I take on faith, having researched more serious issues instead). Third, the parenthesization of the cases in the if statement is inconsistent.

Prev  [1] [2] [3] Next

Close    To Top
  • Prev Article-Programming:
  • Next Article-Programming:
  • Now: Tutorial for Web and Software Design > Programming > Perl > Programming Content
    Photoshop Tutorial
     

    Special Effect

      3D Effect
      Photoshop Articles
    Programming Tutorial
     

    C/C++ Tutorial

      Visual Basic
      C# Tutorial
    Database Tutorial
     

    MySQL Tutorial

      MS SQL Tutorial
      Oracle Tutorial
    Geek Tutorial
     

    Blogging Tutorial

      RSS Tutorial
      Podcasting Tutorial
    Graphic Design Tutorial
      Coreldraw Tutorial
      Illustrator Tutorial
      3D Tutorials
    Webmaster Articles
     

    Domain Service

      Web Hosting
      Site Promotion
    Java Tutorial/ Articles
     

    Java Servlets

      JavaEE Tutorial
     

    JavaBeans Tutorial

    XML Tutorial/ Articles
     

    XML Style

      AJAX Tutorial
      XML Mobile
    Flash Tutorial/ Articles
     

    Flash Video

      Action Script
      Flash Articles
    OS Tutorial/ Articles
      Linux Tutorial
      Symbian Tutorial
      MacOS Tutorial
    Personal Tech
      Hardware Tutorial
      Software Tutorial
      Online Auction