perlmeditation
Discipulus
Good morning nuns and monks,<P>
Nothing to read during next holidays? ;=)<P>
I wrote this tutorial and I'll really appreciate your comments and corrections.<P>
The following tutorial is in part the fruit of a <i>learn by teaching</i> process, so before pointing newbies to my work I need some confirmations.<P>
The tutorial is a step by step journey into perl module development with tests, documentation and git integration. It seemed to me the very minimal approach in late 2018.<P>
Being a long post (over the 64kb perlmonks constraint) the second part is in a reply to this node and because of this, the table of content links are broken for the second part (I'll fix them sooner or later).<P>
This material is already [https://github.com/LorenzoTa/step-by-step-tutorial-on-perl-module-creation-with-tests-and-git|on its github repository] with a long name (I hope can be easier to find). Also the code generated in this tutorial has its own [https://github.com/LorenzoTa/Range-Validator|archived repository].<P>
I'll gladly accept comments here or as pull request (see [https://github.com/LorenzoTa/step-by-step-tutorial-on-perl-module-creation-with-tests-and-git/blob/master/source/contributing.md|contributing]), as you wish, about:<P>
<UL>
<LI> the testing part: i've managed only the basics of testing but i'm quite new in this; please review
<LI> errors in the overall discussion of the matter
<LI> english errors or misuses of terms (I tend to construct phrases in a latin way.. sorry ;)
<LI> git related errors
</UL><P>
The module code presented is.. <i>fairly semplicistic</i> and I do not plan to change it: the tutorial is about all but coding: tests, documentation, distribution and revision control are the points of this guide and I tried to keep everything as small as possible.
If you really cannot resist to rewrite the code of the module, rewrite it all and I can add a TIMTOWTDI section, just for amusement.<P>
By other hand the <C>day eight: other module techniques</C> has room for improvements and additions: if you want to share your own tecquiques about testing, makefile hacking, automating distribution I think this is the place. I choosed <C>module-starter</C> to sketch out the module as it seemed to me simple and complete, but it has some quirks. Other tools examples can be worth another day of tutorial, but keep it simple.<P>
When you have commented this tutorial I'll remove the <C>[RFC]</C> in the title and I'll point newcomers to this guide (or better if it will be reposted in other section?), if you judged it is worth to read.<P>
Thanks!<P>
L*<P>
<B>UPDATE</B> 20 Dec. Added a readmore tag around the below content. The online repository is receiving some pull requests ;) so I added a version number to the doc. [Tux] is very busy but pointed me to [METAMOD://Release::Checklist] and I'l add it to the tutorial.<P>
<hr><P>
<readmore><P>
<h1>Discipulus's step by step tutorial on module creation with tests and git</h1><P>
<a href="#dayzero"><h4>day zero: introduction</h4></a>
<ul>
<li>
<a href="#foreword">foreword</a>
</li>
<li>
<a href="#thebagoftools">the bag of tools</a>
</li>
<li>
<a href="#theplan">the plan</a>
</li>
</ul><P>
<P>
<a href="#dayone"><h4>day one: prepare the ground</h4></a>
<ul>
<li>
<a href="#dayonestep1">step 1) an online repository on github</a>
</li>
<li>
<a href="#dayonestep2">step 2) a new module with module-starter</a>
</li>
<li>
<a href="#dayonestep3">step 3) a local repository with git</a>
</li>
</ul>
<P>
<a href="#daytwo"><h4>day two: some change and tests</h4></a>
<ul>
<li>
<a href="#daytwostep1">step 1) POD documentation</a>
</li>
<li>
<a href="#daytwostep2">step 2) first test</a>
</li>
<li>
<a href="#daytwostep3">step 3) commit changes with git</a>
</li>
<li>
<a href="#daytwostep3">step 4) pushing to github repository</a>
</li>
</ul><P>
<P>
<a href="#daythree"><h4>day three: finally some code</h4></a>
<ul>
<li>
<a href="#daythreestep1">step 1) first lines of code</a>
</li>
<li>
<a href="#daythreestep2">step 2) testing on our own</a>
</li>
<li>
<a href="#daythreestep3">step 3) add dependencies in Makefile.PL</a>
</li>
<li>
<a href="#daythreestep4">step 4) run the new test</a>
</li>
<li>
<a href="#daythreestep5">step 5) commit, add new files and push with git</a>
</li>
</ul><P>
<a href="#dayfour"><h4>day four: the PODist and the coder</h4></a>
<ul>
<li>
<a href="#dayfourstep1">step 1) the educated documentation</a>
</li>
<li>
<a href="#dayfourstep2">step 2) git status again and commit again</a>
</li>
<li>
<a href="#dayfourstep3">step 3) more code...</a>
</li>
<li>
<a href="#dayfourstep4">step 4) ...means more and more tests</a>
</li>
<li>
<a href="#dayfourstep5">step 5) git: a push for two commits</a>
</li>
</ul><P>
<P>
<a href="#dayfive"><h4>day five: deeper tests</h4></a>
<ul>
<li>
<a href="#dayfivestep1">step 1) more validation in the code</a>
</li>
<li>
<a href="#dayfivestep2">step 2) a git excursus</a>
</li>
<li>
<a href="#dayfivestep3">step 3) add deeper tests</a>
</li>
<li>
<a href="#dayfivestep4">step 4) who is ahead? git branches and log</a>
</li>
<li>
<a href="#dayfivestep5">step 5) overall check and release</a>
</li>
<li>
<a href="#dayfivestep6">step 6) test list form</a>
</li>
</ul>
<P>
<a href="#daysix"><h4>day six: testing STDERR</h4></a>
<ul>
<li>
<a href="#daysixstep1">step 1) the problem of empty lists</a>
</li>
<li>
<a href="#daysixstep2">step 2) adding a Carp to the lake</a>
</li>
<li>
<a href="#daysixstep3">step 3) prepare the fishing road: add a dependency for our test</a>
</li>
<li>
<a href="#daysixstep4">step 4) go fishing the Carp in our test</a>
</li>
<li>
<a href="#daysixstep5">step 5) document the new warning feature</a>
</li>
</ul>
<P>
<a href="#dayseven"><h4>day seven: the module is done but not ready</h4></a>
<ul>
<li>
<a href="#daysevenstep1">step 1) sharing</a>
</li>
<li>
<a href="#daysevenstep2">step 2) files in a CPAN distribution</a>
</li>
<li>
<a href="#daysevenstep3">step 3) another kind of test: MANIFEST</a>
</li>
<li>
<a href="#daysevenstep4">step 4) another kind of test: POD and POD coverage</a>
</li>
<li>
<a href="#daysevenstep5">step 5) some README and final review of the work</a>
</li>
<li>
<a href="#daysevenstep6">step 6) try a real CPAN client installation</a>
</li>
</ul> <P>
<a href="#dayeight"><h4>day eight: other module techniques</h4></a>
<ul>
<li>
<a href="#dayeightoptionone">option one - the bare bone module</a>
</li>
<li>
<a href="#dayeightoptiontwo">option two - the Exporter module</a>
</li>
<li>
<a href="#dayeightoptionthree">option three - the OO module</a>
</li>
<li>
<a href="#dayeightothertestingmodules">other testing modules</a>
</li>
<li>
<a href="#dayeightadvancedMakefile.PLusage">advanced Makefile.PL usage</a>
</li>
<li>
<a href="#dayeightadvancedtestingcode">advanced testing code </a>
</li>
</ul><P>
<a href="#bibliography"><h4>bibliography</h4></a>
<ul>
<li>
<a href="#COREdocumentationaboutmodules">CORE documentation about modules</a>
</li>
<li>
<a href="#COREdocumentationabouttesting">CORE documentation about testing</a>
</li>
<li>
<a href="#furtherreadingsaboutmodules">further readings about modules</a>
</li>
<li>
<a href="#furtherreadingsabouttesting">further readings about testing</a>
</li>
</ul><P>
<a href="#acknowledgements"><h4>acknowledgements</h4></a><P>
<a id="dayzero"></a>
<h2>day zero: introduction</h2><P>
<a id="foreword"></a>
<h4>foreword</h4><P>
This tutorial is not about coding: that's it! The code, idea and implementation presented below
are, by choice, futile, piffling and trifling ( the module resulting following this tutorial is available, archived <a href="https://github.com/LorenzoTa/Range-Validator">on its own repository</a>).<P>
This tutorial, by other hands, tries to show to the beginner one possible path in module creation. As always in perl there are many ways to get the job done and mine is far to be the optimal one, but as
I have encountered many difficulties to choice my own path, perhaps sharing my way can help someone else.<P>
There are other similar but different source of knowledge about module creation, notably <a href="https://www.perlmonks.org/index.pl?node_id=431702">José's Guide for creating Perl modules</a>: read this for some point i do not explore (well, read it anyway: it's worth to)<P>
<a id="thebagoftools"></a>
<h4>the bag of tools</h4><P>
As for every duty, check your equipment before starting. You probably already have perl, a shell (or something less fortunate if you are on windows, like me ;) and a favourite text editor or IDE.<P>
But here in this tutorial we'll use <code>git</code> in the command line and <a href="https://github.com">github</a> to store our work in a central point (very handy feature). So get a <code>github</code> account and a <code>git</code> client.<P>
This tutorial will focus on the importance (I'd say preminence or even predominance) of testing while developing a perl module. I wrote lonely scripts for years then I realized that even if my script seemed robust, I have no way to test them in a simple and reliable way.<P>
So we will use the core module <a href="http://perldoc.perl.org/Test/More.html">Test::More</a> and the CPAN one <a href="https://metacpan.org/pod/Test::Exception">Test::Exception</a> in our module so get it installed using your <code>cpan</code> or <code>cpanm</code> client. Take a look to <a href="http://perldoc.perl.org/Test/Simple.html">Test::Simple</a> if you are not used to test.<P>
We also use the core module <a href="http://perldoc.perl.org/Carp.html">Carp</a> to report errors from user point of view.<P>
We use <a href="https://metacpan.org/pod/Module::Starter">Module::Starter</a> to have the skeleton of our module done for us, but, as always there are valid alternatives. Install it.<P>
We'll document our module using POD (Plain Old Documentation) see <a href="https://perldoc.perl.org/perlpod.html">perlpod</a> for reference.<P>
<a id="theplan"></a>
<h4>the plan</h4><P>
Some of your programs or modules work on lists and arrays. Functions inside these programs accept ranges but while you intend what a valid ranges is ( <code>0,1,2</code> or <code>0..2</code> ) you discover that your programs crashed many times because other humans or other programs passed ranges like: <code>0,1..3,2</code> (where <code>2</code> is present twice) or <code>3,2,1</code> (and your application is silently expecting <code>1,2,3</code> ) or <code>9..1</code> or even <code>0,1,good,13..15</code> not being a range at all, or simply <code>1-3</code> being a range for the user but not for you perl code that read it as <code>-2</code>.<P>
Bored of the situation you plan a new module to validate ranges. <code>Range::Validator</code> is the name you choose. Your initial plan is to expose just one sub: <code>validate</code><P>
As in masonry, you need a well prepared plan before starting excavations. Then you need points and lines drawn on the terrain: everything that makes the job complex is part of the job itself.<P>
Look around: you can
bet someone else got your same idea before you. You can also bet he or she was smarter than you and it already uploaded it to <a href="https://metacpan.org">CPAN</a>. <P>
Sharing early is a good principle: if you already have an idea of your module (even before implementing it), can be worth to ask in a forum dedicated to Perl (like <a href="https://www.perlmonks.org">perlmonks.org</a>) posting a RFC post (Request For Comments) or using the dedicated website <a href="http://prepan.org/ ">prepan.org</a>(is not a crowdy place nowadays..;).<P>
Plan it well: it is difficult, but remember that to repair something bad planned is always a worst task.
The basic read is in the core documentation: <a href="http://perldoc.perl.org/index-language.html">perlnewmod</a> is the place to start and <a href="http://perldoc.perl.org/perlmodstyle.html">perlmodstyle</a> is what comes next. Dont miss the basic documentation. <P>
If you want to read more see, in my <a href="https://www.perlmonks.org/index.pl?node_id=1202418#modules">bibliotheca</a>, the scaffold dedicated to modules.<P>
Choose carefully all your names: the module one and names of methods or functions your module exports: good code with bad named methods is many times unusable by others than the author.<P>
Programming is a matter of interfaces. sic. dot. Coding is easy engineering is hard. sic. another dot.
You can change a million of times the implementation, you can never change how other people use your code. So plan well what you offer with your module. You can add in the future new features; you cannot remove not even one of them because someone is already using it in production. Play nice: plan well.<P>
You can profit the read of a wonderful post: <a href="https://www.perlmonks.org/index.pl?node_id=553487">
On Interfaces and APIs</a><P>
<a id="dayone"></a>
<h2>day one: prepare the ground</h2><P>
<a id="dayonestep1"></a>
<h4>step 1) an online repository on github</h4> <P>
Create an empty repository on the github server named Range-Validator (they do not accept <code>::</code> in names) see <a href="https://help.github.com/articles/creating-a-new-repository/">here for instruction</a><P>
<a id="dayonestep2"></a>
<h4>step 2) a new module with module-starter</h4><P>
Open a shell to your scripts location and run the program <code>module-starter</code> that comes within <code>Module::Starter</code> It wants a mail address, the author name and, obviously the module name:<P>
<code>
shell> module-starter --module Range::Validator --author MyName --email MyName@cpan.org
Added to MANIFEST: Changes
Added to MANIFEST: ignore.txt
Added to MANIFEST: lib/Range/Validator.pm
Added to MANIFEST: Makefile.PL
Added to MANIFEST: MANIFEST
Added to MANIFEST: README
Added to MANIFEST: t/00-load.t
Added to MANIFEST: t/manifest.t
Added to MANIFEST: t/pod-coverage.t
Added to MANIFEST: t/pod.t
Added to MANIFEST: xt/boilerplate.t
Created starter directories and files
</code>
A lot of work done for us! The <code>module-starter</code> program created all the above files into a new folder named <code>Range-Validator</code> let's see the content:<P>
<code>
---Range-Validator
| Changes
| ignore.txt
| Makefile.PL
| MANIFEST
| README
|
|---lib
|
| ---Range
| Validator.pm
|
|----t
| 00-load.t
| manifest.t
| pod-coverage.t
| pod.t
|
|----xt
boilerplate.t
</code>
We now have a good starting point to work on. Spend some minute to review the content of the files to get an idea.<P>
<a id="dayonestep3"></a>
<h4>step 3) a local repository with git</h4> <P>
Open another shell for the git client (I prefer to have two, feel free to use just one) to the same path of the above created folder and initialize a git repository (local for the moment) there:<P>
<code>
git-client> git init
Initialized empty Git repository in /path/to/Range-Validator/.git/
</code>
Nothing impressive.. What happened? The above command created a <code>.git</code> directory, ~15Kb of infos, to take track of all changes you'll make to your files inside the <code>Range-Validator</code> folder. In other words it created a git repository. Empty. Empty?!? And all my files?<P>
It's time for a command you'll use many, many times: <code>git status</code><P>
<code>
git-client> git status
On branch master
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
Changes
MANIFEST
Makefile.PL
README
ignore.txt
lib/
t/
xt/
nothing added to commit but untracked files present (use "git add" to track)
</code>
Many terms in the above output would be worth to be explained, but not by me. Just be sure to understand what <code>branch</code>, <code>commit</code>, <code>tracked/untracked</code> means in the git world. Luckily the command is so sweet to add a hint for us as last line: <code>(use "git add" to track)</code> <P>
Git is built for this reason: it can track all modifications we do to code base and it take a picture (a snapshot in git terminology) of the whole code base everytime we commit these changes. But <code>git init</code> initialized an empty repository: we must tell git which files to add to tracked ones.<P>
We simply want to track all files <code>module-starter</code> created for us: <code>git add .</code> add the current directory and all its content to tracked content. Run it and check the status again:<P>
<code>
git-client> git add .
git-client> git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: Changes
new file: MANIFEST
new file: Makefile.PL
new file: README
new file: ignore.txt
new file: lib/Range/Validator.pm
new file: t/00-load.t
new file: t/manifest.t
new file: t/pod-coverage.t
new file: t/pod.t
new file: xt/boilerplate.t
</code>
We added all content but we still have not committed anything! <code>git commit -m "some text"</code> will commit all changes using the message provided as a label for the commit (without <code>-m</code> git will open a text editor to enter the text). Run it and recheck the status again:<P>
<code>
git-client> git commit -m "module-starter created content"
[master (root-commit) 1788c12] module-starter created content
11 files changed, 409 insertions(+)
create mode 100644 Changes
create mode 100644 MANIFEST
create mode 100644 Makefile.PL
create mode 100644 README
create mode 100644 ignore.txt
create mode 100644 lib/Range/Validator.pm
create mode 100644 t/00-load.t
create mode 100644 t/manifest.t
create mode 100644 t/pod-coverage.t
create mode 100644 t/pod.t
create mode 100644 xt/boilerplate.t
git-client> git status
On branch master
nothing to commit, working tree clean
</code>
With the above we committed everything. The status is now <code>working tree clean</code> what better news for a lumberjack used to examine daily tons of dirty logs? ;)<P>
Now we link the local copy and the remote one on github: all examples you find, and even what github propose to you, tell <code>git remote add origin https://github.com/...</code> where <code>origin</code> is not a keyword but just a label, a name: I found this misleading and I use my github name in this place or something
that tell me the meaning, like <code>MyFriendRepo</code>. So from now on we will use <code>YourGithubLogin</code> there.<P>
Add the remote and verify it ( with <code>-v</code> ):
<code>
git-client> git remote add YourGithubLogin https://github.com/YourGithubLogin/Range-Validator.git
git-client> git remote -v
YourGithubLogin https://github.com/YourGithubLogin/Range-Validator.git (fetch)
YourGithubLogin https://github.com/YourGithubLogin/Range-Validator.git (push)
</code>
The verify operation gives us two hints: for the remote repository that we call <code>YourGithubLogin</code> we can do <code>fetch</code> (import all changes you still have not, from the remote repository to your local copy) or <code>push</code> (export your local copy to the remote repository).<P>
Since on github there is nothing and locally we have the whole code base, we definitively want to <code>push</code> and we can do that if and only if, we have the permission in the remote repository. It's our own repository, so no problem (git will ask for the github password). The <code>push</code> wants to know which branch to push: we only have <code>master</code> so:<P>
<code>
git-client> git push YourGithubLogin master
fatal: HttpRequestException encountered.
Username for 'https://github.com': YourGithubLogin
Password for 'https://YourGithubLogin@github.com': ***********
Counting objects: 17, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (17/17), 5.33 KiB | 303.00 KiB/s, done.
Total 17 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), done.
remote:
remote: Create a pull request for 'master' on GitHub by visiting:
remote: https://github.com/YourGithubLogin/Range-Validator/pull/new/master
remote:
To https://github.com/YourGithubLogin/Range-Validator.git
* [new branch] master -> master
</code>
Go to the github website to see what happened: the whole code base is in the online repository too, updated to our last commit (aka our first, unique commit for the moment). From now on we can work on our code from any machine having a <code>git</code> client. To do so we must be diligent and committing and pushing our changes when is the moment, to maintain the online repository up to date. Clean yard, happy master mason.<P>
A whole day is passed, well.. two days, and we did not wrote a single line of perl code: we are starting the right way! Time to go to sleep with a well prepared playground.<P>
<a id="daytwo"></a>
<h2>day two: some change and tests</h2><P>
<a id="daytwostep1"></a>
<h4>step 1) POD documentation</h4> <P>
Well first of all some cleaning: open you local copy of the module <code>/path/to/Range-Validator/lib/Range/Validator.pm</code> in your text editor or IDE. Personally I like the POD documentation to be all together after the <code>__DATA__</code> token rather interleaved with the code. Inside the code I only like to have comments. POD documentation is for the user, comments are for you! After a week or month you'll never remember what your code is doing: comment it explaining what is passing.<P>
So go to the end of the module where the line is the final <code>1;</code> ( remember all modules must have a true return value as last statement) and place, in a new line the <code>__DATA__</code> token. Move all POD after the token. Also cancel the POD and the code relative to <code>function2</code><P>
Then rename <code>function1</code> into <code>validate</code> and change accordingly the name of the POD section too.<P>
Modify the POD part <code>=head1 NAME</code> with a more humble and meaning description: <code>Range::Validator - a simple module to verify array and list ranges</code><P>
Change the <code>=head1 SYNOPSIS</code> part too, removing unneeded text and changing code lines ( see below ): we do not do an object oriented module, so no <code>new</code> method for us. You plan to accept both real ranges and strings representing ranges.<P>
So, if you followed me, the module must look like:
<code>
package Range::Validator;
use 5.006;
use strict;
use warnings;
our $VERSION = '0.01';
sub validate {
}
1;
__DATA__
=head1 NAME
Range::Validator - a simple module to verify array and list ranges
=head1 VERSION
Version 0.01
=cut
=head1 SYNOPSIS
use Range::Validator;
my @range = Range::Validator->validate(0..3); # a valid range
my @range = Range::Validator->validate(0..3,2); # a overlapping range
my @range = Range::Validator->validate('1,3,7'); # a valid range passed as a string
my @range = Range::Validator->validate('1,XXX,3'); # an invalid range passed as a string
# more POD ...
</code>
Ok? Let's check our new POD is correct: open the shell in the directory created yesterday <code>/path/to/Range-Validator</code> and run the following command: <code> perldoc ./lib/Range/Validator.pm </code> <P>
Review the POD. It must be ok. <P>
<a id="daytwostep2"></a>
<h4>step 2) first test</h4> <P>
Now we test if the module syntax is correct. The first simple method is a short one liner using the perl option <code> -I </code> to include <code>./lib</code> in <code>@INC</code> and <code> -MRange::Validator </code> to use our module( see <a href="https://perldoc.perl.org/perlrun.html">
perlrun</a> and <a href="https://perldoc.perl.org/perlvar.html">
perlvar</a> ):<P>
<code>
shell> perl -I ./lib -MRange::Validator -e 1
shell>
</code>
No errors: good! the module can be used and has no syntax errors. But.. one moment: we want to try out all our features, and we plan to add many, using one liners? Are we mad?! No; we will use tests. <P>
Tests are wonderful in perl and planning good tests (a test suite) will save a lot of time in the future and makes your code maintainable. The time you invest writing tests <b>while coding</b> will save a lot of time in the future when you modify the code base. I'm not a theoric of software writing nor an orthodox of test driven development, but to write tests while you code is a very good practice. You can even write tests <b>before coding</b> ie: you write something that test a wanted behaviour, you run it expecting a failure, then you write the code that make the test happy. This is up to you.<P>
What is not a choice is having no test suite or writing all tests at the end of code development. No.<P>
In the day one we used <code>module-starter</code> to produce a skeleton of our module. <code>module-starter</code> was so kind to write a bounch of tests for us in the standard directory <code>/t</code> (ie tests). Tests are run normally during the installation (sorted by their names) of the module but, as we already said, they are the main source of serenity for us as developers. So let's see what <code>module-starter</code> wrote inside <code>/t/00-load.t</code><P>
<code>
#!perl -T
use 5.006;
use strict;
use warnings;
use Test::More;
plan tests => 1;
BEGIN {
use_ok( 'Range::Validator' ) || print "Bail out!\n";
}
diag( "Testing Range::Validator $Range::Validator::VERSION, Perl $], $^X" );
</code>
This perl program use strict and wanrings (you already know they are friends, do you?) then load the core module <a href="https://perldoc.perl.org/Test/More.html">Test::More</a> which generally
requires that you declare how many tests you intend to run ( <code>plan tests => 1</code> ) then inside the <code>BEGIN</code> block use its method <code>use_ok</code> that loads our own module and in case of failure print "Bail out!\n" aka "everything went wrong, leave the boat".<P>
If the above succeeded <a href="https://perldoc.perl.org/Test/More.html">Test::More</a> calls <code>diag</code> that emits a note with the text specified, useful to have while reviewing test output. The module also has the <code>note</code> method that I prefer. Go to the module documentation to have an idea of <a href="https://perldoc.perl.org/Test/More.html">Test::More</a><P>
So, instead of the one liner we can safely call this test:<P>
<code>
shell> perl -I ./lib ./t/00-load.t
"-T" is on the #! line, it must also be used on the command line at ./t/00-load.t line 1.
</code>
The test crash because of the <code>-T</code> that turns taint mode on. Taint mode is base of the security in perl, but for the moment we do not need it enabled, so we remove from the shebang line which will result in <code>#!perl</code> (read about taint mode in the official perl documentation: <a href="https://perldoc.perl.org/perlsec.html#Taint-mode">perlsec</a>). <P>
(Note that removing <code>-T</code> switch is not the best thing to do: <code>perl -T -I ./lib ./t/00-load.t</code> is by far a better solution).<P>
After this change the test will run as expected:<P>
<code>
shell> perl -I ./lib ./t/00-load.t
ok 1 - use Range::Validator;
1..1
# Testing Range::Validator 0.01, Perl 5.026000, /path/to/my/perl
</code>
Wow! we run our first test! ..yes, but in the wrong way. Well not exactly the wrong way but not the way tests are run during installation.
Test are run through a TAP harness (TAP stands for Test Anything Protocol and is present in perl since ever: perl born the right way ;).<P>
With your perl distribution you have the <a href="https://perldoc.perl.org/prove.html">prove</a> command (see its documentation) that run tests through a TAP harness. So we can use it.<P>
We can call <a href="https://perldoc.perl.org/prove.html">prove</a> the very same way we called perl: <code>prove -I ./lib ./t/00-load.t</code> but we are lazy and we spot <code>prove -l</code> which has the same effect of <code>prove -I ./lib</code> ie include <code>./lib</code> in <code>@INC</code><P>
Run the very same test through <a href="https://perldoc.perl.org/prove.html">prove</a> instead that perl and you will see a slightly different output:<P>
<code>
shell> prove -l ./t/00-load.t
./t/00-load.t .. 1/? # Testing Range::Validator 0.01, Perl 5.026000, /path/to/my/perl
./t/00-load.t .. ok
All tests successful.
Files=1, Tests=1, 0 wallclock secs ( 0.01 usr + 0.02 sys = 0.03 CPU)
Result: PASS
</code>
Basically the output includes some statistics and the count of test files processed and the overall number of tests. Also note that the message emitted by <code>diag</code> is in another place: diagnostics by <a href="https://perldoc.perl.org/Test/More.html">Test::More</a> goes to <code>STDERR</code> (which is buffered differently in respect of <code>STDOUT</code> but this is another story..) and TAP aggregates tests results and prints them to <code>STDOUT</code><P>
Finally we have the developer gratification: <code>Result: PASS</code> indicating all went well.<P>
The <code>prove</code> program promotes laziness and without argument (as a test file in the previous example) runs automatically every test file found under <code>/t</code> folder: this is the same behaviour you will have during an effective module installation:<P>
<code>
shell> prove -l
t\00-load.t ....... 1/? # Testing Range::Validator 0.01, Perl 5.026000, /path/to/my/perl
t\00-load.t ....... ok
t\manifest.t ...... skipped: Author tests not required for installation
t\pod-coverage.t .. skipped: Author tests not required for installation
t\pod.t ........... skipped: Author tests not required for installation
All tests successful.
Files=4, Tests=1, 1 wallclock secs ( 0.06 usr + 0.02 sys = 0.08 CPU)
Result: PASS
</code><P>
<a id="daytwostep3"></a>
<h4>step 3) commit changes with git</h4> <P>
Ok we have done some change to the code base, small ones but changes. Wich changes? I'm lazy and I do not remember all files we modified. No problem <code>git</code> will tell us. At least I remember which command I need to review the code base status: <code>git status</code><P>
Go to the git shell and run it:<P>
<code>
git-client> git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: lib/Range/Validator.pm
modified: t/00-load.t
no changes added to commit (use "git add" and/or "git commit -a")
</code>
Ah yes, we modified two files: not only the module also the <code>t/00-load.t</code> removing the <code>-T</code> from shebang line, thanks <code>git</code> and you are also so kind to give me two hints about what to do next: <code>use "git add" and/or "git commit -a"</code><P>
Go for the shorter path: we commit adding all files with <code>git commit -a</code> ie: we commit all files <b>that are already tracked</b> and eventually we remove from tracked list all files deleted in the code base. But we remember that committing needs to include a message as label of the commit: <code>git commit -m "message"</code> so putting all together and checking the status:<P>
<code>
git-client> git commit -a -m "moved POD, removed -T"
[master 49a0690] moved POD, removed -T
2 files changed, 20 insertions(+), 23 deletions(-)
git-client> git status
On branch master
nothing to commit, working tree clean
</code><P>
<a id="daytwostep4"></a>
<h4>step 4) pushing to github repository</h4> <P>
Ok we submitted, well committed, all changes made. What's next? We have to synchronize the online repository that we named <code>YourGithubLogin</code> so check it and push modified content to it:<P>
<code>
git-client>git remote -v
YourGithubLogin https://github.com/YourGithubLogin/Range-Validator (fetch)
YourGithubLogin https://github.com/YourGithubLogin/Range-Validator (push)
git-client> git push YourGithubLogin master
fatal: HttpRequestException encountered.
Username for 'https://github.com': YourGithubLogin
Password for 'https://YourGithubLogin@github.com':
Counting objects: 7, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), 870 bytes | 435.00 KiB/s, done.
Total 7 (delta 3), reused 0 (delta 0)
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
To https://github.com/YourGithubLogin/Range-Validator
1788c12..49a0690 master -> master
</code>
Go to the browser and open the online repository to see what happened after the <code>git push</code>: in the main page, where files are listed we spot our two modified files with a new timestamp and with the message we used when committing. Under the Insight tab and then under Network in the right menu, we can see two points connected by a line segment: this is the visual history of our repository and each commit we have done: here you will find also eventual branches, but this is another story.<P>
Well, another day is passed without writing a single line of perl code! At least for the moment our code is 100% bug free ;)
I vaguely recall a chinese motto: "when you start something, start from the opposite" or something like that. To write a robust perl module start writing no perl code, for two days!<P>
<a id="daythree"></a>
<h2>day three: finally some code</h2><P>
<a id="daythreestep1"></a>
<h4>step 1) first lines of code</h4> <P>
It's time to put some code inside our <code>validate</code> subroutine. We plan to accept both a string like <code>'1..3,5'</code> and a pure range like <code>1..5,6</code> but let's start with string form assuming only one element will be passed to our sub via <code>@_</code><P>
Remember what said in the foreword: this tutorial is not about coding, so be merciful with following examples.<P>
<code>
sub validate{
my $range;
my @range;
# assume we have a string if we receive only one argument
if ( @_ == 1){
$range = $_[0];
}
# otherwise we received a list
else{
...
}
return @range;
}
</code>
The above is straightforward (if ugly): we get something in via <code>@_</code> (a string or a list) and we return something via <code>return @range</code> To accomplish this we initialize <code>$range</code> to hold our string.<P>
A good principle in loops is "put exit conditions early" and following this principle we put our our die conditions as soon as possible, ie after the if/else check.<P>
But we dont want to die with an ugly message like <code>Died at ../Range/Validator.pm line x</code> ie from the module perspective: we want to inform the user where his code provoked our module to die.<P>
The core module <a href="https://perldoc.perl.org/Carp.html">Carp</a> provides this kind of behaviour and we use its function <code>croak</code> that dies from perspective of the caller.<P>
So we add the line needed to load the module, a first <code>croak</code> call if the string passed in contains forbidden characters and some other line too:<P>
<code>
package Range::Validator;
use 5.006;
use strict;
use warnings;
use Carp; # --- new line
our $VERSION = '0.01';
sub validate{
my $range;
my @range;
# assume we have a string if we receive only one argument
if ( @_ == 1){
$range = $_[0];
}
# otherwise we received a list
else{
...
}
# remove any space from string
$range =~ s/\s+//g; # --- new line
# die if invalid characters
croak "invalid character passed in string [$range]!"
if $range =~ /[^\s,.\d]/; # --- new line
@range = eval ($range); # --- new line
return @range;
}
1;
</code><P>
<a id="daythreestep2"></a>
<h4>step 2) testing on our own</h4> <P>
How to see if all works as expected? Obviously with a test. Not <code>00-load.t</code> but a new one dedicated to the <code>validate</code> sub. So go into the <code>t</code> folder and create a new file <code>01-validate.t</code> and open it to edit the content.<P>
Let's populate it with a basic content plus some new stuff (<code>01-validate.t</code>):<P>
<code>
#!perl
use 5.006;
use strict;
use warnings;
use Test::More qw(no_plan);
use Test::Exception;
use_ok( 'Range::Validator' );
ok (scalar Range::Validator::validate('0..2') == 3,
'ok valid string produces correct number of elements' );
note ("starting test of forbidden characters in the string form");
dies_ok { Range::Validator::validate('xxxinvalidstringxxx') }
"expected to die with invalid character";
</code>
First of all we used a different notation for <a href="https://perldoc.perl.org/Test/More.html">Test::More</a> ie. <code>use Test::More qw(no_plan)</code> <P>
We are telling to the module we (still) have not a plan about how many tests will be in this file. This is a handy feature.<P>
The <a href="https://perldoc.perl.org/Test/More.html">Test::More</a> core module offers us <code>ok</code> <code>use_ok</code> and <code>note</code> methods: view the module doc for more info about them.<P>
But we also used in the above test the <code>dies_ok</code> function: this one comes from the CPAN module <code>Test::Exception</code> and we need to add this module in to our dependencies list.<P>
Dependencies list? What is that? Where we spoke about this? Never, until now. <P>
<a id="daythreestep3"></a>
<h4>step 3) add dependencies in Makefile.PL</h4> <P>
Infact the program <code>module-starter</code> used in day one created a file called <code>Makefile.PL</code> with the following default content:<P>
<code>
use 5.006;
use strict;
use warnings;
use ExtUtils::MakeMaker;
WriteMakefile(
NAME => 'Range::Validator',
AUTHOR => q{MyName <MyName@cpan.org>},
VERSION_FROM => 'lib/Range/Validator.pm',
ABSTRACT_FROM => 'lib/Range/Validator.pm',
LICENSE => 'artistic_2',
PL_FILES => {},
MIN_PERL_VERSION => '5.006',
CONFIGURE_REQUIRES => {
'ExtUtils::MakeMaker' => '0',
},
BUILD_REQUIRES => {
'Test::More' => '0',
},
PREREQ_PM => {
#'ABC' => '1.6',
#'Foo::Bar::Module' => '5.0401',
},
dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
clean => { FILES => 'Range-Validator-*' },
);
</code>
This file is run on the target system trying to install your module. It's vaste matter and you can find many, many useful informations in the core documentation of <a href="https://perldoc.perl.org/ExtUtils/MakeMaker.html">ExtUtils::MakeMaker</a> and in the <a href="https://perldoc.perl.org/ExtUtils/MakeMaker/Tutorial.html">ExtUtils::MakeMaker::Tutorial</a> and, as always in perl, there many ways to do it.<P>
In our simple case we only need to know few facts about <code>BUILD_REQUIRES</code> and <code>PREREQ_PM</code> fields.<P>
The first one lists into a hash all modules and their version needed to build up our module, building includes testing, so if you need some module during tests it is the place where insert dependencies. The <code>module-starter</code> program added <code>'Test::More' => '0'</code> entry for us. This is the right place to state that we intend to use <a href="https://metacpan.org/pod/Test::Exception">Test::Exception</a> CPAN module during tests.<P>
By other hand <code>PREREQ_PM</code> lists modules and their minimal versions needed to run your module. As you can see it's a different thing: to run <code>Range::Validator</code> you never need <code>Test::Exception</code> but, for example you 'll need <code>Carp</code><P>
Even if <code>Carp</code> it's a core module is a good practice to include it into <code>PREREQ_PM</code> <P>
Read a very good post about dependencies <a href="https://www.perlmonks.org/?node_id=946044">Re: How to specify tests dependencies with Makefile.PL?</a><P>
Cleaning example lines and given all the above, we will modify <code>Makefile.PL</code> as follow:<P>
<code>
use 5.006;
use strict;
use warnings;
use ExtUtils::MakeMaker;
WriteMakefile(
NAME => 'Range::Validator',
AUTHOR => q{MyName <MyName@cpan.org>},
VERSION_FROM => 'lib/Range/Validator.pm',
ABSTRACT_FROM => 'lib/Range/Validator.pm',
LICENSE => 'artistic_2',
PL_FILES => {},
MIN_PERL_VERSION => '5.006',
CONFIGURE_REQUIRES => {
'ExtUtils::MakeMaker' => '0',
},
BUILD_REQUIRES => {
'Test::More' => '0',
'Test::Exception' => '0', # --- new line
},
PREREQ_PM => {
'Carp' => '0', # --- new line
},
dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
clean => { FILES => 'Range-Validator-*' },
);
</code>
So the moral is: when you add a dependency needed to run your module or to test it remember to update <code>Makefile.PL</code> correspondent part.<P>
<a id="daythreestep4"></a>
<h4>step 4) run the new test</h4>
Ok, is the above test ok? It returns all we expect? Try it using <code>prove -l</code> but specifying also <code>-v</code> to be verbose and the filename of our new test (now we dont want all test run, just the one we are working on):<P>
<code>
shell> prove -l -v ./t/01-validate.t
./t/01-validate.t ..
ok 1 - use Range::Validator;
ok 2 - ok valid string produces correct number of elements
# starting test of forbidden characters in the string form
ok 3 - expected to die with invalid character
1..3
ok
All tests successful.
Files=1, Tests=3, 0 wallclock secs ( 0.05 usr + 0.01 sys = 0.06 CPU)
Result: PASS
</code><P>
<a id="daythreestep5"></a>
<h4>step 5) commit, add new files and push with git</h4> <P>
What we need more from our first day of coding? To check our status and to synchronize our online repository (pay attention to the following commands because we have a new, untracked file!):<P>
<code>
git-client> git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: Makefile.PL
modified: lib/Range/Validator.pm
Untracked files:
(use "git add <file>..." to include in what will be committed)
t/01-validate.t
no changes added to commit (use "git add" and/or "git commit -a")
git-client> git commit -a -m "some code into validate and modified Makefile.PL"
[master 580f628] some code into validate and modified Makefile.PL
2 files changed, 23 insertions(+), 3 deletions(-)
</code>
We committed before adding the new file! shame on us! Add the new file and issue another commit:<P>
<code>
git-client> git add t/01-validate.t
git-client> git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: t/01-validate.t
git-client> git commit -a -m "added 01-validate.t"
[master 5083ec3] added 01-validate.t
1 file changed, 16 insertions(+)
create mode 100644 t/01-validate.t
git-client> git status
On branch master
nothing to commit, working tree clean
</code>
What more? Ah! pushing to the online repository:
<code>
git-client> git remote -v
YourGithubLogin https://github.com/YourGithubLogin/Range-Validator (fetch)
YourGithubLogin https://github.com/YourGithubLogin/Range-Validator (push)
git-client> git push YourGithubLogin master
fatal: HttpRequestException encountered.
Username for 'https://github.com': YourGithubLogin
Password for 'https://YourGithubLogin@github.com': *********
Counting objects: 10, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (10/10), 1.31 KiB | 447.00 KiB/s, done.
Total 10 (delta 5), reused 0 (delta 0)
remote: Resolving deltas: 100% (5/5), completed with 4 local objects.
To https://github.com/YourGithubLogin/Range-Validator
49a0690..5083ec3 master -> master
</code>
What a day! We added six lines of code and an entire test file! Are we programming too much? Probably no but we are doing it in a robust way and we discovered it can be hard work. In perl hard work is justified only by (future) laziness and we are doing all these work because we are lazy and we do not want to waste our time when, in a month or a year, we need to take this code base again to enhance it or to debug it. So now it's time for the bed and for deserved colorful dreams.<P>
[id://1227455|continue..]
</readmore>
<!-- Wiki2Monks {"version":1.142} -->