Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

comment on

( [id://3333]=superdoc: print w/replies, xml ) Need Help??

Sometimes I run into documentation that is hard to read. It may be poorly written, but more often it just uses jargon or concepts I'm not familiar with or focuses on a use case different from my own. The only way to really understand how the module or Perl feature works is to experiment.

One easy way to experiment is to just try different things on the command line using perl -e'...'. This works well for simple documentation questions, but not for more complex questions:

  • it is easy to mess up quoting on the command line and get garbage results.
  • if setup is needed, I have to do setup again and again.
  • command line history is short lived. I can't go back next week and see what worked and didn't.
  • I can't annotate command line history with what I thought I got vs. what I actually did get. Knowing both can help me understand the syntax or module better.

Another way is to write a short script with lots of sample calls and syntax. That way you eliminate repeat setup. You can also keep a history and notes, but it still isn't perfect. One still needs to visually check the answers and comment out incantations that didn't work. You may also have to write out a lot of print statements to see and compare results. That can get tiresome, especially if you decide that you "might have had it right with an earlier example after all".

Fortunately, you can skip all that work by getting a bit creative with Test::More. Test::More is normally used to test code you wrote, but tests are so easy and fast to write, that you can also use it to make sense of what other people wrote. A further advantage is that it checks the right and wrong answers for you, so all you need to do is press a button with all your experiments.

If the subroutine output you want to test is a simple scalar, you can make do with knowing exactly two subroutines: is and isnt. First you try something that you think will work, using either is(test expression, ...) or is(eval {...},...), like this:

is($actual, $expected, $descriptionOfWhatYouTried); # to check effect of parameters passed to subroutine is(substr('abcdef', 0, 2), 'ab', q(tried subtr(..,0,2)}); # to check what got captured by a regular expression is(eval { 'abcdef' =~ /^(\w+)/; $1 }, 'ab', q{tried /^\w+/});

If it doesn't work, you get a nice little error message like this:

not ok 1 - tried /^\w+/ # Failed test 'tried /^\w+/' # in Monks/Snippet.pm at line 6. # got: 'abcdef' # expected: 'ab' 1..1 # Looks like you failed 1 test of 1.

Once you've determined that a particular incanation doesn't work you just change is to isnt or comment the test out. Using isnt, however, means that you never need to worry about going back and checking to see if incantation X really did work after all and you just didn't look at it carefully enough the first time. The test still runs, it just now expects the wrong answer.

#changed is to isnt #This doesn't work. Captures 'abcdef' instead of just 'ab' isnt(eval { 'abcdef' =~ /^(\w+)/; $1 }, 'ab', q{tried /^\w+/});

Using Test::More for experimenting is a little different than using it for testing. There are two main differences:

  • test plans. You may prefer not to have a plan.
  • running tests. You may prefer to use perl rather than prove

At the top of TestMore it says "gotta have a plan". A "plan" is just a hard coded count of the number of tests to run. For formal test situations this is a good idea because it is easy to temporarily comment out important tests while debugging and then forget to put them back. However, in experimental mode, adding and commenting out tests is the whole point of the game. It would be awfully tedious to have to change the test count, everytime you came up with a new incantation to try out.

In formal testings we normally use a special command, like prove to run tests. Or we create a custom harness, using App::Prove, the internal guts of prove, as a base. Formal testing uses prove and App::Prove the modules guts of prove because they need to do things like recursively ignore successful tests, search directories for test files, limit printouts to failed tests, and calculate success percentages.

But when we are experimenting, we may not need such statistics. Scripts using Test::More can run just as easily using perl myexperiments.pl. The only difference is that you will see lots more output and won't get statistics. Here's a comparison of the output when all tests are successful:

#using perl MyExperiments.pl ok 1 - using /(..)/ ok 2 - using /(.)\1/ ok 3 - using /((.)\1)/;$2 ok 4 - using /((.)\1)/;$1 ok 5 - using /(.)(\1+)/;"$1$2" ok 6 - using /(..)/ ok 7 - using /(.)\1/ ok 8 - using /((.)\1)/;$2 ok 9 - using /((.)\1)/;$1 ok 10 - using /(.)(\1+)/;"$1$2" #using prove MyExperiments.pl MyExperiments.pl....ok All tests successful. Files=1, Tests=10, 0 wallclock secs ( 0.05 cusr + 0.04 csys = 0.09 +CPU)

Another advantage of using Test::More (or scripts in general) for tests, is that it gets one thinking in terms of sets of experiments, rather than singleton incantations. Suppose we want to learn more about how regular expressions work. We might start with an experiment file like this:

use strict; use warnings; use Test::More qw(no_plan); #number of tests is constantly changing #changed is to isnt: #this doesn't work, results in 'ab' isnt(eval {'abcdeee' =~ /(..)/;$1}, 'eee', q{using /(..)/}); #changed is to isnt: #this printed out only 'e' isnt(eval {'abcdeee' =~ /(.)\1/;$1}, 'eee', q{using /(.)\1/}); #changed is to isnt: #this printed out undef isnt(eval {'abcdeee' =~ /((.)\1)/;$2}, 'eee', q{using /((.)\1)/;$2}); #changed is to isnt: #this also printed out undef isnt(eval {'abcdeee' =~ /((.)\1)/;$1}, 'eee', q{using /((.)\1)/;$1}); #BINGO! this worked is(eval {'abcdeee' =~ /(.)(\1+)/;"$1$2"}, 'eee', q{using /(.)(\1+)/;"$ +1$2"});

But the next day we want to know if the same thing will work right if there are two or more repeating patterns. Which one will it find? We could repeat the whole mess, using cut and paste ... or we could replace 'abcdeee' and 'eee' with $input an $output and then put the whole mess into a subroutine. Doing this lets us run the same experiments (or additional ones) over and over with a variety of different inputs. Like this:

use strict; use warnings; #we have no plan! #usually want to pick and choose tests anyway #so the number is not fixed use Test::More qw(no_plan); #============================================================ # VARIOUS EXPERIEMENTS #============================================================ #--------------------------------------------------- #GOAL: find run of repeating letters #--------------------------------------------------- sub regexRepeat { my ($sInput, $sOutput) = @_; #----------------------------------------- # add tests here to try out different things # if it doesn't work, change is to isnt #------------------------------------------ #changed is to isnt: #this doesn't work, $input='abcdeee' results in 'ab' isnt(eval {$sInput =~ /(..)/;$1}, $sOutput , q{using /(..)/}); #changed is to isnt: #$input='abcdeee' printed out only 'e' isnt(eval {$sInput =~ /(.)\1/;$1}, $sOutput , q{using /(.)\1/}); #changed is to isnt: #$input='abcdeee' printed out undef isnt(eval {$sInput =~ /((.)\1)/;$2}, $sOutput , q{using /((.)\1)/;$2}); #changed is to isnt: #$input='abcdee' also printed out undef isnt(eval {$sInput =~ /((.)\1)/;$1}, $sOutput , q{using /((.)\1)/;$1}); #BINGO! this worked is(eval {$sInput =~ /(.)(\1+)/;"$1$2"}, $sOutput , q{using /(.)(\1+)/;"$1$2"}); } #============================================================ # HERE IS WHERE I SELECT EXPERIMENTS #============================================================ regexRepeat('abcdeeef', 'eee'); #only one to find regexRepeat('abbb;eeef', 'bbb'); #should find first

The above discussion only covers testing subroutines and regular expressions with simple scalar outputs, but the experimental technique can also be adapted to experiment with routines that output more complex data structures: arrays, hashes, and references to the same. Comparing anything using references can be a problem because is and isnt only check the actual reference, not the things stored inside the reference.

However, CPAN has many, many modules for working with complex data and hopefully you can find a few that work for you. This is just a small sampling:

  • Test::More::is_deeply does a very simple walk of the data structures. It ignores blessings which may or may not bother you.
  • Test::Deep provides tools for more complex structures where a simple walk through the data structure is not enough. Its main drawback is that it automatically imports the world. Some people do not like things that pollute namespaces so blithly.
  • Test::Differences uses Data::Dumper to compare strings and complex data structures. Best used with Perl 5.8 and up. Before Perl 5.8, it didn't compare data structures with hashes properly.
  • Data::Match takes an entirely different strategy: using regular expressions as the model for comparing components of a data structure.

Unfortunately, none of the tools for comparing advanced data structures have an easy way to test for "not equal" , as in the is/isnt combination available for scalars. Commenting out, skipping tests (see Test::More for details) or changing the expected value to the one that actually was produced may be your only options.

A final note: this use of Test::More doesn't need to be limited to testing documentation for Perl syntax and modules. Using the various versions of Inline you can test various parameter combinations for non-Perl documentation as well.

Best, beth

Update fixed nonsensical misplaced phrase in first paragraph.


In reply to Using Test::More to make sense of documentation by ELISHEVA

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or How to display code and escape characters are good places to start.
Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others pondering the Monastery: (5)
As of 2024-03-28 18:36 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found