SlideShare a Scribd company logo
1 of 228
Download to read offline
Testing Code
and Assuring Quality
      Learning to use Test::More,
     Perl::Critic, and Devel::Cover




        Kent Cowgill
Testing Code
and Assuring Quality
 •Learn how to write unit tests in perl
 •Write tests for your code
 •Ensuring your code is high quality
 •Ensuring your tests fully exercise your code
 •Writing functional tests for your code
 •A practical example of creating a test suite
 •How to save time and effort (be lazy!)
What is
testing?
Testing.
Software testing is the process used to help identify the
correctness, completeness, security, and quality of
developed computer software. Testing is a process of
technical investigation, performed on behalf of
stakeholders, that is intended to reveal quality-related
information about the product with respect to the context
in which it is intended to operate. This includes, but is not
limited to, the process of executing a program or
application with the intent of finding errors. Quality is not
an absolute; it is value to some person. With that in mind,
testing can never completely establish the correctness of
arbitrary computer software; testing furnishes a criticism
or comparison that compares the state and behavior of
the product against a specification.
                                -- excerpted from http://en.wikipedia.org/wiki/Software_testing
Testing..
In software engineering, a test case is a set of conditions
or variables under which a tester will determine if a
requirement upon an application is partially or fully
satisfied.

It may take many test cases to determine that a
requirement is fully satisfied. In order to fully test that all
the requirements of an application are met, there must be
at least one test case for each requirement unless a
requirement has sub requirements.

Some methodologies recommend creating at least two
test cases for each requirement. One of them should
perform positive testing of requirement and other should
perform negative testing.       -- excerpted from http://en.wikipedia.org/wiki/Test_Case
Testing...
What characterizes a formal, written test case is that there
is a known input and an expected output, which is worked
out before the test is executed.

If the application is created without formal requirements,
then test cases are written based on the accepted normal
operation of programs of a similar class.




                               -- excerpted from http://en.wikipedia.org/wiki/Test_Case
How can I find out
more information about
  testing with Perl?
(or anything else you
talk about tonight, since
 you don't really cover
anything in great depth?)
     (yeah, sorry about that)
Google
Websites
1
CPAN
2
CPAN
Screencast
demonstration
  removed
   for PDF
Books
How to
 write unit
tests in Perl
Unit tests
  emit
  TAP
Test Anything Protocol
        (TAP)

•   The Test Anything Protocol is a general purpose
    format for transmitting the result of test programs to a
    thing which interprets and takes action on those
    results.
Test Anything Protocol
         (TAP)
1..N
ok 1 Description # Directive
# Diagnostic
....
ok 47 Description
ok 48 Description
more tests....
Test Anything Protocol
         (TAP)
1..4
ok 1 -   Input file opened
not ok   2 - First line of the input valid
ok 3 -   Read the rest of the file
not ok   4 - Summarized correctly # TODO
Let's write
some tests.
Test::Simple
• ok(   <expression>, <description>);


  ok( $num == 30, '$num equals 30' );

  ok( $this =~ m/that/, 'this matches that' );

  ok( do_it( $param ), 'sub do_it() returns true' );

  OUTPUT:

  ok 1 - $num equals 30

  ok 2 - this matches that

  ok 3 - sub do_it() returns true
Test::Simple
• ok(   <expression>, <description>);


  ok( $num == 30, '$num equals 30' );

  ok( $this =~ m/that/, 'this matches that' );

  ok( do_it( $param ), 'sub do_it() returns true' );

  OUTPUT:

  not ok 1 - $num equals 30

  #   Failed test '$num equals 30'

  #   in test.pl at line 10.
Test::Simple
• ok(   <expression>, <description>);


  ok( $num == 30, '$num equals 30' );

  ok( $this =~ m/that/, 'this matches that' );

  ok( do_it( $param ), 'sub do_it() returns true' );

  OUTPUT:

  not ok 2 - this matches that

  #   Failed test 'this matches that'

  #   in test.pl at line 11.
Test::Simple
• ok(   <expression>, <description>);


  ok( $num == 30, '$num equals 30' );

  ok( $this =~ m/that/, 'this matches that' );

  ok( do_it( $param ), 'sub do_it() returns true' );

  OUTPUT:

  not ok 3 - sub do_it() returns true

  #   Failed test 'sub do_it() returns true'

  #   in test.pl at line 13.
Test::More
• is(   <got>, <expected>, <description>);


  is( $this, $that, 'this is the same as that' );
Test::More
• is(   <got>, <expected>, <description>);


  is( $this, $that, 'this is the same as that' );

  OUTPUT:

  ok 1 - this is the same as that
Test::More
• is(   <got>, <expected>, <description>);


  is( $this, $that, 'this is the same as that' );

  OUTPUT:

  not ok 1 - this is the same as that

  #   Failed test 'this is equal to that'

  #   in test.t at line 10

  #           got: 'this'

  #      expected: 'that'
Actual URL: http://pub.langworth.com/perl_test_refcard.pdf
Introducing Prove
PROVE(1)        User Contributed Perl Documentation       PROVE(1)

NAME
         prove -- A command-line tool for running tests

OPTIONS
     -d,     --debug   Includes extra debugging information
     -h,     --help    Display this help
     -H,     --man     Longer manpage for prove
     -I                Add libraries to @INC, as Perl's -I
     -l,   --lib       Add lib to the path for your tests
     -r,   --recurse   Recursively descend into directories
     -s,   --shuffle   Run the tests in a random order
           --timer     Print elapsed time after each test file
       -v, --verbose   Display standard output of test scripts
                       while running

       ...
Output:

$ mv testmore.pl testmore.t

$ prove
./testmore....ok
All tests successful.
Files=1, Tests=3, 0 wallclock secs ( 0.02 cusr +   0.01
csys = 0.03 CPU)

$ prove -v
./testmore....ok 1 - this should equal thistoo
ok 2 - this should be thistoo (is)
ok 3 - this should NOT be that (isnt)
1..3
ok
All tests successful.
Files=1, Tests=3, 0 wallclock secs ( 0.02 cusr +   0.01
csys = 0.03 CPU)
How Many Tests?
#!/usr/bin/perl

use strict;
use warnings;

use Test::More tests => 3;

# set some testing variables
my $this = quot;thisquot;;
my $thistoo = quot;thisquot;;
my $that = quot;thatquot;;

# now for the tests
ok( $this eq $thistoo, quot;this should equal thistooquot; );
is( $this, $thistoo, quot;this should be thistoo (is)quot; );
isnt( $this, $that, quot;this should NOT be that (isnt)quot; );
How Many Tests?
$ prove -v
./testmore....1..3
ok 1 - this should equal thistoo
ok 2 - this should be thistoo (is)
ok 3 - this should NOT be that (isnt)
ok
All tests successful.
Files=1, Tests=3, 0 wallclock secs ( 0.02 cusr +   0.01 csys
= 0.03 CPU)
How Many Tests?
#!/usr/bin/perl

use strict;
use warnings;

use Test::More tests => 4;

# set some testing variables
my $this = quot;thisquot;;
my $thistoo = quot;thisquot;;
my $that = quot;thatquot;;

# now for the tests
ok( $this eq $thistoo, quot;this should equal thistooquot; );
is( $this, $thistoo, quot;this should be thistoo (is)quot; );
isnt( $this, $that, quot;this should NOT be that (isnt)quot; );
How Many Tests?
$ prove -v
testmore....1..4
ok 1 - this equals thistoo
ok 2 - another way to see if this and thistoo are equal
# Looks like you planned 4 tests but only ran 3.
ok 3 - a way to see if this and that are not equal
dubious
        Test returned status 255 (wstat 65280, 0xff00)
DIED. FAILED test 4
        Failed 1/4 tests, 75.00% okay
Failed Test Stat Wstat Total Fail List of Failed
-------------------------------------------------------------
testmore.t   255 65280     4    24
Failed 1/1 test scripts. 1/4 subtests failed.
Files=1, Tests=4, 0 wallclock secs ( 0.02 cusr + 0.01 csys
= 0.03 CPU)
Failed 1/1 test programs. 1/4 subtests failed.
Why prove,
 anyhow?
-l, --lib   Add lib to the path for your tests
-l, --lib       Add lib to the path for your tests




-r, --recurse   Recursively descend into directories
-l, --lib       Add lib to the path for your tests




-r, --recurse   Recursively descend into directories




-s, --shuffle   Run the tests in a random order
That's
great
but...
how does
that help
 me? :-/
perl -c
Your
problem:
Your code
compiles, but
does it do the
 right thing?
Does it?

 I mean,
REALLY?
How do you
  know?
Can you
prove it?
My
  problem:
   ZFML*
* Name changed to protect the innocent
(btw, what
 the heck
is ZFML?)
ZFML is a custom
template system
ZFML is a
mish-mash of HTML
      and Perl
ZFML only exists at
 AcmeCorp.com*

 * Name changed to protect the innocent
I don't think you'd
  want it to exist
  anywhere else.
SRSLY
ZFML
                                                           That looks
<html>
  <head><title></title></head>
                                                           like HTML
  <body>
  </body>
</html>

<!-- __INIT SETUP__

my ($p) = @_;

$p->var->{'ONLOAD'} .= q(agentDOMCheck(););
$p->var->{'SCRIPT'} .= q(<script src=quot;form_functions.jsquot;></script>);

-->

<!-- __EVAL COPYRIGHT_YEAR__

my ($p) = @_;

$p->var->{'COPYRIGHT_YEAR'} = 1900 + (localtime)[5];

-->
ZFML
                                                           That looks
<html>
  <head><title></title></head>
                                                           like HTML
  <body>
  </body>
</html>

<!-- __INIT SETUP__
                                                                WTF?!?
my ($p) = @_;

$p->var->{'ONLOAD'} .= q(agentDOMCheck(););
$p->var->{'SCRIPT'} .= q(<script src=quot;form_functions.jsquot;></script>);

-->

<!-- __EVAL COPYRIGHT_YEAR__

my ($p) = @_;

$p->var->{'COPYRIGHT_YEAR'} = 1900 + (localtime)[5];

-->
It only runs
   under
 mod_perl
:(
$ perl -c index.zfml

Bareword found where operator expected at index.zfml
line 5, near quot;<meta http-equiv=quot;content-typequot;
content=quot;text/htmlquot;
(Might be a runaway multi-line // string starting on line4)
        (Missing operator before html?)
String found where operator expected at index.zfml line 6, near
quot;<meta name=quot;quot;
  (Might be a runaway multi-line quot;quot; string starting on line 5)
        (Missing semicolon on previous line?)
Bareword found where operator expected at index.zfml line 6, near
quot;<meta name=quot;descriptionquot;
        (Missing operator before description?)
String found where operator expected at index.zfml line 6, near
quot;descriptionquot; content=quot;quot;
Bareword found where operator expected at index.zfml line 6, near quot;quot;
content=quot;Findquot;
        (Missing operator before Find?)
Bareword found where operator expected at index.zfml line 7, near quot;<meta
NAME=quot;keywordsquot;
  (Might be a runaway multi-line quot;quot; string starting on line 6)
        (Missing operator before keywords?)
String found where operator expected at index.zfml line 7, near quot;keywordsquot; CONTENT=quot;quot;
Bareword found where operator expected at index.zfml line 7, near quot;quot; CONTENT=quot;AcmeCorpquot;
        (Missing operator before AcmeCorp?)
Bareword found where operator expected at index.zfml line 7, near quot;time jobsquot;
        (Do you need to predeclare time?)
String found where operator expected at index.zfml line 8, near quot;<style type=quot;quot;
   (Might be a runaway multi-line quot;quot; string starting on line 7)
        (Missing semicolon on previous line?)
Bareword found where operator expected at index.zfml line 8, near quot;<style type=quot;textquot;
        (Missing operator before text?)
String found where operator expected at index.zfml line 28, near quot;<div id=quot;quot;
  (Might be a runaway multi-line quot;quot; string starting on line 8)
        (Missing semicolon on previous line?)
Bareword found where operator expected at index.zfml line 28, near quot;<div id=quot;pageContainerquot;
        (Missing operator before pageContainer?)
Write
 tests for
your code
A Simple Class
#!/usr/bin/perl

use strict;
use warnings;

package myObj;

sub new {
   my $class = shift;
   my %args = @_;
   my $self = {};
   $self->{ name } = $args{ name   } || 'default';
   return bless $self, $class;
}
sub set_name {
   my $self = shift;
   $self->{ name } = shift;
}
sub get_name {
   my $self = shift;
   return $self->{ name };
}
1;
A Simple Class
#!/usr/bin/perl

use strict;
use warnings;
                                                   Constructor
package myObj;                     (http://en.wikipedia.org/wiki/Constructor_%28computer_science%29)

sub new {
   my $class = shift;
   my %args = @_;
   my $self = {};
   $self->{ name } = $args{ name   } || 'default';
   return bless $self, $class;
}
sub set_name {
   my $self = shift;
   $self->{ name } = shift;
}
sub get_name {
   my $self = shift;
   return $self->{ name };
}
1;
A Simple Class
#!/usr/bin/perl

use strict;
use warnings;
                                                   Constructor
package myObj;                     (http://en.wikipedia.org/wiki/Constructor_%28computer_science%29)

sub new {
   my $class = shift;
   my %args = @_;
   my $self = {};
   $self->{ name } = $args{ name   } || 'default';
   return bless $self, $class;
}
                                                         Mutator
sub set_name {
   my $self = shift;
                                             (http://en.wikipedia.org/wiki/Mutator_method)
   $self->{ name } = shift;
}
sub get_name {
   my $self = shift;
   return $self->{ name };
}
1;
A Simple Class
#!/usr/bin/perl

use strict;
use warnings;
                                                    Constructor
package myObj;                     (http://en.wikipedia.org/wiki/Constructor_%28computer_science%29)

sub new {
   my $class = shift;
   my %args = @_;
   my $self = {};
   $self->{ name } = $args{ name   } || 'default';
   return bless $self, $class;
}
                                                         Mutator
sub set_name {
   my $self = shift;
                                             (http://en.wikipedia.org/wiki/Mutator_method)
   $self->{ name } = shift;
}
sub get_name {
                                                    Accessor
   my $self = shift;
   return $self->{ name };                   (http://en.wikipedia.org/wiki/Accessor)
}
1;
Using A Simple Class
#!/usr/bin/perl

use strict;
use warnings;

use myObj;

...
Using A Simple Class
#!/usr/bin/perl

use strict;
                                                Calling the
use warnings;

                                               Constructor
use myObj;

my $obj = myObj->new( name => 'My Object' );

...
Using A Simple Class
#!/usr/bin/perl

use strict;
                                                Calling the
use warnings;

                                               Constructor
use myObj;

my $obj = myObj->new( name => 'My Object' );

my $objName = $obj->get_name();

...
                                               Calling the
                                               Accessor
Using A Simple Class
#!/usr/bin/perl

use strict;
                                                Calling the
use warnings;

                                               Constructor
use myObj;

my $obj = myObj->new( name => 'My Object' );

my $objName = $obj->get_name();

my $new_name = 'Your Object' );
                                               Calling the
                                               Accessor
$obj->set_name( $new_name );


                                               Calling the
                                                Mutator
Testing A Simple Class
#!/usr/bin/perl

use strict;
use warnings;

use Test::More
Testing A Simple Class
#!/usr/bin/perl

                             It's fine to start
use strict;
use warnings;
                              out without a
use Test::More 'no_plan';
                                testing plan
                            (number of tests
                                  to run)
Testing A Simple Class
#!/usr/bin/perl

use strict;
use warnings;
                               Make sure
                                you can
use Test::More 'no_plan';

                               quot;usequot; the
BEGIN { use_ok( 'myObj' ); }

                                 object
Testing A Simple Class
#!/usr/bin/perl

use strict;
                                              Make sure you
use warnings;

                                              can instantiate
use Test::More 'no_plan';
                                              the object (call
BEGIN { use_ok( 'myObj' ); }
                                            the constructor)
ok( my $obj1 = myObj->new( name => 'test1' ),
    quot;can create a myObj specifying valuesquot; );
Testing A Simple Class
#!/usr/bin/perl

use strict;
use warnings;

use Test::More 'no_plan';

BEGIN { use_ok( 'myObj' ); }
                                               Make sure your
ok( my $obj1 = myObj->new( name => 'test1'   ),
                                             ); instantiated
    quot;can create a myObj specifying valuesquot;
isa_ok( $obj1, 'myObj' );
                                               object quot;isaquot; type
                                                of object you
                                                   created
Testing A Simple Class
#!/usr/bin/perl

use strict;
use warnings;

use Test::More 'no_plan';

BEGIN { use_ok( 'myObj' ); }

ok( my $obj1 = myObj->new( name => 'test1' ),
    quot;can create a myObj specifying valuesquot; );
isa_ok( $obj1, 'myObj' );

ok( my $obj2 = myObj->new(), quot;can create a myObj not
specifying valuesquot; );
                                                  Instantiate
                                                another object
Testing A Simple Class
#!/usr/bin/perl

use strict;
use warnings;

use Test::More 'no_plan';

BEGIN { use_ok( 'myObj' ); }

ok( my $obj1 = myObj->new( name => 'test1' ),
    quot;can create a myObj specifying valuesquot; );
isa_ok( $obj1, 'myObj' );

ok( my $obj2 = myObj->new(), quot;can create a myObj not
specifying valuesquot; );
                                            Make sure the
isa_ok( $obj2, 'myObj' );
                                           new object quot;isaquot;
                                           quot;myObjquot; object
Testing A Simple Class
                                              Test using the
#!/usr/bin/perl

                                             mutator of the
use strict;
use warnings;
                                            name property of
use Test::More 'no_plan';
                                                the object
BEGIN { use_ok( 'myObj' ); }

ok( my $obj1 = myObj->new( name => 'test1' ),
    quot;can create a myObj specifying valuesquot; );
isa_ok( $obj1, 'myObj' );

ok( my $obj2 = myObj->new(), quot;can create a myObj not
specifying valuesquot; );
isa_ok( $obj2, 'myObj' );

ok( $obj2->set_name( 'test1' ), quot;can set namequot; );
Testing A Simple Class
#!/usr/bin/perl
                                             Make sure the
use strict;
                                            accessor returns
use warnings;

                                            the value we just
use Test::More 'no_plan';

                                                   set
BEGIN { use_ok( 'myObj' ); }

ok( my $obj1 = myObj->new( name => 'test1' ),
    quot;can create a myObj specifying valuesquot; );
isa_ok( $obj1, 'myObj' );

ok( my $obj2 = myObj->new(), quot;can create a myObj not
specifying valuesquot; );
isa_ok( $obj2, 'myObj' );

ok( $obj2->set_name( 'test1' ), quot;can set namequot; );
ok( 'test1' eq $obj2->get_name(), quot;can get namequot; );
Testing A Simple Class
#!/usr/bin/perl
                                            Perform a quot;deepquot;
use strict;
                                              comparison of
use warnings;

                                             the two objects
use Test::More 'no_plan';

                                                (created in
BEGIN { use_ok( 'myObj' ); }

                                           ), different ways)
ok( my $obj1 = myObj->new( name => 'test1'
    quot;can create a myObj specifying valuesquot; );
isa_ok( $obj1, 'myObj' );

ok( my $obj2 = myObj->new(), quot;can create a myObj not
specifying valuesquot; );
isa_ok( $obj2, 'myObj' );

ok( $obj2->set_name( 'test1' ), quot;can set namequot; );
ok( 'test1' eq $obj2->get_name(), quot;can get namequot; );

is_deeply( $obj1, $obj2, quot;obj1 seems deeply similar to
obj2quot; );
Testing A Simple Class
#!/usr/bin/perl

use strict;
use warnings;
                                              Specify the
use Test::More tests => 8;
                                            number of tests
                                            we intend to run
BEGIN { use_ok( 'myObj' ); }

ok( my $obj1 = myObj->new( name => 'test1' ),
    quot;can create a myObj specifying valuesquot; );
isa_ok( $obj1, 'myObj' );

ok( my $obj2 = myObj->new(), quot;can create a myObj not
specifying valuesquot; );
isa_ok( $obj2, 'myObj' );

ok( $obj2->set_name( 'test1' ), quot;can set namequot; );
ok( 'test1' eq $obj2->get_name(), quot;can get namequot; );

is_deeply( $obj1, $obj2, quot;obj1 seems deeply similar to
obj2quot; );
Testing A Simple Class
                         Output:
$ prove -v testobj.t
testobj....1..8
ok 1 - use myObj;
ok 2 - can create a myObj specifying values
ok 3 - The object isa myObj
ok 4 - can create a myObj not specifying values
ok 5 - The object isa myObj
ok 6 - can set name
ok 7 - can get name
ok 8 - obj1 seems deeply similar to obj2
ok
All tests successful.
Files=1, Tests=8, 0 wallclock secs ( 0.02 cusr +   0.01 csys
= 0.03 CPU)
That's
great
but...
how does
that help
 me? :-|
Testing Zfml
$ cat testindex.t
...
BEGIN { use_ok( 'index.zfml' ) };
...

$ prove testindex.t
testindex....
#    Failed test 'use index.zfml;'
#    in testindex.t at line 8.
#      Tried to use 'index.zfml'.
#      Error: syntax error at (eval 3) line 2, near quot;use index.quot;
# Looks like you failed 1 test of 1.
testindex....dubious
         Test returned status 1 (wstat 256, 0x100)
DIED. FAILED test 1
         Failed 1/1 tests, 0.00% okay
Failed Test Stat Wstat Total Fail List of Failed
---------------------------------------------------------------------
testindex.t     1   256     1     11
Failed 1/1 test scripts. 1/1 subtests failed.
Files=1, Tests=1, 0 wallclock secs ( 0.03 cusr + 0.01 csys = 0.04
CPU)
Failed 1/1 test programs. 1/1 subtests failed.
Ensuring
your code is
   high*
  quality**
* for some values of high
** for some values of quality
Introducing
Perl::Critic and perlcritic
Perl::Critic(3)   User Contributed Perl Documentation   Perl::Critic(3)



NAME
       Perl::Critic - Critique Perl source code for best-practices

SYNOPSIS
   use Perl::Critic;
   my $file = shift;
   my $critic = Perl::Critic->new();
   my @violations = $critic->critique($file);
   print @violations;

DESCRIPTION
       Perl::Critic is an extensible framework for creating and
       applying coding standards to Perl source code. Essentially, it
       is a static source code analysis engine. Perl::Critic is
       distributed with a number of Perl::Critic::Policy modules that
       attempt to enforce various coding guidelines. Most Policy
       modules are based on Damian Conway's book Perl Best Practices.
Introducing
Perl::Critic and perlcritic
PERLCRITIC(1)     User Contributed Perl Documentation    PERLCRITIC(1)



NAME
       quot;perlcriticquot; - Command-line interface to critique Perl source

SYNOPSIS
   perlcritic [-12345 | -severity number] [-noprofile | -profile file]
        [-top [ number ]] [-include pattern] [-exclude pattern]
        [-theme expression] [-verbose number | format] [-list]
        [-only | -noonly] [-force | -noforce] [-nocolor] [-Version]
        [-help] [-man] [-quiet] [FILE | DIRECTORY | STDIN]

DESCRIPTION
       quot;perlcriticquot; is a Perl source code analyzer. It is the
       executable front-end to the Perl::Critic engine, which attempts
       to identify awkward, hard to read, error-prone, or
       unconventional constructs in your code. Most of the rules are
       based on Damian Conway's book Perl Best Practices.
Don't worry, it's all
   in perldoc.
Working with perlcritic
$ perlcritic -1 myObj.pm
RCS keywords $Id$ not found at line 1, column 1. See page 441 of PBP.
(Severity: 2)
RCS keywords $Revision$, $HeadURL$, $Date$ not found at line 1, column 1. See
page 441 of PBP. (Severity: 2)
RCS keywords $Revision$, $Source$, $Date$ not found at line 1, column 1. See
page 441 of PBP. (Severity: 2)
No quot;VERSIONquot; variable found at line 1, column 1. See page 404 of PBP.
(Severity: 2)
Code is not tidy at line 1, column 1. See page 33 of PBP. (Severity: 1)
Subroutine does not end with quot;returnquot; at line 16, column 1. See page 197 of
PBP. (Severity: 4)
Working with perlcritic
$ perlcritic -1 myObj.pm
RCS keywords $Id$ not found at line 1, column 1. See page 441 of PBP.
(Severity: 2)
RCS keywords $Revision$, $HeadURL$, $Date$ not found at line 1, column 1. See
page 441 of PBP. (Severity: 2)
RCS keywords $Revision$, $Source$, $Date$ not found at line 1, column 1. See
page 441 of PBP. (Severity: 2)
No quot;VERSIONquot; variable found at line 1, column 1. See page 404 of PBP.
(Severity: 2)
Code is not tidy at line 1, column 1. See page 33 of PBP. (Severity: 1)
Subroutine does not end with quot;returnquot; at line 16, column 1. See page 197 of
PBP. (Severity: 4)
Working with .perlcriticrc
$ cat .perlcriticrc
[-Miscellanea::RequireRcsKeywords]
[-Modules::RequireVersionVar]
Working with perlcritic
$ perlcritic -1 myObj.pm
Code is not tidy at line 1, column 1. See page 33 of PBP. (Severity: 1)
Subroutine does not end with quot;returnquot; at line 16, column 1. See page 197 of
PBP. (Severity: 4)
Working with perlcritic
$ perlcritic -1 myObj.pm
Code is not tidy at line 1, column 1. See page 33 of PBP. (Severity: 1)
Subroutine does not end with quot;returnquot; at line 16, column 1. See page 197 of
PBP. (Severity: 4)
Working with perlcritic

sub set_name {
  my $self = shift;
  $self->{ name } = shift;
  return;
}
Working with perlcritic
                         Output:
$ prove -v testobj.t
testobject....1..8
#   Failed test 'can set name'
#   in testobject.t at line 17.
# Looks like you failed 1 test of 8.
ok 1 - use myObj;
ok 2 - can create a myObj specifying values
ok 3 - The object isa myObj
ok 4 - can create a myObj not specifying values
ok 5 - The object isa myObj
not ok 6 - can set name
ok 7 - can get name
ok 8 - obj1 seems deeply similar to obj2
Files=1, Tests=8, 0 wallclock secs ( 0.03 cusr +   0.01 csys
= 0.04 CPU)
Failed 1/1 test programs. 1/8 subtests failed.
Working with perlcritic
#!/usr/bin/perl

use strict;
use warnings;

use Test::More tests => 8;

BEGIN { use_ok( 'myObj' ); }

ok( my $obj1 = myObj->new( name => 'test1' ),
                                                 The mutator
    quot;can create a myObj specifying valuesquot; );
isa_ok( $obj1, 'myObj' );
                                               shouldn't return
                                                    a value!
ok( my $obj2 = myObj->new(), quot;can create a   myObj not
specifying valuesquot; );
isa_ok( $obj2, 'myObj' );

ok( ! $obj2->set_name( 'test1' ), quot;can set namequot; );
ok( 'test1' eq $obj2->get_name(), quot;can get namequot; );

is_deeply( $obj1, $obj2, quot;obj1 seems deeply similar to
obj2quot; );
Working with perlcritic
                         Output:
$ prove -v testobj.t
testobj....1..8
ok 1 - use myObj;
ok 2 - can create a myObj specifying values
ok 3 - The object isa myObj
ok 4 - can create a myObj not specifying values
ok 5 - The object isa myObj
ok 6 - can set name
ok 7 - can get name
ok 8 - obj1 seems deeply similar to obj2
ok
All tests successful.
Files=1, Tests=8, 0 wallclock secs ( 0.02 cusr +   0.01 csys
= 0.03 CPU)
Perl::Critic and Zfml
$ perlcritic -1 index.zfml
Code not contained in explicit package at line 1, column 1.
Violates encapsulation. (Severity: 4)
Code before strictures are enabled at line 1, column 1.                                                                                    See page
429 of PBP. (Severity: 5)
Code before warnings are enabled at line 1, column 1.                                                                              See page 431
of PBP. (Severity: 4)
Mixed high and low-precedence booleans at line 1, column 1.                                                                           See page 70
of PBP. (Severity: 4)
Useless interpolation of literal string at line 1, column 23.                                                                      See page 51 of
PBP. (Severity: 1)
Useless interpolation of literal string at line 1, column 64.                                                                  See page 51 of PBP.
(Severity: 1)
Useless interpolation of literal string at line 2, column 13.                                                            See page 51 of PBP.
(Severity: 1)
Hard tabs used at line 4, column 60.                                See page 20 of PBP.                   (Severity: 3)
Code not contained in explicit package at line 5, column 54.                                         Violates encapsulation.      (Severity: 4)
Mixed high and low-precedence booleans at line 5, column 54. See page 70 of PBP. (Severity: 4)
Hard tabs used at line 5, column 72. See page 20 of PBP. (Severity: 3)
Useless interpolation of literal string at line 5, column 72. See page 51 of PBP. (Severity: 1)
Useless interpolation of literal string at line 6, column 26.                     See page 51 of PBP.          (Severity: 1)
Postfix control quot;forquot; used at line 6, column 164. See page 96 of PBP. (Severity: 1)
Hard tabs used at line 6, column 259. See page 20 of PBP. (Severity: 3)
Useless interpolation of literal string at line 6, column 259. See page 51 of PBP. (Severity: 1)
Useless interpolation of literal string at line 7, column 23. See page 51 of PBP. (Severity: 1)
Postfix control quot;forquot; used at line 7, column 261. See page 96 of PBP. (Severity: 1)
Postfix control quot;forquot; used at line 7, column 393. See page 96 of PBP. (Severity: 1)
Postfix control quot;forquot; used at line 7, column 568. See page 96 of PBP. (Severity: 1)
Postfix control quot;forquot; used at line 7, column 587. See page 96 of PBP. (Severity: 1)
Hard tabs used at line 7, column 678. See page 20 of PBP. (Severity: 3)
Useless interpolation of literal string at line 7, column 678. See page 51 of PBP. (Severity: 1)
Hard tabs used at line 8, column 24. See page 20 of PBP. (Severity: 3)
Useless interpolation of literal string at line 33, column 22. See page 51 of PBP. (Severity: 1)
Mismatched operator at line 34, column 15. Numeric/string operators and operands should match. (Severity: 3)
Useless interpolation of literal string at line 34, column 45. See page 51 of PBP. (Severity: 1)
Useless interpolation of literal string at line 34, column 64. See page 51 of PBP. (Severity: 1)
Mismatched operator at line 34, column 86. Numeric/string operators and operands should match. (Severity: 3)
Useless interpolation of literal string at line 34, column 186. See page 51 of PBP. (Severity: 1)
Hard tabs used at line 34, column 209. See page 20 of PBP. (Severity: 3)
Useless interpolation of literal string at line 34, column 209. See page 51 of PBP. (Severity: 1)
Working with perlcritic
$ perlcritic -1 myObj.pm
Code is not tidy at line 1, column 1.   See page 33 of PBP.   (Severity: 1)
Working with perlcritic
$ perlcritic -1 myObj.pm
Code is not tidy at line 1, column 1.   See page 33 of PBP.   (Severity: 1)
Working with perltidy
PERLTIDY(1)   User Contributed Perl Documentation   PERLTIDY(1)



NAME
       perltidy - a perl script indenter and reformatter

SYNOPSIS
       perltidy [ options ] file1 file2 file3 ...
                (output goes to file1.tdy, file2.tdy, ...)
       perltidy [ options ] file1 -o outfile
       perltidy [ options ] file1 -st >outfile
       perltidy [ options ] <infile >outfile
Working with perltidy
$ cat .perltidyrc
-l=78    # Max line width is 78 cols
-i=2     # Indent level is 2 cols
-ci=2    # Continuation indent is 2 cols
-lp      # line up parenthesis
-vt=2    # Maximal vertical tightness
-vtc=1 # medium vertical something tightness
-cti=1 # No extra indentation for closing brackets
-pt=1    # Medium parenthesis tightness
-bt=1    # Medium brace tightness
-sbt=1 # Medium square bracket tightness
-bbt=1 # Medium block brace tightness
-nsfs    # No space before semicolons
-nolq    # Don't outdent long quoted strings
-wbb=quot;% + - * / x != == >= <= =~ !~ < > | & >= < = **= += *= &= <<= &&= -= /= |=
>>= ||= .= %= ^= x=quot;
         # Break before all operators
-nsak=quot;my local our if elsif until unless while for foreach return switch case
given whenquot;
-bar
-cab=3
-wrs=quot;! ,quot; # want right space after these tokens
-wls=quot;!quot;    # want left space after !
Screencast
demonstration
  removed
   for PDF
ZFML
$ perltidy index.zfml


There is no previous '?' to match a ':' on line 4
4: <title>AcmeCorp: Widgets, Gadgets and Doodads</title>
                 ^

5: <meta http-equiv=quot;content-typequot; content=quot;text/html;charset=iso-8 ...
                    -------------- ^
found bareword where operator expected (previous token underlined)

5: ... ent=quot;text/html;charset=iso-8859-1quot; />
                                          -^
found > where term expected (previous token underlined)

7: <meta NAME=quot;keywordsquot; CONTENT=quot;AcmeCorp, widgets, gadgets ...
              ---------- ^
found bareword where operator expected (previous token underlined)

9: @import url(/AcmeCorp/templates/gateway85styles.css);
   ^
found Array where operator expected

Missing ';' above?

9: @import url(/AcmeCorp/templates/gateway85styles.css);
   ------- ^
found bareword where operator expected (previous token underlined)

9: @import url(/AcmeCorp/templates/gateway85styles.css);
               ---------^
found bareword where operator expected (previous token underlined)

Missing ';' above?


                                                                         to match a ':' on line 14
There is no previous '?'
                                                                            fix valid */
14: max-height: 140px; /* to
                                                                     ^
Missing ';' above?

                                                                            15
There is no previous '?' to match a ':' on line
15: padding: 12px; margin-top: 5px; border-top: 1px solid #e0e0e0;
           ^


There is no previous '?' to match a ':' on line 15
15: padding: 12px; margin-top: 5px; border-top: 1px solid #e0e0e0;
                             ^

There is no previous '?' to match a ':' on line 15
15: padding: 12px; margin-top: 5px; border-top: 1px solid #e0e0e0;
                                              ^
Working with perlcritic
$ perlcritic -1 myObj.pm
myObj.pm source OK
That's
great
but...
how does
that help
 me? :-
Ensuring
  your tests
fully exercise
  your code
Introducing
                  Devel::Cover
Devel::Cover(3)     Perl Documentation     Devel::Cover(3)



NAME
       Devel::Cover - Code coverage metrics for Perl

SYNOPSIS
  perl -MDevel::Cover yourprog args
  cover

  perl -MDevel::Cover=-db,cover_db,-coverage,statement,time
        yourprog args

       To test an uninstalled module:

  cover -delete
  HARNESS_PERL_SWITCHES=-MDevel::Cover make test
  cover
huh?
Introducing
                   Devel::Cover
$ perl -MDevel::Cover testobj.t
1..8
ok 1 - use myObj;
... # some Devel::Cover output snipped
ok 2 - can create a myObj specifying values
ok 3 - The object isa myObj
ok 4 - can create a myObj not specifying values
ok 5 - The object isa myObj
ok 6 - can set name
ok 7 - can get name
ok 8 - obj1 seems deeply similar to obj2
Devel::Cover: Writing coverage database to /Users/kentcowgill/cover_db/runs/
1169095517.23575.48192
---------------------------- ------ ------ ------ ------ ------ ------ ------
File                           stmt    bran  cond    sub    pod   time total
---------------------------- ------ ------ ------ ------ ------ ------ ------
myObj.pm                      100.0     n/a 100.0 100.0     n/a   23.5 100.0
testobj.t                     100.0     n/a   n/a 100.0     n/a   76.5 100.0
Total                         100.0     n/a 100.0 100.0     n/a 100.0 100.0
---------------------------- ------ ------ ------ ------ ------ ------ ------
Introducing
                         cover
$ cover
Reading database from /Users/kentcowgill/cover_db


---------------------------- ------ ------ ------ ------ ------ ------ ------
File                           stmt   bran   cond    sub    pod   time total
---------------------------- ------ ------ ------ ------ ------ ------ ------
myObj.pm                      100.0    n/a 100.0 100.0      n/a   23.5 100.0
testobj.t                     100.0    n/a    n/a 100.0     n/a   76.5 100.0
Total                         100.0    n/a 100.0 100.0      n/a 100.0 100.0
---------------------------- ------ ------ ------ ------ ------ ------ ------

Writing HTML output to /Users/kentcowgill/cover_db/coverage.html ...
done.
Introducing
                         cover
$ cover
Reading database from /Users/kentcowgill/cover_db


---------------------------- ------ ------ ------ ------ ------ ------ ------
File                           stmt   bran   cond    sub    pod   time total
---------------------------- ------ ------ ------ ------ ------ ------ ------
myObj.pm                      100.0    n/a 100.0 100.0      n/a   23.5 100.0
testobj.t                     100.0    n/a    n/a 100.0     n/a   76.5 100.0
Total                         100.0    n/a 100.0 100.0      n/a 100.0 100.0
---------------------------- ------ ------ ------ ------ ------ ------ ------

Writing HTML output to /Users/kentcowgill/cover_db/coverage.html ...
done.
html?
 :-D
Tweaking
                       Devel::Cover
Devel::Cover(3)        Perl Documentation    Devel::Cover(3)

OPTIONS

...

          -ignore RE   - Set REs of files to ignore
          +ignore RE   - Append to REs of files to ignore.
Tweaking
                   Devel::Cover
$ perl -MDevel::Cover=+ignore,.*.t testobj.t
1..8
ok 1 - use myObj;
... # Devel::Cover output snipped
Ignoring packages matching:
     /Devel/Cover[./]
     .*.t
... # Devel::Cover output snipped
ok 2 - can create a myObj specifying values
ok 3 - The object isa myObj
ok 4 - can create a myObj not specifying values
ok 5 - The object isa myObj
ok 6 - can set name
ok 7 - can get name
ok 8 - obj1 seems deeply similar to obj2
Devel::Cover: Writing coverage database to /Users/kentcowgill/cover_db/runs/
1169096938.23619.10353
---------------------------- ------ ------ ------ ------ ------ ------ ------
File                           stmt   bran   cond    sub    pod   time total
---------------------------- ------ ------ ------ ------ ------ ------ ------
myObj.pm                      100.0    n/a 100.0 100.0      n/a 100.0 100.0
Total                         100.0    n/a 100.0 100.0      n/a 100.0 100.0
---------------------------- ------ ------ ------ ------ ------ ------ ------
what
happened
to prove?
Tweaking
                          prove
$ prove -MDevel::Cover=+ignore,.*.t testobj.t
Tweaking
                            prove
$ prove   -MDevel::Cover=+ignore,.*.t testobj.t
Unknown   option: M
Unknown   option: e
Unknown   option: e
Unknown   option: :
Unknown   option: :
Unknown   option: C
Unknown   option: o
Unknown   option: e
Unknown   option: =
Unknown   option: +
Unknown   option: i
Unknown   option: g
Unknown   option: n
Unknown   option: o
Unknown   option: e
Unknown   option: ,
Unknown   option: .
Unknown   option: *
Unknown   option: .
ouch!
Tweaking
                         prove
$ PERL5OPT=-MDevel::Cover=+ignore,.*.t prove -v testobj.t
testobj....1..8
ok 1 - use myObj;
ok 2 - can create a myObj specifying values
ok 3 - The object isa myObj
ok 4 - can create a myObj not specifying values
ok 5 - The object isa myObj
ok 6 - can set name
ok 7 - can get name
ok 8 - obj1 seems deeply similar to obj2
ok
All tests successful.
Files=1, Tests=8, 3 wallclock secs ( 3.18 cusr + 0.08 csys =   3.26 CPU)
Tweaking
                          prove
$ cover
Reading database from /Users/kentcowgill/cover_db


---------------------------- ------ ------ ------ ------ ------ ------ ------
File                           stmt   bran   cond    sub    pod   time total
---------------------------- ------ ------ ------ ------ ------ ------ ------
/usr/bin/prove                 73.7   43.8    0.0   46.7    n/a   98.0   61.1
myObj.pm                      100.0    n/a 100.0 100.0      n/a    2.0 100.0
Total                          78.0   43.8   40.0   60.0    n/a 100.0    66.9
---------------------------- ------ ------ ------ ------ ------ ------ ------


Writing HTML output to /Users/kentcowgill/cover_db/coverage.html ...
done.
uh, was
that 'prove'
 in there?
Tweaking
            prove
$ cover
Reading database from /Users/kentco

---------------------------- ------
File                           stmt
---------------------------- ------
/usr/bin/prove                 73.7
myObj.pm                      100.0
Tweaking
            prove
$ cover
Reading database from /Users/kentco

---------------------------- ------
File                           stmt
---------------------------- ------
/usr/bin/prove                 73.7
myObj.pm                      100.0
yeah :(
Tweaking
                          prove
$ PERL5OPT=-MDevel::Cover=+ignore,.*.t,+ignore,prove prove -v testobj.t
testobj....1..8
ok 1 - use myObj;
ok 2 - can create a myObj specifying values
ok 3 - The object isa myObj
ok 4 - can create a myObj not specifying values
ok 5 - The object isa myObj
ok 6 - can set name
ok 7 - can get name
ok 8 - obj1 seems deeply similar to obj2
ok
All tests successful.
Files=1, Tests=8, 3 wallclock secs ( 3.18 cusr + 0.08 csys = 3.26 CPU)
Saving Some Typing
$ cat Makefile
OPENCMD = open
BROWSER = /Applications/Safari.app
clean:
        cover -delete
test:
        prove testobj.t
cover:
        make clean
        PERL5OPT=-MDevel::Cover=+ignore,.*.t,+ignore,prove make test 2>&1
        cover
        make report
report:
        $(OPENCMD) $(BROWSER) cover_db/coverage.html
Saving Some Typing
$ make cover
make clean
cover -delete
Deleting database /Users/kentcowgill/cover_db
PERL5OPT=-MDevel::Cover=+ignore,.*.t,+ignore,prove make test 2>&1
prove testobj.t
testobj....1..8
testobj....ok
All tests successful.
Files=1, Tests=8, 7 wallclock secs ( 3.22 cusr + 0.09 csys = 3.31 CPU)
cover
Reading database from /Users/kentcowgill/cover_db

---------------------------- ------ ------ ------ ------ ------ ------ ------
File                           stmt   bran   cond    sub    pod   time total
---------------------------- ------ ------ ------ ------ ------ ------ ------
myObj.pm                      100.0    n/a 100.0 100.0      n/a 100.0 100.0
Total                         100.0    n/a 100.0 100.0      n/a 100.0 100.0
---------------------------- ------ ------ ------ ------ ------ ------ ------

Writing HTML output to /Users/kentcowgill/cover_db/coverage.html ... done.
make report
open /Applications/Safari.app cover_db/coverage.html
100% yay!
  8-D
Introducing
                      Test::ZFML
Test::Zfml(3)       User Contributed Perl Documentation        Test::Zfml(3)



NAME
       Test::ZFML - Custom Test:: module built specifically for parsing
       ZFML.

DESCRIPTION
       Long has it been lamented that AcmeCorp's implementation of ZFML (and
       who knows what that really stands for) is unmaintainable, and more
       importantly untestable.

       No more.

       Test::ZFML attempts to make the unparseable parseable, the unmaintain-
       able maintainable, and the untestable testable. It does this by
       implementing it's own mini ZFML parser and places chunks of ZFML
       inside their own package, surrounded by their own subroutines which
       have defined inputs and testable outputs.
Using
                    Test::ZFML
#!/usr/bin/perl

use strict;
use warnings;

use Test::More qw/no_plan/;
use Test::ZFML;
use ZFML;

my $p = ZFML->new();

my $file = q[test.zfml];

    load_ok(    $file,   quot;Loaded ZFML file $filequot; );
   parse_ok(    $file,   quot;Parsed ZFML file $filequot; );
evaluate_ok(    $file,   quot;Evaluated ZFML file $filequot; );
critique_ok(    $file,   quot;Critiqued ZFML file $filequot; );
That's
great
but...
How
about a
demo?
Screencast
demonstration
  removed
   for PDF
How'd you
 do that?
Test::Builder::Module
NAME
       Test::Builder::Module - Base class for test
       modules

SYNOPSIS
           # Emulates Test::Simple
           package Your::Module;

           my $CLASS = __PACKAGE__;

           use base 'Test::Builder::Module';
           @EXPORT = qw(ok);

           sub ok ($;$) {
               my $tb = $CLASS->builder;
               return $tb->ok(@_);
           }

           1;
Test::Builder::Module
NAME
       Test::Builder::Module - Base class for test
       modules

SYNOPSIS
           # Emulates Test::Simple
           package Your::Module;

           my $CLASS = __PACKAGE__;

           use base 'Test::Builder::Module';

                                               Start
           @EXPORT = qw(ok);


                                               Here
           sub ok ($;$) {
               my $tb = $CLASS->builder;
               return $tb->ok(@_);
           }

           1;
Test::ZFML
package Test::ZFML;

use strict;
use warnings;

use Perl::Critic qw/critique/;
use Test::HTML::Lint ();
use Carp;

use lib '/Users/kentcowgill/acmecorp/lib';
use ZFML;

use vars qw/$VERSION @ISA @EXPORT %EXPORT_TAGS $TODO/;

use base q/Test::Builder::Module/;

@EXPORT = qw/load_ok parse_ok evaluate_ok critique_ok
          replace_ok contains_ok lacks_ok html_ok/;
Test::ZFML
                                         Standard
package Test::ZFML;


                                           stuff
use strict;
use warnings;

use Perl::Critic qw/critique/;
use Test::HTML::Lint ();
use Carp;

use lib '/Users/kentcowgill/acmecorp/lib';
use ZFML;

use vars qw/$VERSION @ISA @EXPORT %EXPORT_TAGS $TODO/;

use base q/Test::Builder::Module/;

@EXPORT = qw/load_ok parse_ok evaluate_ok critique_ok
          replace_ok contains_ok lacks_ok html_ok/;
Test::ZFML

# global regexes
my $includeparse
  = qr/<!--s+__(TEMPLATE[sA-Z]*?)__s*?n(.*?)n-->/s;
my $htmlparse
  = qr/<!--s+__([ A-Z_]+)__s*n(.*?)n-->/s;
my $zfmlparse
  = qr/(<!--s+__(?:EVAL|INIT|POST) [^ ]+__s*n.*?n-->)/s;
my $zfmlextract
  = qr/<!--s+__(EVAL|INIT|POST) ([^ ]+)__s*n(.*?)n-->/s;
Test::ZFML
                                                 Icky
                                               regexes
# global regexes
my $includeparse
  = qr/<!--s+__(TEMPLATE[sA-Z]*?)__s*?n(.*?)n-->/s;
my $htmlparse
  = qr/<!--s+__([ A-Z_]+)__s*n(.*?)n-->/s;
my $zfmlparse
  = qr/(<!--s+__(?:EVAL|INIT|POST) [^ ]+__s*n.*?n-->)/s;
my $zfmlextract
  = qr/<!--s+__(EVAL|INIT|POST) ([^ ]+)__s*n(.*?)n-->/s;
Test::ZFML
sub load_ok {
  my $desc;
  ( $file_to_test, $desc ) = @_;
  _load_file( $file_to_test );
  $zfml_filestate = LOADED;
  my $tb = Test::ZFML->builder;

    # minimal (testable) sanity check, ensures that
    # $file_contents has contents
    $tb->ok( $file_contents, $desc );
}
Test::ZFML
                                            Load the
                                              file
sub load_ok {
  my $desc;
  ( $file_to_test, $desc ) = @_;
  _load_file( $file_to_test );
  $zfml_filestate = LOADED;
  my $tb = Test::ZFML->builder;

    # minimal (testable) sanity check, ensures that
    # $file_contents has contents
    $tb->ok( $file_contents, $desc );
}
Test::ZFML

sub _load_file {
  $file_to_test = shift;
  _get_contents( $file_contents, $file_to_test );
  push @vars, grep { ! /^$(ENV|inp)/ }
                   $file_contents =~ m/($[A-Z_]+)/g;
  return;
}
Test::ZFML

sub _load_file {
  $file_to_test = shift;
  _get_contents( $file_contents, $file_to_test );
  push @vars, grep { ! /^$(ENV|inp)/ }
                   $file_contents =~ m/($[A-Z_]+)/g;
  return;
}


                                        Just does
                                         a slurp
Test::ZFML
sub parse_ok {
  my( $file, $p, $desc ) = @_;
  croak 'wrong file' if $file ne $file_to_test;
  croak 'You must load the file first' if
        $zfml_filestate != LOADED;
  _parse_file( $p );
  $zfml_filestate = PARSED;
  my $tb = Test::ZFML->builder;

    # minimal (testable) sanity check, ensures that
    # $stuff got stuffed
    $tb->ok( $stuff, $desc );
}
Test::ZFML
sub parse_ok {
  my( $file, $p, $desc ) = @_;
  croak 'wrong file' if $file ne $file_to_test;
  croak 'You must load the file first' if
        $zfml_filestate != LOADED;
  _parse_file( $p );
  $zfml_filestate = PARSED;
  my $tb = Test::ZFML->builder;

    # minimal (testable) sanity check, ensures that
    # $stuff got stuffed
    $tb->ok( $stuff, $desc );

                                             Parse the
}



                                                file
Test::ZFML
sub _parse_file {
  my( $p ) = @_;

  # grab the executable hunks of perl code
  my @zfml = $file_contents =~ /$zfmlparse/g;
  $file_contents =~ s/$zfmlparse//g;

  # grab the hunks that are responsible for templates
  my %includes = $file_contents =~ /$includeparse/g;
  $file_contents =~ s/$includeparse//g;

  # finally, grab the hunks that get turned into HTML
  my %zfmlvars = $file_contents =~ /$htmlparse/g;
  $file_contents =~ s/$htmlparse//g;

 ...
Test::ZFML                         Really
                                                parse it
sub _parse_file {
  my( $p ) = @_;

  # grab the executable hunks of perl code
  my @zfml = $file_contents =~ /$zfmlparse/g;
  $file_contents =~ s/$zfmlparse//g;

  # grab the hunks that are responsible for templates
  my %includes = $file_contents =~ /$includeparse/g;
  $file_contents =~ s/$includeparse//g;

  # finally, grab the hunks that get turned into HTML
  my %zfmlvars = $file_contents =~ /$htmlparse/g;
  $file_contents =~ s/$htmlparse//g;

 ...
Test::ZFML
 ...

 for my $key( keys %includes ){
   # process all the include files :)
   my $tb = Test::Zfml->builder;
   $tb->ok( _get_includes( $key, $includes{ $key },
             $file_to_test ),
             quot;Included $key file $includes{ $key }quot;
          );
 }

 for my $key( keys %zfmlvars ){
   $p->var->{$key} = $zfmlvars{$key};
 }

  for my $zfml( @zfml ){
    if( $zfml =~ m/$zfmlextract/s ) {
      push @{ $stuff->{$1} }, { $2 => $3 };
    }
  }
} # end
Test::ZFML
 ...

 for my $key( keys %includes ){
   # process all the include files :)
   my $tb = Test::Zfml->builder;
   $tb->ok( _get_includes( $key, $includes{ $key },
             $file_to_test ),
             quot;Included $key file $includes{ $key }quot;
          );
 }


                                               Chug
 for my $key( keys %zfmlvars ){
   $p->var->{$key} = $zfmlvars{$key};
 }

                                              through
  for my $zfml( @zfml ){

                                                 it
    if( $zfml =~ m/$zfmlextract/s ) {
      push @{ $stuff->{$1} }, { $2 => $3 };
    }
  }
} # end
Test::ZFML
sub _get_includes {
  my( $name, $file, $fromfile ) = @_;
  my $filepath = quot;$webroot/$filequot;;

 if( $filepath =~ /$VERSION/ ){
   $filepath =~ s/$VERSION/$version/;
 }

 if( $filepath =~ /$LOCAL/ ){
   my $path = $fromfile;
   $path =~ s/^.+?/(.+)/[a-z.]+$/$version/$1/;
   $filepath =~ s/$LOCAL/$path/;
 }

  my $tb = Test::ZFML->builder();
  $tb->ok( -e $filepath,
    quot;Inlude/Template file ($filepath) Existsquot; );

...
Test::ZFML
                                            Process
sub _get_includes {
  my( $name, $file, $fromfile ) = @_;
  my $filepath = quot;$webroot/$filequot;;

                                           included
 if( $filepath =~ /$VERSION/ ){

                                             files
   $filepath =~ s/$VERSION/$version/;
 }

 if( $filepath =~ /$LOCAL/ ){
   my $path = $fromfile;
   $path =~ s/^.+?/(.+)/[a-z.]+$/$version/$1/;
   $filepath =~ s/$LOCAL/$path/;
 }

  my $tb = Test::ZFML->builder();
  $tb->ok( -e $filepath,
    quot;Inlude/Template file ($filepath) Existsquot; );

...
Test::ZFML
    ...

    open( my $tmp, '<', $filepath )
      or die quot;can't open include filequot;;
    my @file = <$tmp>;
    my $contents;
    for my $line ( @file ){
      $contents .= $line;
      if( $line =~ m/$([A-Z]+)s/ ){
        eval quot;$testzfml::$1 = 'dummy content'quot;;
      }
      if( $line =~ m/var->{'([A-Z_]+)'}/ ){
        eval quot;$testzfml::$1 = 'dummy content'quot;;
      }
    }
    my %includes = $contents =~ /$includeparse/g;
    for my $key( keys %includes ){
      _get_includes( $key, $includes{ $key }, $file );
    }
    close( $tmp );
}
Test::ZFML Evaluate,
    ...


                                            evaluate,
    open( my $tmp, '<', $filepath )
      or die quot;can't open include filequot;;
    my @file = <$tmp>;
                                            evaluate
    my $contents;
    for my $line ( @file ){
      $contents .= $line;
      if( $line =~ m/$([A-Z]+)s/ ){
        eval quot;$testzfml::$1 = 'dummy content'quot;;
      }
      if( $line =~ m/var->{'([A-Z_]+)'}/ ){
        eval quot;$testzfml::$1 = 'dummy content'quot;;
      }
    }
    my %includes = $contents =~ /$includeparse/g;
    for my $key( keys %includes ){
      _get_includes( $key, $includes{ $key }, $file );
    }
    close( $tmp );
}
Test::ZFML
sub evaluate_ok {
  my( $file, $p, $desc ) = @_;
  croak 'wrong file' if $file ne $file_to_test;
  croak 'You must parse the file first'
    if $zfml_filestate != PARSED;
  $zfml_filestate = EVALED;

    for my $hunk ( keys %{$stuff} ) {
      for my $evals ( @{ $stuff->{$hunk} } ) {
        for my $var ( keys %{$evals} ) {
          _evaluate_code( $p, $hunk, $var,
                          $evals->{$var}, $file, $desc );
        }
      }
    }

    # loads everything into memory for testing
    require $_ for @cov_files;    ## no critic
}
Test::ZFML
sub evaluate_ok {
  my( $file, $p, $desc ) = @_;
  croak 'wrong file' if $file ne $file_to_test;
  croak 'You must parse the file first'
    if $zfml_filestate != PARSED;
  $zfml_filestate = EVALED;

    for my $hunk ( keys %{$stuff} ) {
      for my $evals ( @{ $stuff->{$hunk} } ) {
        for my $var ( keys %{$evals} ) {
          _evaluate_code( $p, $hunk, $var,
                          $evals->{$var}, $file, $desc );
        }

                                                  Really
      }
    }

                                                 evaluate
    # loads everything into memory for testing
    require $_ for @cov_files;    ## no critic

                                                    it
}
Test::ZFML
sub _evaluate_code {
  my( $p, $eval_init, $name, $hunk, $file, $desc ) = @_;
  $file =~ s/.*/(.*)$/$1/;
  my $subname = quot;$eval_init$namequot;;
  $hunk = _wrap_hunk( $hunk, $subname );
  my $filename = quot;$file.$subnamequot;;
  my $tb = Test::ZFML->builder;

    # Writing the contents out to a file so I can run
    # the tests with Devel::Cover turned on.
    open my $cov, '>', quot;.$filenamequot;;
    print {$cov} $hunk;
    close $cov;
    push @cov_files, quot;.$filenamequot;;
    eval quot;require '.$filename';quot;;    ## no critic
    $tb->ok( ! $@, quot;$desc chunk ( $filename ) $@quot; );
    eval quot;testzfml::$subname( $p );quot;;
    die quot;eval failed - $@quot; if $@;
}
Test::ZFML
sub _evaluate_code {
  my( $p, $eval_init, $name, $hunk, $file, $desc ) = @_;
  $file =~ s/.*/(.*)$/$1/;
  my $subname = quot;$eval_init$namequot;;
  $hunk = _wrap_hunk( $hunk, $subname );
  my $filename = quot;$file.$subnamequot;;
  my $tb = Test::ZFML->builder;

    # Writing the contents out to a file so I can run
    # the tests with Devel::Cover turned on.
    open my $cov, '>', quot;.$filenamequot;;

                                                  Write
    print {$cov} $hunk;
    close $cov;

                                                out files
    push @cov_files, quot;.$filenamequot;;
    eval quot;require '.$filename';quot;;    ## no critic
    $tb->ok( ! $@, quot;$desc chunk ( $filename ) $@quot; );

                                                for Code
    eval quot;testzfml::$subname( $p );quot;;
    die quot;eval failed - $@quot; if $@;
}

                                                Coverage
Test::ZFML
sub _wrap_hunk {
  my( $hunk, $subname ) = @_;

 # HEREDOCs inside eval aren't recognizable as HEREDOCs.
 # This re-quotes HEREDOCs as q()/qq() strings.
 if( $hunk =~ m/<</s ) {

     # replace all intended quoting   chars with an HTML entity
     $hunk =~ s/|/&#124;/gs;
     $hunk =~ s/=s*              #   start of an assignment
                    <<            #   involving a heredoc
                       ('|quot;)      #   using a quoting delimiter

                      (?{ $1 eq q(quot;) ? 'qq' : 'q' })
                                  # which we'll remember in $^R

                      ([A-Z]+)    #   next the heredoc token
                      1;         #   close quoting delimiter
                      (.*?)n     #   the heredoc
                      2          #   closing heredoc token
               /= $^R|$3|;/gsx;   #   replace with quoting
 }

 ...
Test::ZFML
  sub _wrap_hunk {
    my( $hunk, $subname ) = @_;

   # HEREDOCs inside eval aren't recognizable as HEREDOCs.
   # This re-quotes HEREDOCs as q()/qq() strings.
   if( $hunk =~ m/<</s ) {

       # replace all intended quoting   chars with an HTML entity
       $hunk =~ s/|/&#124;/gs;
       $hunk =~ s/=s*              #   start of an assignment
                      <<            #   involving a heredoc
                         ('|quot;)      #   using a quoting delimiter

                        (?{ $1 eq q(quot;) ? 'qq' : 'q' })

 Wrap                               # which we'll remember in $^R



Heredocs
                        ([A-Z]+)    #   next the heredoc token
                        1;         #   close quoting delimiter
                        (.*?)n     #   the heredoc
                        2          #   closing heredoc token
                 /= $^R|$3|;/gsx;   #   replace with quoting
   }

   ...
Test::ZFML
    ...

    my $chunk;

   # wrap the hunk with its own package, strictures and
   # warnings enabled, a sigwarn handler that causes eval
   # errors ($@) to throw a test ok() error, and callable via a
   # subroutine call.
   $chunk = <<quot;EOCquot;;
package testzfml;
use strict;
use warnings;
use ZFML;
BEGIN { $SIG{'__WARN__'} = sub { die $_[0] } } ## no critic
sub $subname {
$hunk
}
1;
EOC

    return $chunk;
}
Test::ZFML
    ...

    my $chunk;

   # wrap the hunk with its own package, strictures and
   # warnings enabled, a sigwarn handler that causes eval
   # errors ($@) to throw a test ok() error, and callable via a
   # subroutine call.
   $chunk = <<quot;EOCquot;;
package testzfml;
use strict;
use warnings;
use ZFML;
BEGIN { $SIG{'__WARN__'} = sub { die $_[0] } } ## no critic
sub $subname {

                                           Wrap it in
$hunk
}


                                            it's own
1;
EOC



                                           namespace
    return $chunk;
}
Test::ZFML

sub critique_ok {
  my( $file, $desc ) = @_;
  croak 'wrong file' if $file ne $file_to_test;
  for my $hunk ( keys %{$stuff} ) {
    for my $evals ( @{ $stuff->{$hunk} } ) {
      for my $var ( keys %{$evals} ) {
        _critique_code( $hunk, $var, $evals->{$var}, $desc );
      }
    }
  }
}
Test::ZFML

sub critique_ok {
  my( $file, $desc ) = @_;
  croak 'wrong file' if $file ne $file_to_test;
  for my $hunk ( keys %{$stuff} ) {
    for my $evals ( @{ $stuff->{$hunk} } ) {
      for my $var ( keys %{$evals} ) {
        _critique_code( $hunk, $var, $evals->{$var}, $desc );
      }
    }
  }
}

                                             Critique
                                                it
Test::ZFML
sub _critique_code {
  my( $eval_init, $name, $hunk, $desc ) = @_;
  my $subname = quot;$eval_init$namequot;;
  my $problems = 0;

    $hunk = _wrap_hunk( $hunk, $subname );
    my $tb = Test::ZFML->builder;

    for my $violation ( critique( { -severity => 1,
                                    -verbose => 1 }, $hunk ) ){
      $tb->ok( ! $violation, quot;Critique problem: $violationquot; );
      $problems++;
    }
    $tb->ok( ! $problems, quot;$desc chunk ( $subname )quot; );
    return;
}
Test::ZFML
                                                 Report
sub _critique_code {

                                                violations
  my( $eval_init, $name, $hunk, $desc ) = @_;
  my $subname = quot;$eval_init$namequot;;
  my $problems = 0;

    $hunk = _wrap_hunk( $hunk, $subname );
    my $tb = Test::ZFML->builder;

    for my $violation ( critique( { -severity => 1,
                                    -verbose => 1 }, $hunk ) ){
      $tb->ok( ! $violation, quot;Critique problem: $violationquot; );
      $problems++;
    }
    $tb->ok( ! $problems, quot;$desc chunk ( $subname )quot; );
    return;
}
Test::ZFML
sub replace_ok {
  my( $file, $p, $desc ) = @_;
  croak 'wrong file' if $file ne $file_to_test;
  my $tb = Test::ZFML->builder;

    for my $var (@vars) {
      my $varname = $var;
      $varname =~ s/^$//;
      my $pname = $p->var->{$varname};
      $tb->ok( $p->var->{$varname}, quot;$varname found in $filequot; );
      $file_contents =~ s/Q$varE/$pname/g;
    }
    my %input = %{ $p->input };
    $file_contents =~ s/$(input{)'?([A-Za-z_]+)'?}/$$1$2}/g;
    eval quot;$file_contents = qq|$file_contents|;quot;;
}
Test::ZFML
                                                  Replace
sub replace_ok {
  my( $file, $p, $desc ) = @_;
  croak 'wrong file' if $file ne $file_to_test;

                                                   special
  my $tb = Test::ZFML->builder;

    for my $var (@vars) {

                                                  variables
      my $varname = $var;
      $varname =~ s/^$//;
      my $pname = $p->var->{$varname};
      $tb->ok( $p->var->{$varname}, quot;$varname found in $filequot; );
      $file_contents =~ s/Q$varE/$pname/g;
    }
    my %input = %{ $p->input };
    $file_contents =~ s/$(input{)'?([A-Za-z_]+)'?}/$$1$2}/g;
    eval quot;$file_contents = qq|$file_contents|;quot;;
}
Test::ZFML

sub contains_ok {
  my( $file, $p, $regex, $desc ) = @_;
  croak 'wrong file' if $file ne $file_to_test;
  $p->render();
  my $tb = Test::ZFML->builder;
  $tb->like( $file_contents, $regex, $desc );
}
Check            Test::ZFML
  its'
contents
  sub contains_ok {
    my( $file, $p, $regex, $desc ) = @_;
    croak 'wrong file' if $file ne $file_to_test;
    $p->render();
    my $tb = Test::ZFML->builder;
    $tb->like( $file_contents, $regex, $desc );
  }
Test::ZFML

sub lacks_ok {
  my( $file, $p, $regex, $desc ) = @_;
  croak 'wrong file' if $file ne $file_to_test;
  $p->render();
  my $tb = Test::ZFML->builder;
  $tb->unlike( $file_contents, $regex, $desc );
}
Test::ZFML

 sub lacks_ok {
   my( $file, $p, $regex, $desc ) = @_;
   croak 'wrong file' if $file ne $file_to_test;
   $p->render();
   my $tb = Test::ZFML->builder;
   $tb->unlike( $file_contents, $regex, $desc );
 }


Make sure it
doesn't have
specific bits
Test::ZFML

sub html_ok {
  my( $file, $desc ) = @_;
  croak 'wrong file' if $file ne $file_to_test;
  Test::HTML::Lint::html_ok( $file_contents, $desc );
  return;
}
Test::ZFML
Check the
 HTML
   sub html_ok {
     my( $file, $desc ) = @_;
     croak 'wrong file' if $file ne $file_to_test;
     Test::HTML::Lint::html_ok( $file_contents, $desc );
     return;
   }
Putting it all together

• Test::More
• prove
• Perl::Critic
• Devel::Cover
• Makefile
• Test::ZFML
The Test File
 Screencast
demonstration
  removed
   for PDF
The Makefile
Kent-Cowgills-Computer(~/acmecorp)$ cat Makefile
OPENCMD = open
BROWSER = /Applications/Safari.app
clean:
         cover -delete
test:
         prove t/
verbose:
         prove -v t/
shuffle:
         prove -s t/
cover:
         make clean
         PERL5OPT=-MDevel::Cover=+ignore,prove,+ignore,lib.*.pm,
+ignore,zfml.t make test
         cover
         make report
report:
         $(OPENCMD) $(BROWSER) cover_db/coverage.html
Running Tests

Kent-Cowgills-Computer(~/acmecorp)$ make test
prove t/
t/artdesign-zfml....ok
t/index-zfml........ok
t/testlogin-zfml....ok
All tests successful.
Files=3, Tests=59, 2 wallclock secs ( 1.24 cusr +   0.20 csys =
1.44 CPU)
Test Failure

Kent-Cowgills-Computer(~/acmecorp)$ make test
prove t/
t/artdesign-zfml....ok 1/29
#   Failed test 'HTML passes Lint test'
#   in /Users/kentcowgill/acmecorp/lib/Test/ZFML.pm at line 136.
Test Code Coverage
Kent-Cowgills-Computer(~/acmecorp)$ make cover
make clean
cover -delete
Deleting database /Users/kentcowgill/acmecorp/cover_db
PERL5OPT=-MDevel::Cover=-ignore,prove,+ignore,lib.*.pm,
+ignore,zfml.t make test
prove t/
t/artdesign-zfml....ok
t/index-zfml........ok
t/testlogin-zfml....ok
All tests successful.
Files=3, Tests=59, 22 wallclock secs (18.46 cusr + 0.48 csys =
18.94 CPU)
cover
Reading database from /Users/kentcowgill/acmecorp/cover_db
Devel::Cover: merging data for .artdesign.zfml.EVALCOPYRIGHT_YEAR
into .index.zfml.EVALCOPYRIGHT_YEAR
Test Code Coverage
                             Continued...
---------------------------- ------ ------ ------ ------ ------ ------ ------
File                           stmt   bran   cond    sub    pod   time total
---------------------------- ------ ------ ------ ------ ------ ------ ------
.artdesign.zfml.INITSETUP     100.0    n/a    n/a 100.0     n/a   12.9 100.0
.index.zfml.EVALCOPYRIGHT_YEAR 92.3    n/a    n/a 100.0     n/a   63.9   94.4
.index.zfml.INITSETUP         100.0    n/a    n/a 100.0     n/a   11.5 100.0
.testlogin.zfml.INITFORM      100.0   75.0    n/a 100.0     n/a   11.8   96.0
Total                          98.3   75.0    n/a 100.0     n/a 100.0    97.6
---------------------------- ------ ------ ------ ------ ------ ------ ------


Writing HTML output to /Users/kentcowgill/acmecorp/cover_db/
coverage.html ... done.
make report
open /Applications/Safari.app cover_db/coverage.html
Test Code Coverage
Sweet!
 =D
Functional
 Testing
Introducing
Test::WWW::Mechanize
Test::WWW::Mechanize(3)                          Test::WWW::Mechanize(3)

NAME
       Test::WWW::Mechanize - Testing-specific WWW::Mechanize subclass

VERSION
       Version 1.12

SYNOPSIS
       Test::WWW::Mechanize is a subclass of WWW::Mechanize that
       incorporates features for web application testing. For example:

          $mech->get_ok( $page );
          $mech->base_is( 'http://petdance.com/', 'Proper <BASE HREF>' );
          $mech->title_is( quot;Invoice Statusquot;, quot;On the invoice pagequot; );
          $mech->content_contains( quot;Andy Lesterquot;, quot;My name somewherequot; );
          $mech->content_like( qr/(cpan|perl).org/, quot;Link: perl or CPANquot; );
Planetary::DblClick_tag.pm
#!/usr/bin/perl

use strict;
use warnings;

#use Test::More tests => 40;
use Test::More 'no_plan';
use Test::WWW::Mechanize;

...
Planetary::DblClick_tag.pm
#!/usr/bin/perl

use strict;
use warnings;

#use Test::More tests => 40;
use Test::More 'no_plan';
use Test::WWW::Mechanize;

# .fwpwd contains my AcmeCorp user id and password
our( $AcmeCorp_username, $AcmeCorp_password );
require '/Users/kentcowgill/acmecorp/.fwpwd';

# create a new Test::WWW:Mechanize object.
my $ua = Test::WWW::Mechanize->new;

...
Planetary::DblClick_tag.pm
...

# first, get the home page
$ua->get_ok( quot;http://test.AcmeCorp.comquot;, quot;Check base URLquot; );

# log in (using kcowgill credentials)
$ua->form_number( 1 );
$ua->field( 'EMAIL_ADDRESS', $AcmeCorp_username );
$ua->field( 'PASSWORD',      $AcmeCorp_password );
$ua->click( 'returnLogin' );

# basic sanity check that we're on the right page (/AcmeCorp/my/index)
$ua->content_contains( quot;Hi, Kent!quot;, quot;received greeting messagequot; );

...
Planetary::DblClick_tag.pm
...

# grab the iframe src tag
my( $iframe ) = $ua->content =~ m/iframe .*src=quot;([^quot;]+)quot;/;

# make sure it's got the right stuff in it.
like( $iframe, qr/site=fw/,
      'got site=fw in iframe src tag' );
like( $iframe, qr/affiliate=fw/,
      'got affiliate=fw in iframe src tag' );
like( $iframe, qr/app=(?:my|other)/,
      'got app=my in iframe src tag' );


...
Planetary::DblClick_tag.pm
$ prove -v dblclick.t
dblclick....ok 1 - Check base URL
ok 2 - received greeting message
ok 3 - got site=ac in iframe src tag
ok 4 - got affiliate=ac in iframe src tag
ok 5 - got app=tango in iframe src tag
ok 6 - got size=160x600 in iframe src tag
ok 7 - got pp=1 in iframe src tag
ok 8 - got path=$ID in iframe src tag
ok 9 - got dcpc=606 in iframe src tag
ok 10 - got ge=2 in iframe src tag
ok 11 - got age=19 in iframe src tag
ok 12 - got widget=14 in iframe src tag
ok 13 - got wango=5 in iframe src tag
ok 14 - got state=30 in iframe src tag
ok 15 - got tango=3.8 in iframe src tag
ok 16 - got doodad=0 in iframe src tag
ok 17 - got gadget=0075 in iframe src tag
ok 18 - got mtfnpy=4 in iframe src tag
...
ok
All tests successful.
Files=1, Tests=38, 8 wallclock secs ( 0.27 cusr +   0.08 csys =   0.35 CPU)
How To
  Get
Started
The VIM Plugin
The VIM Plugin
 Screencast
demonstration
  removed
   for PDF
The VIM Plugin
The Perl Module
The Perl Module
 Screencast
demonstration
  removed
   for PDF
The Perl Module
~fin~
References
•Wikipedia:
 http://en.wikipedia.org/wiki/Software_testing
 http://en.wikipedia.org/wiki/Test_Case

•Testing Reference card:
 http://pub.langworth.com/perl_test_refcard.pdf

•Test modules:
 http://search.cpan.org/dist/Test-Simple/

•ViM script:
 http://www.vim.org/scripts/script.php?script_id=1985

•Test::StubGenerator:
 http://search.cpan.org/dist/Test-StubGenerator/

•Screencast software (Mac only):
 http://www.ambrosiasw.com/utilities/snapzprox/

•Cats:
 http://icanhascheezburger.com/

More Related Content

What's hot

Unit Testing Presentation
Unit Testing PresentationUnit Testing Presentation
Unit Testing Presentationnicobn
 
Unit Testing using PHPUnit
Unit Testing using  PHPUnitUnit Testing using  PHPUnit
Unit Testing using PHPUnitvaruntaliyan
 
Test in action week 2
Test in action   week 2Test in action   week 2
Test in action week 2Yi-Huan Chan
 
Keeping objects healthy with Object::Exercise.
Keeping objects healthy with Object::Exercise.Keeping objects healthy with Object::Exercise.
Keeping objects healthy with Object::Exercise.Workhorse Computing
 
Introduction to Unit Testing with PHPUnit
Introduction to Unit Testing with PHPUnitIntroduction to Unit Testing with PHPUnit
Introduction to Unit Testing with PHPUnitMichelangelo van Dam
 
Unit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnitUnit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnitMichelangelo van Dam
 
Test your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practiceTest your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practiceSebastian Marek
 
Advanced PHPUnit Testing
Advanced PHPUnit TestingAdvanced PHPUnit Testing
Advanced PHPUnit TestingMike Lively
 
Test Driven Development with PHPUnit
Test Driven Development with PHPUnitTest Driven Development with PHPUnit
Test Driven Development with PHPUnitMindfire Solutions
 
New Features PHPUnit 3.3 - Sebastian Bergmann
New Features PHPUnit 3.3 - Sebastian BergmannNew Features PHPUnit 3.3 - Sebastian Bergmann
New Features PHPUnit 3.3 - Sebastian Bergmanndpc
 
BSDM with BASH: Command Interpolation
BSDM with BASH: Command InterpolationBSDM with BASH: Command Interpolation
BSDM with BASH: Command InterpolationWorkhorse Computing
 
The $path to knowledge: What little it take to unit-test Perl.
The $path to knowledge: What little it take to unit-test Perl.The $path to knowledge: What little it take to unit-test Perl.
The $path to knowledge: What little it take to unit-test Perl.Workhorse Computing
 
PHPUnit: from zero to hero
PHPUnit: from zero to heroPHPUnit: from zero to hero
PHPUnit: from zero to heroJeremy Cook
 
Unit testing with PHPUnit - there's life outside of TDD
Unit testing with PHPUnit - there's life outside of TDDUnit testing with PHPUnit - there's life outside of TDD
Unit testing with PHPUnit - there's life outside of TDDPaweł Michalik
 
Functional Structures in PHP
Functional Structures in PHPFunctional Structures in PHP
Functional Structures in PHPMarcello Duarte
 
UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013Michelangelo van Dam
 

What's hot (20)

Unit Testing Presentation
Unit Testing PresentationUnit Testing Presentation
Unit Testing Presentation
 
Unit Testing using PHPUnit
Unit Testing using  PHPUnitUnit Testing using  PHPUnit
Unit Testing using PHPUnit
 
Getting Testy With Perl6
Getting Testy With Perl6Getting Testy With Perl6
Getting Testy With Perl6
 
Test in action week 2
Test in action   week 2Test in action   week 2
Test in action week 2
 
Keeping objects healthy with Object::Exercise.
Keeping objects healthy with Object::Exercise.Keeping objects healthy with Object::Exercise.
Keeping objects healthy with Object::Exercise.
 
Introduction to Unit Testing with PHPUnit
Introduction to Unit Testing with PHPUnitIntroduction to Unit Testing with PHPUnit
Introduction to Unit Testing with PHPUnit
 
Short Introduction To "perl -d"
Short Introduction To "perl -d"Short Introduction To "perl -d"
Short Introduction To "perl -d"
 
Unit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnitUnit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnit
 
Test your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practiceTest your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practice
 
Advanced PHPUnit Testing
Advanced PHPUnit TestingAdvanced PHPUnit Testing
Advanced PHPUnit Testing
 
Memory Manglement in Raku
Memory Manglement in RakuMemory Manglement in Raku
Memory Manglement in Raku
 
Test Driven Development with PHPUnit
Test Driven Development with PHPUnitTest Driven Development with PHPUnit
Test Driven Development with PHPUnit
 
Metadata-driven Testing
Metadata-driven TestingMetadata-driven Testing
Metadata-driven Testing
 
New Features PHPUnit 3.3 - Sebastian Bergmann
New Features PHPUnit 3.3 - Sebastian BergmannNew Features PHPUnit 3.3 - Sebastian Bergmann
New Features PHPUnit 3.3 - Sebastian Bergmann
 
BSDM with BASH: Command Interpolation
BSDM with BASH: Command InterpolationBSDM with BASH: Command Interpolation
BSDM with BASH: Command Interpolation
 
The $path to knowledge: What little it take to unit-test Perl.
The $path to knowledge: What little it take to unit-test Perl.The $path to knowledge: What little it take to unit-test Perl.
The $path to knowledge: What little it take to unit-test Perl.
 
PHPUnit: from zero to hero
PHPUnit: from zero to heroPHPUnit: from zero to hero
PHPUnit: from zero to hero
 
Unit testing with PHPUnit - there's life outside of TDD
Unit testing with PHPUnit - there's life outside of TDDUnit testing with PHPUnit - there's life outside of TDD
Unit testing with PHPUnit - there's life outside of TDD
 
Functional Structures in PHP
Functional Structures in PHPFunctional Structures in PHP
Functional Structures in PHP
 
UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013
 

Viewers also liked

Empathize and define
Empathize and defineEmpathize and define
Empathize and definedebmithu
 
French capitalism = socialist cronyism
French capitalism = socialist cronyismFrench capitalism = socialist cronyism
French capitalism = socialist cronyismMarkets Beyond
 
[DevOn 2013] Dynamic web proxy
[DevOn 2013] Dynamic web proxy[DevOn 2013] Dynamic web proxy
[DevOn 2013] Dynamic web proxy흥래 김
 
도메인을 넘는 통신기술 (CORS + Proxy Server = bsNet)
도메인을 넘는 통신기술 (CORS + Proxy Server = bsNet)도메인을 넘는 통신기술 (CORS + Proxy Server = bsNet)
도메인을 넘는 통신기술 (CORS + Proxy Server = bsNet)Yongho Ji
 
[2010 네이트 앱스토어 개발자 세미나] 앱스 제작 사례 (2) 소셜게임 서버 구성 전략
[2010 네이트 앱스토어 개발자 세미나] 앱스 제작 사례 (2) 소셜게임 서버 구성 전략[2010 네이트 앱스토어 개발자 세미나] 앱스 제작 사례 (2) 소셜게임 서버 구성 전략
[2010 네이트 앱스토어 개발자 세미나] 앱스 제작 사례 (2) 소셜게임 서버 구성 전략Cyworld AppStore (SK Communications)
 
Proxy pattern
Proxy patternProxy pattern
Proxy patternscor7910
 
Proxy_design_pattern_in_Java_SYS4U
Proxy_design_pattern_in_Java_SYS4UProxy_design_pattern_in_Java_SYS4U
Proxy_design_pattern_in_Java_SYS4Usys4u
 

Viewers also liked (7)

Empathize and define
Empathize and defineEmpathize and define
Empathize and define
 
French capitalism = socialist cronyism
French capitalism = socialist cronyismFrench capitalism = socialist cronyism
French capitalism = socialist cronyism
 
[DevOn 2013] Dynamic web proxy
[DevOn 2013] Dynamic web proxy[DevOn 2013] Dynamic web proxy
[DevOn 2013] Dynamic web proxy
 
도메인을 넘는 통신기술 (CORS + Proxy Server = bsNet)
도메인을 넘는 통신기술 (CORS + Proxy Server = bsNet)도메인을 넘는 통신기술 (CORS + Proxy Server = bsNet)
도메인을 넘는 통신기술 (CORS + Proxy Server = bsNet)
 
[2010 네이트 앱스토어 개발자 세미나] 앱스 제작 사례 (2) 소셜게임 서버 구성 전략
[2010 네이트 앱스토어 개발자 세미나] 앱스 제작 사례 (2) 소셜게임 서버 구성 전략[2010 네이트 앱스토어 개발자 세미나] 앱스 제작 사례 (2) 소셜게임 서버 구성 전략
[2010 네이트 앱스토어 개발자 세미나] 앱스 제작 사례 (2) 소셜게임 서버 구성 전략
 
Proxy pattern
Proxy patternProxy pattern
Proxy pattern
 
Proxy_design_pattern_in_Java_SYS4U
Proxy_design_pattern_in_Java_SYS4UProxy_design_pattern_in_Java_SYS4U
Proxy_design_pattern_in_Java_SYS4U
 

Similar to Testing Code and Assuring Quality

Advanced Perl Techniques
Advanced Perl TechniquesAdvanced Perl Techniques
Advanced Perl TechniquesDave Cross
 
php programming.pptx
php programming.pptxphp programming.pptx
php programming.pptxrani marri
 
PHPunit and you
PHPunit and youPHPunit and you
PHPunit and youmarkstory
 
MT_01_unittest_python.pdf
MT_01_unittest_python.pdfMT_01_unittest_python.pdf
MT_01_unittest_python.pdfHans Jones
 
Testing in Laravel
Testing in LaravelTesting in Laravel
Testing in LaravelAhmed Yahia
 
How To Test Everything
How To Test EverythingHow To Test Everything
How To Test Everythingnoelrap
 
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Michelangelo van Dam
 
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnitinternational PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnitsmueller_sandsmedia
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Michelangelo van Dam
 
Automated Unit Testing
Automated Unit TestingAutomated Unit Testing
Automated Unit TestingMike Lively
 
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...Rodolfo Carvalho
 
Software Testing
Software TestingSoftware Testing
Software TestingLambert Lum
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11Michelangelo van Dam
 

Similar to Testing Code and Assuring Quality (20)

Advanced Perl Techniques
Advanced Perl TechniquesAdvanced Perl Techniques
Advanced Perl Techniques
 
php programming.pptx
php programming.pptxphp programming.pptx
php programming.pptx
 
PHPunit and you
PHPunit and youPHPunit and you
PHPunit and you
 
MT_01_unittest_python.pdf
MT_01_unittest_python.pdfMT_01_unittest_python.pdf
MT_01_unittest_python.pdf
 
Getting testy with Perl
Getting testy with PerlGetting testy with Perl
Getting testy with Perl
 
Testing in Laravel
Testing in LaravelTesting in Laravel
Testing in Laravel
 
How To Test Everything
How To Test EverythingHow To Test Everything
How To Test Everything
 
PHPUnit testing to Zend_Test
PHPUnit testing to Zend_TestPHPUnit testing to Zend_Test
PHPUnit testing to Zend_Test
 
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
 
Test driven development_for_php
Test driven development_for_phpTest driven development_for_php
Test driven development_for_php
 
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnitinternational PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12
 
Automated Unit Testing
Automated Unit TestingAutomated Unit Testing
Automated Unit Testing
 
Web 8 | Introduction to PHP
Web 8 | Introduction to PHPWeb 8 | Introduction to PHP
Web 8 | Introduction to PHP
 
Laravel Unit Testing
Laravel Unit TestingLaravel Unit Testing
Laravel Unit Testing
 
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...
 
Software Testing
Software TestingSoftware Testing
Software Testing
 
Unit testing zend framework apps
Unit testing zend framework appsUnit testing zend framework apps
Unit testing zend framework apps
 
PHP Unit Testing
PHP Unit TestingPHP Unit Testing
PHP Unit Testing
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11
 

Recently uploaded

Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfAlex Barbosa Coqueiro
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubKalema Edgar
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr BaganFwdays
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Enterprise Knowledge
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostZilliz
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteDianaGray10
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity PlanDatabarracks
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenHervé Boutemy
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek SchlawackFwdays
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024Lorenzo Miniero
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxLoriGlavin3
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningLars Bell
 
Story boards and shot lists for my a level piece
Story boards and shot lists for my a level pieceStory boards and shot lists for my a level piece
Story boards and shot lists for my a level piececharlottematthew16
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brandgvaughan
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLScyllaDB
 

Recently uploaded (20)

Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdf
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding Club
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test Suite
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity Plan
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache Maven
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine Tuning
 
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptxE-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
 
Story boards and shot lists for my a level piece
Story boards and shot lists for my a level pieceStory boards and shot lists for my a level piece
Story boards and shot lists for my a level piece
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brand
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQL
 

Testing Code and Assuring Quality

  • 1. Testing Code and Assuring Quality Learning to use Test::More, Perl::Critic, and Devel::Cover Kent Cowgill
  • 2. Testing Code and Assuring Quality •Learn how to write unit tests in perl •Write tests for your code •Ensuring your code is high quality •Ensuring your tests fully exercise your code •Writing functional tests for your code •A practical example of creating a test suite •How to save time and effort (be lazy!)
  • 4. Testing. Software testing is the process used to help identify the correctness, completeness, security, and quality of developed computer software. Testing is a process of technical investigation, performed on behalf of stakeholders, that is intended to reveal quality-related information about the product with respect to the context in which it is intended to operate. This includes, but is not limited to, the process of executing a program or application with the intent of finding errors. Quality is not an absolute; it is value to some person. With that in mind, testing can never completely establish the correctness of arbitrary computer software; testing furnishes a criticism or comparison that compares the state and behavior of the product against a specification. -- excerpted from http://en.wikipedia.org/wiki/Software_testing
  • 5. Testing.. In software engineering, a test case is a set of conditions or variables under which a tester will determine if a requirement upon an application is partially or fully satisfied. It may take many test cases to determine that a requirement is fully satisfied. In order to fully test that all the requirements of an application are met, there must be at least one test case for each requirement unless a requirement has sub requirements. Some methodologies recommend creating at least two test cases for each requirement. One of them should perform positive testing of requirement and other should perform negative testing. -- excerpted from http://en.wikipedia.org/wiki/Test_Case
  • 6. Testing... What characterizes a formal, written test case is that there is a known input and an expected output, which is worked out before the test is executed. If the application is created without formal requirements, then test cases are written based on the accepted normal operation of programs of a similar class. -- excerpted from http://en.wikipedia.org/wiki/Test_Case
  • 7.
  • 8. How can I find out more information about testing with Perl?
  • 9. (or anything else you talk about tonight, since you don't really cover anything in great depth?) (yeah, sorry about that)
  • 11.
  • 12.
  • 14.
  • 16.
  • 19. Books
  • 20.
  • 21.
  • 22. How to write unit tests in Perl
  • 23. Unit tests emit TAP
  • 24. Test Anything Protocol (TAP) • The Test Anything Protocol is a general purpose format for transmitting the result of test programs to a thing which interprets and takes action on those results.
  • 25. Test Anything Protocol (TAP) 1..N ok 1 Description # Directive # Diagnostic .... ok 47 Description ok 48 Description more tests....
  • 26. Test Anything Protocol (TAP) 1..4 ok 1 - Input file opened not ok 2 - First line of the input valid ok 3 - Read the rest of the file not ok 4 - Summarized correctly # TODO
  • 28. Test::Simple • ok( <expression>, <description>); ok( $num == 30, '$num equals 30' ); ok( $this =~ m/that/, 'this matches that' ); ok( do_it( $param ), 'sub do_it() returns true' ); OUTPUT: ok 1 - $num equals 30 ok 2 - this matches that ok 3 - sub do_it() returns true
  • 29. Test::Simple • ok( <expression>, <description>); ok( $num == 30, '$num equals 30' ); ok( $this =~ m/that/, 'this matches that' ); ok( do_it( $param ), 'sub do_it() returns true' ); OUTPUT: not ok 1 - $num equals 30 # Failed test '$num equals 30' # in test.pl at line 10.
  • 30. Test::Simple • ok( <expression>, <description>); ok( $num == 30, '$num equals 30' ); ok( $this =~ m/that/, 'this matches that' ); ok( do_it( $param ), 'sub do_it() returns true' ); OUTPUT: not ok 2 - this matches that # Failed test 'this matches that' # in test.pl at line 11.
  • 31. Test::Simple • ok( <expression>, <description>); ok( $num == 30, '$num equals 30' ); ok( $this =~ m/that/, 'this matches that' ); ok( do_it( $param ), 'sub do_it() returns true' ); OUTPUT: not ok 3 - sub do_it() returns true # Failed test 'sub do_it() returns true' # in test.pl at line 13.
  • 32. Test::More • is( <got>, <expected>, <description>); is( $this, $that, 'this is the same as that' );
  • 33. Test::More • is( <got>, <expected>, <description>); is( $this, $that, 'this is the same as that' ); OUTPUT: ok 1 - this is the same as that
  • 34. Test::More • is( <got>, <expected>, <description>); is( $this, $that, 'this is the same as that' ); OUTPUT: not ok 1 - this is the same as that # Failed test 'this is equal to that' # in test.t at line 10 # got: 'this' # expected: 'that'
  • 36. Introducing Prove PROVE(1) User Contributed Perl Documentation PROVE(1) NAME prove -- A command-line tool for running tests OPTIONS -d, --debug Includes extra debugging information -h, --help Display this help -H, --man Longer manpage for prove -I Add libraries to @INC, as Perl's -I -l, --lib Add lib to the path for your tests -r, --recurse Recursively descend into directories -s, --shuffle Run the tests in a random order --timer Print elapsed time after each test file -v, --verbose Display standard output of test scripts while running ...
  • 37. Output: $ mv testmore.pl testmore.t $ prove ./testmore....ok All tests successful. Files=1, Tests=3, 0 wallclock secs ( 0.02 cusr + 0.01 csys = 0.03 CPU) $ prove -v ./testmore....ok 1 - this should equal thistoo ok 2 - this should be thistoo (is) ok 3 - this should NOT be that (isnt) 1..3 ok All tests successful. Files=1, Tests=3, 0 wallclock secs ( 0.02 cusr + 0.01 csys = 0.03 CPU)
  • 38. How Many Tests? #!/usr/bin/perl use strict; use warnings; use Test::More tests => 3; # set some testing variables my $this = quot;thisquot;; my $thistoo = quot;thisquot;; my $that = quot;thatquot;; # now for the tests ok( $this eq $thistoo, quot;this should equal thistooquot; ); is( $this, $thistoo, quot;this should be thistoo (is)quot; ); isnt( $this, $that, quot;this should NOT be that (isnt)quot; );
  • 39. How Many Tests? $ prove -v ./testmore....1..3 ok 1 - this should equal thistoo ok 2 - this should be thistoo (is) ok 3 - this should NOT be that (isnt) ok All tests successful. Files=1, Tests=3, 0 wallclock secs ( 0.02 cusr + 0.01 csys = 0.03 CPU)
  • 40. How Many Tests? #!/usr/bin/perl use strict; use warnings; use Test::More tests => 4; # set some testing variables my $this = quot;thisquot;; my $thistoo = quot;thisquot;; my $that = quot;thatquot;; # now for the tests ok( $this eq $thistoo, quot;this should equal thistooquot; ); is( $this, $thistoo, quot;this should be thistoo (is)quot; ); isnt( $this, $that, quot;this should NOT be that (isnt)quot; );
  • 41. How Many Tests? $ prove -v testmore....1..4 ok 1 - this equals thistoo ok 2 - another way to see if this and thistoo are equal # Looks like you planned 4 tests but only ran 3. ok 3 - a way to see if this and that are not equal dubious Test returned status 255 (wstat 65280, 0xff00) DIED. FAILED test 4 Failed 1/4 tests, 75.00% okay Failed Test Stat Wstat Total Fail List of Failed ------------------------------------------------------------- testmore.t 255 65280 4 24 Failed 1/1 test scripts. 1/4 subtests failed. Files=1, Tests=4, 0 wallclock secs ( 0.02 cusr + 0.01 csys = 0.03 CPU) Failed 1/1 test programs. 1/4 subtests failed.
  • 43.
  • 44. -l, --lib Add lib to the path for your tests
  • 45. -l, --lib Add lib to the path for your tests -r, --recurse Recursively descend into directories
  • 46. -l, --lib Add lib to the path for your tests -r, --recurse Recursively descend into directories -s, --shuffle Run the tests in a random order
  • 52. Your code compiles, but does it do the right thing?
  • 53. Does it? I mean, REALLY?
  • 54. How do you know?
  • 56. My problem: ZFML* * Name changed to protect the innocent
  • 57. (btw, what the heck is ZFML?)
  • 58. ZFML is a custom template system
  • 59. ZFML is a mish-mash of HTML and Perl
  • 60. ZFML only exists at AcmeCorp.com* * Name changed to protect the innocent
  • 61. I don't think you'd want it to exist anywhere else.
  • 62. SRSLY
  • 63. ZFML That looks <html> <head><title></title></head> like HTML <body> </body> </html> <!-- __INIT SETUP__ my ($p) = @_; $p->var->{'ONLOAD'} .= q(agentDOMCheck();); $p->var->{'SCRIPT'} .= q(<script src=quot;form_functions.jsquot;></script>); --> <!-- __EVAL COPYRIGHT_YEAR__ my ($p) = @_; $p->var->{'COPYRIGHT_YEAR'} = 1900 + (localtime)[5]; -->
  • 64. ZFML That looks <html> <head><title></title></head> like HTML <body> </body> </html> <!-- __INIT SETUP__ WTF?!? my ($p) = @_; $p->var->{'ONLOAD'} .= q(agentDOMCheck();); $p->var->{'SCRIPT'} .= q(<script src=quot;form_functions.jsquot;></script>); --> <!-- __EVAL COPYRIGHT_YEAR__ my ($p) = @_; $p->var->{'COPYRIGHT_YEAR'} = 1900 + (localtime)[5]; -->
  • 65. It only runs under mod_perl
  • 66. :(
  • 67. $ perl -c index.zfml Bareword found where operator expected at index.zfml line 5, near quot;<meta http-equiv=quot;content-typequot; content=quot;text/htmlquot; (Might be a runaway multi-line // string starting on line4) (Missing operator before html?) String found where operator expected at index.zfml line 6, near quot;<meta name=quot;quot; (Might be a runaway multi-line quot;quot; string starting on line 5) (Missing semicolon on previous line?) Bareword found where operator expected at index.zfml line 6, near quot;<meta name=quot;descriptionquot; (Missing operator before description?) String found where operator expected at index.zfml line 6, near quot;descriptionquot; content=quot;quot; Bareword found where operator expected at index.zfml line 6, near quot;quot; content=quot;Findquot; (Missing operator before Find?) Bareword found where operator expected at index.zfml line 7, near quot;<meta NAME=quot;keywordsquot; (Might be a runaway multi-line quot;quot; string starting on line 6) (Missing operator before keywords?) String found where operator expected at index.zfml line 7, near quot;keywordsquot; CONTENT=quot;quot; Bareword found where operator expected at index.zfml line 7, near quot;quot; CONTENT=quot;AcmeCorpquot; (Missing operator before AcmeCorp?) Bareword found where operator expected at index.zfml line 7, near quot;time jobsquot; (Do you need to predeclare time?) String found where operator expected at index.zfml line 8, near quot;<style type=quot;quot; (Might be a runaway multi-line quot;quot; string starting on line 7) (Missing semicolon on previous line?) Bareword found where operator expected at index.zfml line 8, near quot;<style type=quot;textquot; (Missing operator before text?) String found where operator expected at index.zfml line 28, near quot;<div id=quot;quot; (Might be a runaway multi-line quot;quot; string starting on line 8) (Missing semicolon on previous line?) Bareword found where operator expected at index.zfml line 28, near quot;<div id=quot;pageContainerquot; (Missing operator before pageContainer?)
  • 68.
  • 70. A Simple Class #!/usr/bin/perl use strict; use warnings; package myObj; sub new { my $class = shift; my %args = @_; my $self = {}; $self->{ name } = $args{ name } || 'default'; return bless $self, $class; } sub set_name { my $self = shift; $self->{ name } = shift; } sub get_name { my $self = shift; return $self->{ name }; } 1;
  • 71. A Simple Class #!/usr/bin/perl use strict; use warnings; Constructor package myObj; (http://en.wikipedia.org/wiki/Constructor_%28computer_science%29) sub new { my $class = shift; my %args = @_; my $self = {}; $self->{ name } = $args{ name } || 'default'; return bless $self, $class; } sub set_name { my $self = shift; $self->{ name } = shift; } sub get_name { my $self = shift; return $self->{ name }; } 1;
  • 72. A Simple Class #!/usr/bin/perl use strict; use warnings; Constructor package myObj; (http://en.wikipedia.org/wiki/Constructor_%28computer_science%29) sub new { my $class = shift; my %args = @_; my $self = {}; $self->{ name } = $args{ name } || 'default'; return bless $self, $class; } Mutator sub set_name { my $self = shift; (http://en.wikipedia.org/wiki/Mutator_method) $self->{ name } = shift; } sub get_name { my $self = shift; return $self->{ name }; } 1;
  • 73. A Simple Class #!/usr/bin/perl use strict; use warnings; Constructor package myObj; (http://en.wikipedia.org/wiki/Constructor_%28computer_science%29) sub new { my $class = shift; my %args = @_; my $self = {}; $self->{ name } = $args{ name } || 'default'; return bless $self, $class; } Mutator sub set_name { my $self = shift; (http://en.wikipedia.org/wiki/Mutator_method) $self->{ name } = shift; } sub get_name { Accessor my $self = shift; return $self->{ name }; (http://en.wikipedia.org/wiki/Accessor) } 1;
  • 74. Using A Simple Class #!/usr/bin/perl use strict; use warnings; use myObj; ...
  • 75. Using A Simple Class #!/usr/bin/perl use strict; Calling the use warnings; Constructor use myObj; my $obj = myObj->new( name => 'My Object' ); ...
  • 76. Using A Simple Class #!/usr/bin/perl use strict; Calling the use warnings; Constructor use myObj; my $obj = myObj->new( name => 'My Object' ); my $objName = $obj->get_name(); ... Calling the Accessor
  • 77. Using A Simple Class #!/usr/bin/perl use strict; Calling the use warnings; Constructor use myObj; my $obj = myObj->new( name => 'My Object' ); my $objName = $obj->get_name(); my $new_name = 'Your Object' ); Calling the Accessor $obj->set_name( $new_name ); Calling the Mutator
  • 78. Testing A Simple Class #!/usr/bin/perl use strict; use warnings; use Test::More
  • 79. Testing A Simple Class #!/usr/bin/perl It's fine to start use strict; use warnings; out without a use Test::More 'no_plan'; testing plan (number of tests to run)
  • 80. Testing A Simple Class #!/usr/bin/perl use strict; use warnings; Make sure you can use Test::More 'no_plan'; quot;usequot; the BEGIN { use_ok( 'myObj' ); } object
  • 81. Testing A Simple Class #!/usr/bin/perl use strict; Make sure you use warnings; can instantiate use Test::More 'no_plan'; the object (call BEGIN { use_ok( 'myObj' ); } the constructor) ok( my $obj1 = myObj->new( name => 'test1' ), quot;can create a myObj specifying valuesquot; );
  • 82. Testing A Simple Class #!/usr/bin/perl use strict; use warnings; use Test::More 'no_plan'; BEGIN { use_ok( 'myObj' ); } Make sure your ok( my $obj1 = myObj->new( name => 'test1' ), ); instantiated quot;can create a myObj specifying valuesquot; isa_ok( $obj1, 'myObj' ); object quot;isaquot; type of object you created
  • 83. Testing A Simple Class #!/usr/bin/perl use strict; use warnings; use Test::More 'no_plan'; BEGIN { use_ok( 'myObj' ); } ok( my $obj1 = myObj->new( name => 'test1' ), quot;can create a myObj specifying valuesquot; ); isa_ok( $obj1, 'myObj' ); ok( my $obj2 = myObj->new(), quot;can create a myObj not specifying valuesquot; ); Instantiate another object
  • 84. Testing A Simple Class #!/usr/bin/perl use strict; use warnings; use Test::More 'no_plan'; BEGIN { use_ok( 'myObj' ); } ok( my $obj1 = myObj->new( name => 'test1' ), quot;can create a myObj specifying valuesquot; ); isa_ok( $obj1, 'myObj' ); ok( my $obj2 = myObj->new(), quot;can create a myObj not specifying valuesquot; ); Make sure the isa_ok( $obj2, 'myObj' ); new object quot;isaquot; quot;myObjquot; object
  • 85. Testing A Simple Class Test using the #!/usr/bin/perl mutator of the use strict; use warnings; name property of use Test::More 'no_plan'; the object BEGIN { use_ok( 'myObj' ); } ok( my $obj1 = myObj->new( name => 'test1' ), quot;can create a myObj specifying valuesquot; ); isa_ok( $obj1, 'myObj' ); ok( my $obj2 = myObj->new(), quot;can create a myObj not specifying valuesquot; ); isa_ok( $obj2, 'myObj' ); ok( $obj2->set_name( 'test1' ), quot;can set namequot; );
  • 86. Testing A Simple Class #!/usr/bin/perl Make sure the use strict; accessor returns use warnings; the value we just use Test::More 'no_plan'; set BEGIN { use_ok( 'myObj' ); } ok( my $obj1 = myObj->new( name => 'test1' ), quot;can create a myObj specifying valuesquot; ); isa_ok( $obj1, 'myObj' ); ok( my $obj2 = myObj->new(), quot;can create a myObj not specifying valuesquot; ); isa_ok( $obj2, 'myObj' ); ok( $obj2->set_name( 'test1' ), quot;can set namequot; ); ok( 'test1' eq $obj2->get_name(), quot;can get namequot; );
  • 87. Testing A Simple Class #!/usr/bin/perl Perform a quot;deepquot; use strict; comparison of use warnings; the two objects use Test::More 'no_plan'; (created in BEGIN { use_ok( 'myObj' ); } ), different ways) ok( my $obj1 = myObj->new( name => 'test1' quot;can create a myObj specifying valuesquot; ); isa_ok( $obj1, 'myObj' ); ok( my $obj2 = myObj->new(), quot;can create a myObj not specifying valuesquot; ); isa_ok( $obj2, 'myObj' ); ok( $obj2->set_name( 'test1' ), quot;can set namequot; ); ok( 'test1' eq $obj2->get_name(), quot;can get namequot; ); is_deeply( $obj1, $obj2, quot;obj1 seems deeply similar to obj2quot; );
  • 88. Testing A Simple Class #!/usr/bin/perl use strict; use warnings; Specify the use Test::More tests => 8; number of tests we intend to run BEGIN { use_ok( 'myObj' ); } ok( my $obj1 = myObj->new( name => 'test1' ), quot;can create a myObj specifying valuesquot; ); isa_ok( $obj1, 'myObj' ); ok( my $obj2 = myObj->new(), quot;can create a myObj not specifying valuesquot; ); isa_ok( $obj2, 'myObj' ); ok( $obj2->set_name( 'test1' ), quot;can set namequot; ); ok( 'test1' eq $obj2->get_name(), quot;can get namequot; ); is_deeply( $obj1, $obj2, quot;obj1 seems deeply similar to obj2quot; );
  • 89. Testing A Simple Class Output: $ prove -v testobj.t testobj....1..8 ok 1 - use myObj; ok 2 - can create a myObj specifying values ok 3 - The object isa myObj ok 4 - can create a myObj not specifying values ok 5 - The object isa myObj ok 6 - can set name ok 7 - can get name ok 8 - obj1 seems deeply similar to obj2 ok All tests successful. Files=1, Tests=8, 0 wallclock secs ( 0.02 cusr + 0.01 csys = 0.03 CPU)
  • 93. Testing Zfml $ cat testindex.t ... BEGIN { use_ok( 'index.zfml' ) }; ... $ prove testindex.t testindex.... # Failed test 'use index.zfml;' # in testindex.t at line 8. # Tried to use 'index.zfml'. # Error: syntax error at (eval 3) line 2, near quot;use index.quot; # Looks like you failed 1 test of 1. testindex....dubious Test returned status 1 (wstat 256, 0x100) DIED. FAILED test 1 Failed 1/1 tests, 0.00% okay Failed Test Stat Wstat Total Fail List of Failed --------------------------------------------------------------------- testindex.t 1 256 1 11 Failed 1/1 test scripts. 1/1 subtests failed. Files=1, Tests=1, 0 wallclock secs ( 0.03 cusr + 0.01 csys = 0.04 CPU) Failed 1/1 test programs. 1/1 subtests failed.
  • 94.
  • 95. Ensuring your code is high* quality**
  • 96. * for some values of high
  • 97. ** for some values of quality
  • 98. Introducing Perl::Critic and perlcritic Perl::Critic(3) User Contributed Perl Documentation Perl::Critic(3) NAME Perl::Critic - Critique Perl source code for best-practices SYNOPSIS use Perl::Critic; my $file = shift; my $critic = Perl::Critic->new(); my @violations = $critic->critique($file); print @violations; DESCRIPTION Perl::Critic is an extensible framework for creating and applying coding standards to Perl source code. Essentially, it is a static source code analysis engine. Perl::Critic is distributed with a number of Perl::Critic::Policy modules that attempt to enforce various coding guidelines. Most Policy modules are based on Damian Conway's book Perl Best Practices.
  • 99. Introducing Perl::Critic and perlcritic PERLCRITIC(1) User Contributed Perl Documentation PERLCRITIC(1) NAME quot;perlcriticquot; - Command-line interface to critique Perl source SYNOPSIS perlcritic [-12345 | -severity number] [-noprofile | -profile file] [-top [ number ]] [-include pattern] [-exclude pattern] [-theme expression] [-verbose number | format] [-list] [-only | -noonly] [-force | -noforce] [-nocolor] [-Version] [-help] [-man] [-quiet] [FILE | DIRECTORY | STDIN] DESCRIPTION quot;perlcriticquot; is a Perl source code analyzer. It is the executable front-end to the Perl::Critic engine, which attempts to identify awkward, hard to read, error-prone, or unconventional constructs in your code. Most of the rules are based on Damian Conway's book Perl Best Practices.
  • 100. Don't worry, it's all in perldoc.
  • 101. Working with perlcritic $ perlcritic -1 myObj.pm RCS keywords $Id$ not found at line 1, column 1. See page 441 of PBP. (Severity: 2) RCS keywords $Revision$, $HeadURL$, $Date$ not found at line 1, column 1. See page 441 of PBP. (Severity: 2) RCS keywords $Revision$, $Source$, $Date$ not found at line 1, column 1. See page 441 of PBP. (Severity: 2) No quot;VERSIONquot; variable found at line 1, column 1. See page 404 of PBP. (Severity: 2) Code is not tidy at line 1, column 1. See page 33 of PBP. (Severity: 1) Subroutine does not end with quot;returnquot; at line 16, column 1. See page 197 of PBP. (Severity: 4)
  • 102. Working with perlcritic $ perlcritic -1 myObj.pm RCS keywords $Id$ not found at line 1, column 1. See page 441 of PBP. (Severity: 2) RCS keywords $Revision$, $HeadURL$, $Date$ not found at line 1, column 1. See page 441 of PBP. (Severity: 2) RCS keywords $Revision$, $Source$, $Date$ not found at line 1, column 1. See page 441 of PBP. (Severity: 2) No quot;VERSIONquot; variable found at line 1, column 1. See page 404 of PBP. (Severity: 2) Code is not tidy at line 1, column 1. See page 33 of PBP. (Severity: 1) Subroutine does not end with quot;returnquot; at line 16, column 1. See page 197 of PBP. (Severity: 4)
  • 103. Working with .perlcriticrc $ cat .perlcriticrc [-Miscellanea::RequireRcsKeywords] [-Modules::RequireVersionVar]
  • 104. Working with perlcritic $ perlcritic -1 myObj.pm Code is not tidy at line 1, column 1. See page 33 of PBP. (Severity: 1) Subroutine does not end with quot;returnquot; at line 16, column 1. See page 197 of PBP. (Severity: 4)
  • 105. Working with perlcritic $ perlcritic -1 myObj.pm Code is not tidy at line 1, column 1. See page 33 of PBP. (Severity: 1) Subroutine does not end with quot;returnquot; at line 16, column 1. See page 197 of PBP. (Severity: 4)
  • 106. Working with perlcritic sub set_name { my $self = shift; $self->{ name } = shift; return; }
  • 107. Working with perlcritic Output: $ prove -v testobj.t testobject....1..8 # Failed test 'can set name' # in testobject.t at line 17. # Looks like you failed 1 test of 8. ok 1 - use myObj; ok 2 - can create a myObj specifying values ok 3 - The object isa myObj ok 4 - can create a myObj not specifying values ok 5 - The object isa myObj not ok 6 - can set name ok 7 - can get name ok 8 - obj1 seems deeply similar to obj2 Files=1, Tests=8, 0 wallclock secs ( 0.03 cusr + 0.01 csys = 0.04 CPU) Failed 1/1 test programs. 1/8 subtests failed.
  • 108. Working with perlcritic #!/usr/bin/perl use strict; use warnings; use Test::More tests => 8; BEGIN { use_ok( 'myObj' ); } ok( my $obj1 = myObj->new( name => 'test1' ), The mutator quot;can create a myObj specifying valuesquot; ); isa_ok( $obj1, 'myObj' ); shouldn't return a value! ok( my $obj2 = myObj->new(), quot;can create a myObj not specifying valuesquot; ); isa_ok( $obj2, 'myObj' ); ok( ! $obj2->set_name( 'test1' ), quot;can set namequot; ); ok( 'test1' eq $obj2->get_name(), quot;can get namequot; ); is_deeply( $obj1, $obj2, quot;obj1 seems deeply similar to obj2quot; );
  • 109. Working with perlcritic Output: $ prove -v testobj.t testobj....1..8 ok 1 - use myObj; ok 2 - can create a myObj specifying values ok 3 - The object isa myObj ok 4 - can create a myObj not specifying values ok 5 - The object isa myObj ok 6 - can set name ok 7 - can get name ok 8 - obj1 seems deeply similar to obj2 ok All tests successful. Files=1, Tests=8, 0 wallclock secs ( 0.02 cusr + 0.01 csys = 0.03 CPU)
  • 110. Perl::Critic and Zfml $ perlcritic -1 index.zfml Code not contained in explicit package at line 1, column 1. Violates encapsulation. (Severity: 4) Code before strictures are enabled at line 1, column 1. See page 429 of PBP. (Severity: 5) Code before warnings are enabled at line 1, column 1. See page 431 of PBP. (Severity: 4) Mixed high and low-precedence booleans at line 1, column 1. See page 70 of PBP. (Severity: 4) Useless interpolation of literal string at line 1, column 23. See page 51 of PBP. (Severity: 1) Useless interpolation of literal string at line 1, column 64. See page 51 of PBP. (Severity: 1) Useless interpolation of literal string at line 2, column 13. See page 51 of PBP. (Severity: 1) Hard tabs used at line 4, column 60. See page 20 of PBP. (Severity: 3) Code not contained in explicit package at line 5, column 54. Violates encapsulation. (Severity: 4) Mixed high and low-precedence booleans at line 5, column 54. See page 70 of PBP. (Severity: 4) Hard tabs used at line 5, column 72. See page 20 of PBP. (Severity: 3) Useless interpolation of literal string at line 5, column 72. See page 51 of PBP. (Severity: 1) Useless interpolation of literal string at line 6, column 26. See page 51 of PBP. (Severity: 1) Postfix control quot;forquot; used at line 6, column 164. See page 96 of PBP. (Severity: 1) Hard tabs used at line 6, column 259. See page 20 of PBP. (Severity: 3) Useless interpolation of literal string at line 6, column 259. See page 51 of PBP. (Severity: 1) Useless interpolation of literal string at line 7, column 23. See page 51 of PBP. (Severity: 1) Postfix control quot;forquot; used at line 7, column 261. See page 96 of PBP. (Severity: 1) Postfix control quot;forquot; used at line 7, column 393. See page 96 of PBP. (Severity: 1) Postfix control quot;forquot; used at line 7, column 568. See page 96 of PBP. (Severity: 1) Postfix control quot;forquot; used at line 7, column 587. See page 96 of PBP. (Severity: 1) Hard tabs used at line 7, column 678. See page 20 of PBP. (Severity: 3) Useless interpolation of literal string at line 7, column 678. See page 51 of PBP. (Severity: 1) Hard tabs used at line 8, column 24. See page 20 of PBP. (Severity: 3) Useless interpolation of literal string at line 33, column 22. See page 51 of PBP. (Severity: 1) Mismatched operator at line 34, column 15. Numeric/string operators and operands should match. (Severity: 3) Useless interpolation of literal string at line 34, column 45. See page 51 of PBP. (Severity: 1) Useless interpolation of literal string at line 34, column 64. See page 51 of PBP. (Severity: 1) Mismatched operator at line 34, column 86. Numeric/string operators and operands should match. (Severity: 3) Useless interpolation of literal string at line 34, column 186. See page 51 of PBP. (Severity: 1) Hard tabs used at line 34, column 209. See page 20 of PBP. (Severity: 3) Useless interpolation of literal string at line 34, column 209. See page 51 of PBP. (Severity: 1)
  • 111.
  • 112. Working with perlcritic $ perlcritic -1 myObj.pm Code is not tidy at line 1, column 1. See page 33 of PBP. (Severity: 1)
  • 113. Working with perlcritic $ perlcritic -1 myObj.pm Code is not tidy at line 1, column 1. See page 33 of PBP. (Severity: 1)
  • 114. Working with perltidy PERLTIDY(1) User Contributed Perl Documentation PERLTIDY(1) NAME perltidy - a perl script indenter and reformatter SYNOPSIS perltidy [ options ] file1 file2 file3 ... (output goes to file1.tdy, file2.tdy, ...) perltidy [ options ] file1 -o outfile perltidy [ options ] file1 -st >outfile perltidy [ options ] <infile >outfile
  • 115. Working with perltidy $ cat .perltidyrc -l=78 # Max line width is 78 cols -i=2 # Indent level is 2 cols -ci=2 # Continuation indent is 2 cols -lp # line up parenthesis -vt=2 # Maximal vertical tightness -vtc=1 # medium vertical something tightness -cti=1 # No extra indentation for closing brackets -pt=1 # Medium parenthesis tightness -bt=1 # Medium brace tightness -sbt=1 # Medium square bracket tightness -bbt=1 # Medium block brace tightness -nsfs # No space before semicolons -nolq # Don't outdent long quoted strings -wbb=quot;% + - * / x != == >= <= =~ !~ < > | & >= < = **= += *= &= <<= &&= -= /= |= >>= ||= .= %= ^= x=quot; # Break before all operators -nsak=quot;my local our if elsif until unless while for foreach return switch case given whenquot; -bar -cab=3 -wrs=quot;! ,quot; # want right space after these tokens -wls=quot;!quot; # want left space after !
  • 117. ZFML
  • 118. $ perltidy index.zfml There is no previous '?' to match a ':' on line 4 4: <title>AcmeCorp: Widgets, Gadgets and Doodads</title> ^ 5: <meta http-equiv=quot;content-typequot; content=quot;text/html;charset=iso-8 ... -------------- ^ found bareword where operator expected (previous token underlined) 5: ... ent=quot;text/html;charset=iso-8859-1quot; /> -^ found > where term expected (previous token underlined) 7: <meta NAME=quot;keywordsquot; CONTENT=quot;AcmeCorp, widgets, gadgets ... ---------- ^ found bareword where operator expected (previous token underlined) 9: @import url(/AcmeCorp/templates/gateway85styles.css); ^ found Array where operator expected Missing ';' above? 9: @import url(/AcmeCorp/templates/gateway85styles.css); ------- ^ found bareword where operator expected (previous token underlined) 9: @import url(/AcmeCorp/templates/gateway85styles.css); ---------^ found bareword where operator expected (previous token underlined) Missing ';' above? to match a ':' on line 14 There is no previous '?' fix valid */ 14: max-height: 140px; /* to ^ Missing ';' above? 15 There is no previous '?' to match a ':' on line 15: padding: 12px; margin-top: 5px; border-top: 1px solid #e0e0e0; ^ There is no previous '?' to match a ':' on line 15 15: padding: 12px; margin-top: 5px; border-top: 1px solid #e0e0e0; ^ There is no previous '?' to match a ':' on line 15 15: padding: 12px; margin-top: 5px; border-top: 1px solid #e0e0e0; ^
  • 119. Working with perlcritic $ perlcritic -1 myObj.pm myObj.pm source OK
  • 121. but...
  • 123.
  • 124. Ensuring your tests fully exercise your code
  • 125. Introducing Devel::Cover Devel::Cover(3) Perl Documentation Devel::Cover(3) NAME Devel::Cover - Code coverage metrics for Perl SYNOPSIS perl -MDevel::Cover yourprog args cover perl -MDevel::Cover=-db,cover_db,-coverage,statement,time yourprog args To test an uninstalled module: cover -delete HARNESS_PERL_SWITCHES=-MDevel::Cover make test cover
  • 126. huh?
  • 127. Introducing Devel::Cover $ perl -MDevel::Cover testobj.t 1..8 ok 1 - use myObj; ... # some Devel::Cover output snipped ok 2 - can create a myObj specifying values ok 3 - The object isa myObj ok 4 - can create a myObj not specifying values ok 5 - The object isa myObj ok 6 - can set name ok 7 - can get name ok 8 - obj1 seems deeply similar to obj2 Devel::Cover: Writing coverage database to /Users/kentcowgill/cover_db/runs/ 1169095517.23575.48192 ---------------------------- ------ ------ ------ ------ ------ ------ ------ File stmt bran cond sub pod time total ---------------------------- ------ ------ ------ ------ ------ ------ ------ myObj.pm 100.0 n/a 100.0 100.0 n/a 23.5 100.0 testobj.t 100.0 n/a n/a 100.0 n/a 76.5 100.0 Total 100.0 n/a 100.0 100.0 n/a 100.0 100.0 ---------------------------- ------ ------ ------ ------ ------ ------ ------
  • 128. Introducing cover $ cover Reading database from /Users/kentcowgill/cover_db ---------------------------- ------ ------ ------ ------ ------ ------ ------ File stmt bran cond sub pod time total ---------------------------- ------ ------ ------ ------ ------ ------ ------ myObj.pm 100.0 n/a 100.0 100.0 n/a 23.5 100.0 testobj.t 100.0 n/a n/a 100.0 n/a 76.5 100.0 Total 100.0 n/a 100.0 100.0 n/a 100.0 100.0 ---------------------------- ------ ------ ------ ------ ------ ------ ------ Writing HTML output to /Users/kentcowgill/cover_db/coverage.html ... done.
  • 129. Introducing cover $ cover Reading database from /Users/kentcowgill/cover_db ---------------------------- ------ ------ ------ ------ ------ ------ ------ File stmt bran cond sub pod time total ---------------------------- ------ ------ ------ ------ ------ ------ ------ myObj.pm 100.0 n/a 100.0 100.0 n/a 23.5 100.0 testobj.t 100.0 n/a n/a 100.0 n/a 76.5 100.0 Total 100.0 n/a 100.0 100.0 n/a 100.0 100.0 ---------------------------- ------ ------ ------ ------ ------ ------ ------ Writing HTML output to /Users/kentcowgill/cover_db/coverage.html ... done.
  • 130.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138. Tweaking Devel::Cover Devel::Cover(3) Perl Documentation Devel::Cover(3) OPTIONS ... -ignore RE - Set REs of files to ignore +ignore RE - Append to REs of files to ignore.
  • 139. Tweaking Devel::Cover $ perl -MDevel::Cover=+ignore,.*.t testobj.t 1..8 ok 1 - use myObj; ... # Devel::Cover output snipped Ignoring packages matching: /Devel/Cover[./] .*.t ... # Devel::Cover output snipped ok 2 - can create a myObj specifying values ok 3 - The object isa myObj ok 4 - can create a myObj not specifying values ok 5 - The object isa myObj ok 6 - can set name ok 7 - can get name ok 8 - obj1 seems deeply similar to obj2 Devel::Cover: Writing coverage database to /Users/kentcowgill/cover_db/runs/ 1169096938.23619.10353 ---------------------------- ------ ------ ------ ------ ------ ------ ------ File stmt bran cond sub pod time total ---------------------------- ------ ------ ------ ------ ------ ------ ------ myObj.pm 100.0 n/a 100.0 100.0 n/a 100.0 100.0 Total 100.0 n/a 100.0 100.0 n/a 100.0 100.0 ---------------------------- ------ ------ ------ ------ ------ ------ ------
  • 141. Tweaking prove $ prove -MDevel::Cover=+ignore,.*.t testobj.t
  • 142. Tweaking prove $ prove -MDevel::Cover=+ignore,.*.t testobj.t Unknown option: M Unknown option: e Unknown option: e Unknown option: : Unknown option: : Unknown option: C Unknown option: o Unknown option: e Unknown option: = Unknown option: + Unknown option: i Unknown option: g Unknown option: n Unknown option: o Unknown option: e Unknown option: , Unknown option: . Unknown option: * Unknown option: .
  • 143. ouch!
  • 144. Tweaking prove $ PERL5OPT=-MDevel::Cover=+ignore,.*.t prove -v testobj.t testobj....1..8 ok 1 - use myObj; ok 2 - can create a myObj specifying values ok 3 - The object isa myObj ok 4 - can create a myObj not specifying values ok 5 - The object isa myObj ok 6 - can set name ok 7 - can get name ok 8 - obj1 seems deeply similar to obj2 ok All tests successful. Files=1, Tests=8, 3 wallclock secs ( 3.18 cusr + 0.08 csys = 3.26 CPU)
  • 145. Tweaking prove $ cover Reading database from /Users/kentcowgill/cover_db ---------------------------- ------ ------ ------ ------ ------ ------ ------ File stmt bran cond sub pod time total ---------------------------- ------ ------ ------ ------ ------ ------ ------ /usr/bin/prove 73.7 43.8 0.0 46.7 n/a 98.0 61.1 myObj.pm 100.0 n/a 100.0 100.0 n/a 2.0 100.0 Total 78.0 43.8 40.0 60.0 n/a 100.0 66.9 ---------------------------- ------ ------ ------ ------ ------ ------ ------ Writing HTML output to /Users/kentcowgill/cover_db/coverage.html ... done.
  • 146. uh, was that 'prove' in there?
  • 147. Tweaking prove $ cover Reading database from /Users/kentco ---------------------------- ------ File stmt ---------------------------- ------ /usr/bin/prove 73.7 myObj.pm 100.0
  • 148. Tweaking prove $ cover Reading database from /Users/kentco ---------------------------- ------ File stmt ---------------------------- ------ /usr/bin/prove 73.7 myObj.pm 100.0
  • 150. Tweaking prove $ PERL5OPT=-MDevel::Cover=+ignore,.*.t,+ignore,prove prove -v testobj.t testobj....1..8 ok 1 - use myObj; ok 2 - can create a myObj specifying values ok 3 - The object isa myObj ok 4 - can create a myObj not specifying values ok 5 - The object isa myObj ok 6 - can set name ok 7 - can get name ok 8 - obj1 seems deeply similar to obj2 ok All tests successful. Files=1, Tests=8, 3 wallclock secs ( 3.18 cusr + 0.08 csys = 3.26 CPU)
  • 151. Saving Some Typing $ cat Makefile OPENCMD = open BROWSER = /Applications/Safari.app clean: cover -delete test: prove testobj.t cover: make clean PERL5OPT=-MDevel::Cover=+ignore,.*.t,+ignore,prove make test 2>&1 cover make report report: $(OPENCMD) $(BROWSER) cover_db/coverage.html
  • 152. Saving Some Typing $ make cover make clean cover -delete Deleting database /Users/kentcowgill/cover_db PERL5OPT=-MDevel::Cover=+ignore,.*.t,+ignore,prove make test 2>&1 prove testobj.t testobj....1..8 testobj....ok All tests successful. Files=1, Tests=8, 7 wallclock secs ( 3.22 cusr + 0.09 csys = 3.31 CPU) cover Reading database from /Users/kentcowgill/cover_db ---------------------------- ------ ------ ------ ------ ------ ------ ------ File stmt bran cond sub pod time total ---------------------------- ------ ------ ------ ------ ------ ------ ------ myObj.pm 100.0 n/a 100.0 100.0 n/a 100.0 100.0 Total 100.0 n/a 100.0 100.0 n/a 100.0 100.0 ---------------------------- ------ ------ ------ ------ ------ ------ ------ Writing HTML output to /Users/kentcowgill/cover_db/coverage.html ... done. make report open /Applications/Safari.app cover_db/coverage.html
  • 153.
  • 154. 100% yay! 8-D
  • 155. Introducing Test::ZFML Test::Zfml(3) User Contributed Perl Documentation Test::Zfml(3) NAME Test::ZFML - Custom Test:: module built specifically for parsing ZFML. DESCRIPTION Long has it been lamented that AcmeCorp's implementation of ZFML (and who knows what that really stands for) is unmaintainable, and more importantly untestable. No more. Test::ZFML attempts to make the unparseable parseable, the unmaintain- able maintainable, and the untestable testable. It does this by implementing it's own mini ZFML parser and places chunks of ZFML inside their own package, surrounded by their own subroutines which have defined inputs and testable outputs.
  • 156. Using Test::ZFML #!/usr/bin/perl use strict; use warnings; use Test::More qw/no_plan/; use Test::ZFML; use ZFML; my $p = ZFML->new(); my $file = q[test.zfml]; load_ok( $file, quot;Loaded ZFML file $filequot; ); parse_ok( $file, quot;Parsed ZFML file $filequot; ); evaluate_ok( $file, quot;Evaluated ZFML file $filequot; ); critique_ok( $file, quot;Critiqued ZFML file $filequot; );
  • 158. but...
  • 161. How'd you do that?
  • 162. Test::Builder::Module NAME Test::Builder::Module - Base class for test modules SYNOPSIS # Emulates Test::Simple package Your::Module; my $CLASS = __PACKAGE__; use base 'Test::Builder::Module'; @EXPORT = qw(ok); sub ok ($;$) { my $tb = $CLASS->builder; return $tb->ok(@_); } 1;
  • 163. Test::Builder::Module NAME Test::Builder::Module - Base class for test modules SYNOPSIS # Emulates Test::Simple package Your::Module; my $CLASS = __PACKAGE__; use base 'Test::Builder::Module'; Start @EXPORT = qw(ok); Here sub ok ($;$) { my $tb = $CLASS->builder; return $tb->ok(@_); } 1;
  • 164. Test::ZFML package Test::ZFML; use strict; use warnings; use Perl::Critic qw/critique/; use Test::HTML::Lint (); use Carp; use lib '/Users/kentcowgill/acmecorp/lib'; use ZFML; use vars qw/$VERSION @ISA @EXPORT %EXPORT_TAGS $TODO/; use base q/Test::Builder::Module/; @EXPORT = qw/load_ok parse_ok evaluate_ok critique_ok replace_ok contains_ok lacks_ok html_ok/;
  • 165. Test::ZFML Standard package Test::ZFML; stuff use strict; use warnings; use Perl::Critic qw/critique/; use Test::HTML::Lint (); use Carp; use lib '/Users/kentcowgill/acmecorp/lib'; use ZFML; use vars qw/$VERSION @ISA @EXPORT %EXPORT_TAGS $TODO/; use base q/Test::Builder::Module/; @EXPORT = qw/load_ok parse_ok evaluate_ok critique_ok replace_ok contains_ok lacks_ok html_ok/;
  • 166. Test::ZFML # global regexes my $includeparse = qr/<!--s+__(TEMPLATE[sA-Z]*?)__s*?n(.*?)n-->/s; my $htmlparse = qr/<!--s+__([ A-Z_]+)__s*n(.*?)n-->/s; my $zfmlparse = qr/(<!--s+__(?:EVAL|INIT|POST) [^ ]+__s*n.*?n-->)/s; my $zfmlextract = qr/<!--s+__(EVAL|INIT|POST) ([^ ]+)__s*n(.*?)n-->/s;
  • 167. Test::ZFML Icky regexes # global regexes my $includeparse = qr/<!--s+__(TEMPLATE[sA-Z]*?)__s*?n(.*?)n-->/s; my $htmlparse = qr/<!--s+__([ A-Z_]+)__s*n(.*?)n-->/s; my $zfmlparse = qr/(<!--s+__(?:EVAL|INIT|POST) [^ ]+__s*n.*?n-->)/s; my $zfmlextract = qr/<!--s+__(EVAL|INIT|POST) ([^ ]+)__s*n(.*?)n-->/s;
  • 168. Test::ZFML sub load_ok { my $desc; ( $file_to_test, $desc ) = @_; _load_file( $file_to_test ); $zfml_filestate = LOADED; my $tb = Test::ZFML->builder; # minimal (testable) sanity check, ensures that # $file_contents has contents $tb->ok( $file_contents, $desc ); }
  • 169. Test::ZFML Load the file sub load_ok { my $desc; ( $file_to_test, $desc ) = @_; _load_file( $file_to_test ); $zfml_filestate = LOADED; my $tb = Test::ZFML->builder; # minimal (testable) sanity check, ensures that # $file_contents has contents $tb->ok( $file_contents, $desc ); }
  • 170. Test::ZFML sub _load_file { $file_to_test = shift; _get_contents( $file_contents, $file_to_test ); push @vars, grep { ! /^$(ENV|inp)/ } $file_contents =~ m/($[A-Z_]+)/g; return; }
  • 171. Test::ZFML sub _load_file { $file_to_test = shift; _get_contents( $file_contents, $file_to_test ); push @vars, grep { ! /^$(ENV|inp)/ } $file_contents =~ m/($[A-Z_]+)/g; return; } Just does a slurp
  • 172. Test::ZFML sub parse_ok { my( $file, $p, $desc ) = @_; croak 'wrong file' if $file ne $file_to_test; croak 'You must load the file first' if $zfml_filestate != LOADED; _parse_file( $p ); $zfml_filestate = PARSED; my $tb = Test::ZFML->builder; # minimal (testable) sanity check, ensures that # $stuff got stuffed $tb->ok( $stuff, $desc ); }
  • 173. Test::ZFML sub parse_ok { my( $file, $p, $desc ) = @_; croak 'wrong file' if $file ne $file_to_test; croak 'You must load the file first' if $zfml_filestate != LOADED; _parse_file( $p ); $zfml_filestate = PARSED; my $tb = Test::ZFML->builder; # minimal (testable) sanity check, ensures that # $stuff got stuffed $tb->ok( $stuff, $desc ); Parse the } file
  • 174. Test::ZFML sub _parse_file { my( $p ) = @_; # grab the executable hunks of perl code my @zfml = $file_contents =~ /$zfmlparse/g; $file_contents =~ s/$zfmlparse//g; # grab the hunks that are responsible for templates my %includes = $file_contents =~ /$includeparse/g; $file_contents =~ s/$includeparse//g; # finally, grab the hunks that get turned into HTML my %zfmlvars = $file_contents =~ /$htmlparse/g; $file_contents =~ s/$htmlparse//g; ...
  • 175. Test::ZFML Really parse it sub _parse_file { my( $p ) = @_; # grab the executable hunks of perl code my @zfml = $file_contents =~ /$zfmlparse/g; $file_contents =~ s/$zfmlparse//g; # grab the hunks that are responsible for templates my %includes = $file_contents =~ /$includeparse/g; $file_contents =~ s/$includeparse//g; # finally, grab the hunks that get turned into HTML my %zfmlvars = $file_contents =~ /$htmlparse/g; $file_contents =~ s/$htmlparse//g; ...
  • 176. Test::ZFML ... for my $key( keys %includes ){ # process all the include files :) my $tb = Test::Zfml->builder; $tb->ok( _get_includes( $key, $includes{ $key }, $file_to_test ), quot;Included $key file $includes{ $key }quot; ); } for my $key( keys %zfmlvars ){ $p->var->{$key} = $zfmlvars{$key}; } for my $zfml( @zfml ){ if( $zfml =~ m/$zfmlextract/s ) { push @{ $stuff->{$1} }, { $2 => $3 }; } } } # end
  • 177. Test::ZFML ... for my $key( keys %includes ){ # process all the include files :) my $tb = Test::Zfml->builder; $tb->ok( _get_includes( $key, $includes{ $key }, $file_to_test ), quot;Included $key file $includes{ $key }quot; ); } Chug for my $key( keys %zfmlvars ){ $p->var->{$key} = $zfmlvars{$key}; } through for my $zfml( @zfml ){ it if( $zfml =~ m/$zfmlextract/s ) { push @{ $stuff->{$1} }, { $2 => $3 }; } } } # end
  • 178. Test::ZFML sub _get_includes { my( $name, $file, $fromfile ) = @_; my $filepath = quot;$webroot/$filequot;; if( $filepath =~ /$VERSION/ ){ $filepath =~ s/$VERSION/$version/; } if( $filepath =~ /$LOCAL/ ){ my $path = $fromfile; $path =~ s/^.+?/(.+)/[a-z.]+$/$version/$1/; $filepath =~ s/$LOCAL/$path/; } my $tb = Test::ZFML->builder(); $tb->ok( -e $filepath, quot;Inlude/Template file ($filepath) Existsquot; ); ...
  • 179. Test::ZFML Process sub _get_includes { my( $name, $file, $fromfile ) = @_; my $filepath = quot;$webroot/$filequot;; included if( $filepath =~ /$VERSION/ ){ files $filepath =~ s/$VERSION/$version/; } if( $filepath =~ /$LOCAL/ ){ my $path = $fromfile; $path =~ s/^.+?/(.+)/[a-z.]+$/$version/$1/; $filepath =~ s/$LOCAL/$path/; } my $tb = Test::ZFML->builder(); $tb->ok( -e $filepath, quot;Inlude/Template file ($filepath) Existsquot; ); ...
  • 180. Test::ZFML ... open( my $tmp, '<', $filepath ) or die quot;can't open include filequot;; my @file = <$tmp>; my $contents; for my $line ( @file ){ $contents .= $line; if( $line =~ m/$([A-Z]+)s/ ){ eval quot;$testzfml::$1 = 'dummy content'quot;; } if( $line =~ m/var->{'([A-Z_]+)'}/ ){ eval quot;$testzfml::$1 = 'dummy content'quot;; } } my %includes = $contents =~ /$includeparse/g; for my $key( keys %includes ){ _get_includes( $key, $includes{ $key }, $file ); } close( $tmp ); }
  • 181. Test::ZFML Evaluate, ... evaluate, open( my $tmp, '<', $filepath ) or die quot;can't open include filequot;; my @file = <$tmp>; evaluate my $contents; for my $line ( @file ){ $contents .= $line; if( $line =~ m/$([A-Z]+)s/ ){ eval quot;$testzfml::$1 = 'dummy content'quot;; } if( $line =~ m/var->{'([A-Z_]+)'}/ ){ eval quot;$testzfml::$1 = 'dummy content'quot;; } } my %includes = $contents =~ /$includeparse/g; for my $key( keys %includes ){ _get_includes( $key, $includes{ $key }, $file ); } close( $tmp ); }
  • 182. Test::ZFML sub evaluate_ok { my( $file, $p, $desc ) = @_; croak 'wrong file' if $file ne $file_to_test; croak 'You must parse the file first' if $zfml_filestate != PARSED; $zfml_filestate = EVALED; for my $hunk ( keys %{$stuff} ) { for my $evals ( @{ $stuff->{$hunk} } ) { for my $var ( keys %{$evals} ) { _evaluate_code( $p, $hunk, $var, $evals->{$var}, $file, $desc ); } } } # loads everything into memory for testing require $_ for @cov_files; ## no critic }
  • 183. Test::ZFML sub evaluate_ok { my( $file, $p, $desc ) = @_; croak 'wrong file' if $file ne $file_to_test; croak 'You must parse the file first' if $zfml_filestate != PARSED; $zfml_filestate = EVALED; for my $hunk ( keys %{$stuff} ) { for my $evals ( @{ $stuff->{$hunk} } ) { for my $var ( keys %{$evals} ) { _evaluate_code( $p, $hunk, $var, $evals->{$var}, $file, $desc ); } Really } } evaluate # loads everything into memory for testing require $_ for @cov_files; ## no critic it }
  • 184. Test::ZFML sub _evaluate_code { my( $p, $eval_init, $name, $hunk, $file, $desc ) = @_; $file =~ s/.*/(.*)$/$1/; my $subname = quot;$eval_init$namequot;; $hunk = _wrap_hunk( $hunk, $subname ); my $filename = quot;$file.$subnamequot;; my $tb = Test::ZFML->builder; # Writing the contents out to a file so I can run # the tests with Devel::Cover turned on. open my $cov, '>', quot;.$filenamequot;; print {$cov} $hunk; close $cov; push @cov_files, quot;.$filenamequot;; eval quot;require '.$filename';quot;; ## no critic $tb->ok( ! $@, quot;$desc chunk ( $filename ) $@quot; ); eval quot;testzfml::$subname( $p );quot;; die quot;eval failed - $@quot; if $@; }
  • 185. Test::ZFML sub _evaluate_code { my( $p, $eval_init, $name, $hunk, $file, $desc ) = @_; $file =~ s/.*/(.*)$/$1/; my $subname = quot;$eval_init$namequot;; $hunk = _wrap_hunk( $hunk, $subname ); my $filename = quot;$file.$subnamequot;; my $tb = Test::ZFML->builder; # Writing the contents out to a file so I can run # the tests with Devel::Cover turned on. open my $cov, '>', quot;.$filenamequot;; Write print {$cov} $hunk; close $cov; out files push @cov_files, quot;.$filenamequot;; eval quot;require '.$filename';quot;; ## no critic $tb->ok( ! $@, quot;$desc chunk ( $filename ) $@quot; ); for Code eval quot;testzfml::$subname( $p );quot;; die quot;eval failed - $@quot; if $@; } Coverage
  • 186. Test::ZFML sub _wrap_hunk { my( $hunk, $subname ) = @_; # HEREDOCs inside eval aren't recognizable as HEREDOCs. # This re-quotes HEREDOCs as q()/qq() strings. if( $hunk =~ m/<</s ) { # replace all intended quoting chars with an HTML entity $hunk =~ s/|/&#124;/gs; $hunk =~ s/=s* # start of an assignment << # involving a heredoc ('|quot;) # using a quoting delimiter (?{ $1 eq q(quot;) ? 'qq' : 'q' }) # which we'll remember in $^R ([A-Z]+) # next the heredoc token 1; # close quoting delimiter (.*?)n # the heredoc 2 # closing heredoc token /= $^R|$3|;/gsx; # replace with quoting } ...
  • 187. Test::ZFML sub _wrap_hunk { my( $hunk, $subname ) = @_; # HEREDOCs inside eval aren't recognizable as HEREDOCs. # This re-quotes HEREDOCs as q()/qq() strings. if( $hunk =~ m/<</s ) { # replace all intended quoting chars with an HTML entity $hunk =~ s/|/&#124;/gs; $hunk =~ s/=s* # start of an assignment << # involving a heredoc ('|quot;) # using a quoting delimiter (?{ $1 eq q(quot;) ? 'qq' : 'q' }) Wrap # which we'll remember in $^R Heredocs ([A-Z]+) # next the heredoc token 1; # close quoting delimiter (.*?)n # the heredoc 2 # closing heredoc token /= $^R|$3|;/gsx; # replace with quoting } ...
  • 188. Test::ZFML ... my $chunk; # wrap the hunk with its own package, strictures and # warnings enabled, a sigwarn handler that causes eval # errors ($@) to throw a test ok() error, and callable via a # subroutine call. $chunk = <<quot;EOCquot;; package testzfml; use strict; use warnings; use ZFML; BEGIN { $SIG{'__WARN__'} = sub { die $_[0] } } ## no critic sub $subname { $hunk } 1; EOC return $chunk; }
  • 189. Test::ZFML ... my $chunk; # wrap the hunk with its own package, strictures and # warnings enabled, a sigwarn handler that causes eval # errors ($@) to throw a test ok() error, and callable via a # subroutine call. $chunk = <<quot;EOCquot;; package testzfml; use strict; use warnings; use ZFML; BEGIN { $SIG{'__WARN__'} = sub { die $_[0] } } ## no critic sub $subname { Wrap it in $hunk } it's own 1; EOC namespace return $chunk; }
  • 190. Test::ZFML sub critique_ok { my( $file, $desc ) = @_; croak 'wrong file' if $file ne $file_to_test; for my $hunk ( keys %{$stuff} ) { for my $evals ( @{ $stuff->{$hunk} } ) { for my $var ( keys %{$evals} ) { _critique_code( $hunk, $var, $evals->{$var}, $desc ); } } } }
  • 191. Test::ZFML sub critique_ok { my( $file, $desc ) = @_; croak 'wrong file' if $file ne $file_to_test; for my $hunk ( keys %{$stuff} ) { for my $evals ( @{ $stuff->{$hunk} } ) { for my $var ( keys %{$evals} ) { _critique_code( $hunk, $var, $evals->{$var}, $desc ); } } } } Critique it
  • 192. Test::ZFML sub _critique_code { my( $eval_init, $name, $hunk, $desc ) = @_; my $subname = quot;$eval_init$namequot;; my $problems = 0; $hunk = _wrap_hunk( $hunk, $subname ); my $tb = Test::ZFML->builder; for my $violation ( critique( { -severity => 1, -verbose => 1 }, $hunk ) ){ $tb->ok( ! $violation, quot;Critique problem: $violationquot; ); $problems++; } $tb->ok( ! $problems, quot;$desc chunk ( $subname )quot; ); return; }
  • 193. Test::ZFML Report sub _critique_code { violations my( $eval_init, $name, $hunk, $desc ) = @_; my $subname = quot;$eval_init$namequot;; my $problems = 0; $hunk = _wrap_hunk( $hunk, $subname ); my $tb = Test::ZFML->builder; for my $violation ( critique( { -severity => 1, -verbose => 1 }, $hunk ) ){ $tb->ok( ! $violation, quot;Critique problem: $violationquot; ); $problems++; } $tb->ok( ! $problems, quot;$desc chunk ( $subname )quot; ); return; }
  • 194. Test::ZFML sub replace_ok { my( $file, $p, $desc ) = @_; croak 'wrong file' if $file ne $file_to_test; my $tb = Test::ZFML->builder; for my $var (@vars) { my $varname = $var; $varname =~ s/^$//; my $pname = $p->var->{$varname}; $tb->ok( $p->var->{$varname}, quot;$varname found in $filequot; ); $file_contents =~ s/Q$varE/$pname/g; } my %input = %{ $p->input }; $file_contents =~ s/$(input{)'?([A-Za-z_]+)'?}/$$1$2}/g; eval quot;$file_contents = qq|$file_contents|;quot;; }
  • 195. Test::ZFML Replace sub replace_ok { my( $file, $p, $desc ) = @_; croak 'wrong file' if $file ne $file_to_test; special my $tb = Test::ZFML->builder; for my $var (@vars) { variables my $varname = $var; $varname =~ s/^$//; my $pname = $p->var->{$varname}; $tb->ok( $p->var->{$varname}, quot;$varname found in $filequot; ); $file_contents =~ s/Q$varE/$pname/g; } my %input = %{ $p->input }; $file_contents =~ s/$(input{)'?([A-Za-z_]+)'?}/$$1$2}/g; eval quot;$file_contents = qq|$file_contents|;quot;; }
  • 196. Test::ZFML sub contains_ok { my( $file, $p, $regex, $desc ) = @_; croak 'wrong file' if $file ne $file_to_test; $p->render(); my $tb = Test::ZFML->builder; $tb->like( $file_contents, $regex, $desc ); }
  • 197. Check Test::ZFML its' contents sub contains_ok { my( $file, $p, $regex, $desc ) = @_; croak 'wrong file' if $file ne $file_to_test; $p->render(); my $tb = Test::ZFML->builder; $tb->like( $file_contents, $regex, $desc ); }
  • 198. Test::ZFML sub lacks_ok { my( $file, $p, $regex, $desc ) = @_; croak 'wrong file' if $file ne $file_to_test; $p->render(); my $tb = Test::ZFML->builder; $tb->unlike( $file_contents, $regex, $desc ); }
  • 199. Test::ZFML sub lacks_ok { my( $file, $p, $regex, $desc ) = @_; croak 'wrong file' if $file ne $file_to_test; $p->render(); my $tb = Test::ZFML->builder; $tb->unlike( $file_contents, $regex, $desc ); } Make sure it doesn't have specific bits
  • 200. Test::ZFML sub html_ok { my( $file, $desc ) = @_; croak 'wrong file' if $file ne $file_to_test; Test::HTML::Lint::html_ok( $file_contents, $desc ); return; }
  • 201. Test::ZFML Check the HTML sub html_ok { my( $file, $desc ) = @_; croak 'wrong file' if $file ne $file_to_test; Test::HTML::Lint::html_ok( $file_contents, $desc ); return; }
  • 202.
  • 203. Putting it all together • Test::More • prove • Perl::Critic • Devel::Cover • Makefile • Test::ZFML
  • 204. The Test File Screencast demonstration removed for PDF
  • 205. The Makefile Kent-Cowgills-Computer(~/acmecorp)$ cat Makefile OPENCMD = open BROWSER = /Applications/Safari.app clean: cover -delete test: prove t/ verbose: prove -v t/ shuffle: prove -s t/ cover: make clean PERL5OPT=-MDevel::Cover=+ignore,prove,+ignore,lib.*.pm, +ignore,zfml.t make test cover make report report: $(OPENCMD) $(BROWSER) cover_db/coverage.html
  • 206. Running Tests Kent-Cowgills-Computer(~/acmecorp)$ make test prove t/ t/artdesign-zfml....ok t/index-zfml........ok t/testlogin-zfml....ok All tests successful. Files=3, Tests=59, 2 wallclock secs ( 1.24 cusr + 0.20 csys = 1.44 CPU)
  • 207. Test Failure Kent-Cowgills-Computer(~/acmecorp)$ make test prove t/ t/artdesign-zfml....ok 1/29 # Failed test 'HTML passes Lint test' # in /Users/kentcowgill/acmecorp/lib/Test/ZFML.pm at line 136.
  • 208. Test Code Coverage Kent-Cowgills-Computer(~/acmecorp)$ make cover make clean cover -delete Deleting database /Users/kentcowgill/acmecorp/cover_db PERL5OPT=-MDevel::Cover=-ignore,prove,+ignore,lib.*.pm, +ignore,zfml.t make test prove t/ t/artdesign-zfml....ok t/index-zfml........ok t/testlogin-zfml....ok All tests successful. Files=3, Tests=59, 22 wallclock secs (18.46 cusr + 0.48 csys = 18.94 CPU) cover Reading database from /Users/kentcowgill/acmecorp/cover_db Devel::Cover: merging data for .artdesign.zfml.EVALCOPYRIGHT_YEAR into .index.zfml.EVALCOPYRIGHT_YEAR
  • 209. Test Code Coverage Continued... ---------------------------- ------ ------ ------ ------ ------ ------ ------ File stmt bran cond sub pod time total ---------------------------- ------ ------ ------ ------ ------ ------ ------ .artdesign.zfml.INITSETUP 100.0 n/a n/a 100.0 n/a 12.9 100.0 .index.zfml.EVALCOPYRIGHT_YEAR 92.3 n/a n/a 100.0 n/a 63.9 94.4 .index.zfml.INITSETUP 100.0 n/a n/a 100.0 n/a 11.5 100.0 .testlogin.zfml.INITFORM 100.0 75.0 n/a 100.0 n/a 11.8 96.0 Total 98.3 75.0 n/a 100.0 n/a 100.0 97.6 ---------------------------- ------ ------ ------ ------ ------ ------ ------ Writing HTML output to /Users/kentcowgill/acmecorp/cover_db/ coverage.html ... done. make report open /Applications/Safari.app cover_db/coverage.html
  • 213. Introducing Test::WWW::Mechanize Test::WWW::Mechanize(3) Test::WWW::Mechanize(3) NAME Test::WWW::Mechanize - Testing-specific WWW::Mechanize subclass VERSION Version 1.12 SYNOPSIS Test::WWW::Mechanize is a subclass of WWW::Mechanize that incorporates features for web application testing. For example: $mech->get_ok( $page ); $mech->base_is( 'http://petdance.com/', 'Proper <BASE HREF>' ); $mech->title_is( quot;Invoice Statusquot;, quot;On the invoice pagequot; ); $mech->content_contains( quot;Andy Lesterquot;, quot;My name somewherequot; ); $mech->content_like( qr/(cpan|perl).org/, quot;Link: perl or CPANquot; );
  • 214. Planetary::DblClick_tag.pm #!/usr/bin/perl use strict; use warnings; #use Test::More tests => 40; use Test::More 'no_plan'; use Test::WWW::Mechanize; ...
  • 215. Planetary::DblClick_tag.pm #!/usr/bin/perl use strict; use warnings; #use Test::More tests => 40; use Test::More 'no_plan'; use Test::WWW::Mechanize; # .fwpwd contains my AcmeCorp user id and password our( $AcmeCorp_username, $AcmeCorp_password ); require '/Users/kentcowgill/acmecorp/.fwpwd'; # create a new Test::WWW:Mechanize object. my $ua = Test::WWW::Mechanize->new; ...
  • 216. Planetary::DblClick_tag.pm ... # first, get the home page $ua->get_ok( quot;http://test.AcmeCorp.comquot;, quot;Check base URLquot; ); # log in (using kcowgill credentials) $ua->form_number( 1 ); $ua->field( 'EMAIL_ADDRESS', $AcmeCorp_username ); $ua->field( 'PASSWORD', $AcmeCorp_password ); $ua->click( 'returnLogin' ); # basic sanity check that we're on the right page (/AcmeCorp/my/index) $ua->content_contains( quot;Hi, Kent!quot;, quot;received greeting messagequot; ); ...
  • 217. Planetary::DblClick_tag.pm ... # grab the iframe src tag my( $iframe ) = $ua->content =~ m/iframe .*src=quot;([^quot;]+)quot;/; # make sure it's got the right stuff in it. like( $iframe, qr/site=fw/, 'got site=fw in iframe src tag' ); like( $iframe, qr/affiliate=fw/, 'got affiliate=fw in iframe src tag' ); like( $iframe, qr/app=(?:my|other)/, 'got app=my in iframe src tag' ); ...
  • 218. Planetary::DblClick_tag.pm $ prove -v dblclick.t dblclick....ok 1 - Check base URL ok 2 - received greeting message ok 3 - got site=ac in iframe src tag ok 4 - got affiliate=ac in iframe src tag ok 5 - got app=tango in iframe src tag ok 6 - got size=160x600 in iframe src tag ok 7 - got pp=1 in iframe src tag ok 8 - got path=$ID in iframe src tag ok 9 - got dcpc=606 in iframe src tag ok 10 - got ge=2 in iframe src tag ok 11 - got age=19 in iframe src tag ok 12 - got widget=14 in iframe src tag ok 13 - got wango=5 in iframe src tag ok 14 - got state=30 in iframe src tag ok 15 - got tango=3.8 in iframe src tag ok 16 - got doodad=0 in iframe src tag ok 17 - got gadget=0075 in iframe src tag ok 18 - got mtfnpy=4 in iframe src tag ... ok All tests successful. Files=1, Tests=38, 8 wallclock secs ( 0.27 cusr + 0.08 csys = 0.35 CPU)
  • 219. How To Get Started
  • 221. The VIM Plugin Screencast demonstration removed for PDF
  • 224. The Perl Module Screencast demonstration removed for PDF
  • 226. ~fin~
  • 227.
  • 228. References •Wikipedia: http://en.wikipedia.org/wiki/Software_testing http://en.wikipedia.org/wiki/Test_Case •Testing Reference card: http://pub.langworth.com/perl_test_refcard.pdf •Test modules: http://search.cpan.org/dist/Test-Simple/ •ViM script: http://www.vim.org/scripts/script.php?script_id=1985 •Test::StubGenerator: http://search.cpan.org/dist/Test-StubGenerator/ •Screencast software (Mac only): http://www.ambrosiasw.com/utilities/snapzprox/ •Cats: http://icanhascheezburger.com/