I am requesting feedback on code I have written to test subroutines which, in normal use, require the user to enter information at the command-prompt.

Consider this subroutine, which requires the user to enter a 4-digit number beginning with the numeral '4':

sub enter_number { local $_; while () { print "Enter new room number: "; chomp ($_ = <STDIN>); if ($_ =~ /^4\d\d\d$/) { return $_; } else { print "\nMust be a 4-digit number beginning with '4'; try again. +\n"; next; } } }

How would I test this routine using the standard Test::Harness/Test::Builder-based framework? Specifically, how would I have the test framework do something automatically at <STDIN> -- a place where the actual program pauses for user intervention?

I grepped the documentation for 'STDIN'. I googled comp.lang.perl.misc, c.l.p.modules and the perl.module-authors and perl.qa archives. I couldn't find an explicit solution.

I eventually formulated my problem as: ''I want to provide the subroutines with a list of arguments that will be processed as if they were being entered from <STDIN>. I want to tie an array holding that list to the STDIN filehandle.'' Once I had phrased it that way, I studied the section, ''Tying Filehandles'' in Chap. 14 of Programming Perl and wrote the following tie package:

package Preempt::Stdin; use strict; sub TIEHANDLE { my $class = shift; my @lines = @_; bless \@lines, $class; } sub READLINE { my $self = shift; shift @$self; } 1;
This echoes the TieOut.pm module which Michael Schwern uses in Test::More's own test suite -- but this focuses more on working around STDIN rather than STDOUT.

I now can write a script which begins with the package above and proceeds as follows:

package main; use strict; use warnings; my @lines = ( 'a string with numbers and letters 4678', '5678', '46789', ' 4678', '4678', ); tie *STDIN, 'Preempt::Stdin', @lines; my $room = enter_number(); untie *STDIN; print "Room: $room\n";

The script DWIMs. Instead of pausing for user intervention at the prompt, the script shifts elements off @lines and supplies them to enter_room() at <STDIN>. (You will note that I have first supplied four failing values before supplying a value that will succeed.)

How would this look in a testing context? Just throw in some standard code from Test::More.

package main; use strict; use warnings; use Test::More tests => 2; my @lines = ( 'a string with numbers and letters 4678', '5678', '46789', ' 4678', '4678', ); tie *STDIN, 'Preempt::Stdin', @lines; my $room; ok($room = enter_number(), 'enter_number executed'); is(4678, $room, 'predicted value achieved'); untie *STDIN; print "Room: $room\n"; sub enter_number { local $_; while () { print "Enter new room number: "; chomp ($_ = <STDIN>); if ($_ =~ /^4\d\d\d$/) { return $_; } else { print "\nMust be a 4-digit number beginning with '4'; try +again.\n"; next; } } }

If you wanted to clean this up further, you could capture all those prompt messages going to STDOUT with IO::Capture::Stdout -- a CPAN module I was recently turned on to (cf. perl.qa mailing list).

But there are problems.

First, this doesn't work if I have used Perl's 'diamond' line-input operator <> instead of <STDIN>. Tying to STDIN appears to have no effect on the line-input operator; the line-input operator simply pauses and waits for input from the regular STDIN.

Second, if the code being tested calls for input from STDIN more times than there are elements in the array tied to STDIN (or if those elements are undef), and if the user prompt is located (as is likely) within a while (<>) loop, then the program goes into an infinite loop because all elements of the tied array have been shifted off.

So I pose this question to the monastery: Has anyone else come up with a better way to pre-empt STDIN for testing purposes?

Thanks in advance.

Jim Keenan


In reply to Pre-empting STDIN during Testing by jkeenan1

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



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.