Thank you Michael, Danny and Alan for your suggestions.
I've included below my summary of the three very different
suggestions, my brief analysis of them, and my conclusion.
Anyone is free and welcome to comment!

On 4/18/06, Andre Roberge <[EMAIL PROTECTED]> wrote:
> Hi all-
>
> Suppose I had a function like the following:
>
> def y_n(prompt="Answer yes or no"):
>     while True:
>         answer = raw_input(prompt)
>         if answer in ['y', 'Y', 'yes']:
>             print "You said yes!"
>             break
>         elif answer in ['n', 'N', 'no']:
>             print "You said no!"
>             break
>         else:
>             print "%s is an invalid answer."%answer
>
> How could I go about to write an automated test for it?
>
> André
>
Alan Gauld suggested to create a data file that simulate
possible user responses and to use redirection to feed
the data to the program.  Assuming the function to be
tested is in y_n.py, it would look like the following:

$ python y_n.py < fake_user_data > test_result

Then, I assume I would do an automated comparison between
"test_result" and some "expected_result" file to ensure everything
is ok.

This, in my opinion, is the "cleanest" solution.  However, it may
not scale well if one wants to build a test suite with many
functions/methods, some of which require some user-input.
Ideally, one should be able to change the order in which unit tests
are run; with this approach, both the test suite and the user data
file would probably have to be change in sync.

====

Danny Yoo suggested to change the function to be tested, to
include an extra parameter, i.e. go from
def y_n(prompt="Answer yes or no"):
to
def y_n(prompt="Answer yes or no", raw_input=raw_input):

To quote Danny:
But now we can hardcode particular inputs by sending y_n() a mock
"raw_input" that returns precooked values.

###########################
def yes1_raw_input(prompt):
    return "y"

def yes2_raw_input(prompt):
    return "Y"

def yes3_raw_input(prompt):
    return "yes"

def yes4_raw_input(prompt):
    return "YES"
###########################
[end quote]

My first reaction is "Neat! :-)".   However,
this is only usable if one has access to the original
function.  If one uses a "library" function like y_n()
(for example, there is a similar function in the livewires module
which some of you probably know)
which is not, in principle, modifyable, this approach fails.

====

Michael (last name unknown) suggests another approach, which
shares some similarities with Danny's.  Here's Michael's
edited/truncated explanation:

You create a mock for raw_input, put the above code inside a module and rebind
raw_input in the module before calling your function.

ie:
------------(CONTENTS of y_n.py)------------
def y_n(prompt="Answer yes or no"):
   while True:
       answer = raw_input(prompt)
       if answer in ['y', 'Y', 'yes']:
           print "You said yes!"
           break
[snip]
------------(END CONTENTS of y_n.py)------------

[Michael's explanation using the interpreter is modified below]

Then, one can have a test function in a separate file

------- begin test function
import y_n
def raw_input_mock(prompt): # create a mock
    return "n"
y_n.raw_input = raw_input_mock      # rebind the name inside the module
y_n.y_n() # Run, this now calls our mock instead of the real raw_input
------- end test function
[end of quote]

Note that y_n() should probably return a string
(instead of using a print statement)
so that the output can be compared with the expected result;
otherwise, one has to redirect the output to a file
and do a comparison, as I mentioned with Alan's explanation.

This is a rather clever method; I would never have thought
of rebinding y_n.raw_input in this way.

The only weakness that I see with this approach is if one
wants to test a function like y_n() knowing that it ask
the user for some input ... but not knowing what function
it uses to do so.  This can be the case if one has a compiled
library with only the interface (what values can be passed
to the functions and how; what are the expected outputs) being
known.  Danny's approach would also not work here, whereas
Alan's would.

As it is, I will be working on a very small project with another
programmer, where I will be responsible to write some
unit tests.  I have access to the source code (so I know
which function is used to get input from the user), but I do not want
the other programmer to have to change the way his code
is written simply to accommodate my tests.
The other programmer's code will be in a single module; similarly,
I want to write a single test module myself - without having to
worry about keeping a separate data file.
Therefore, I will go with Michael's approach.

Thanks to everyone, and I hope that I didn't misrepresent any
of the three solutions proposed.  It's been fun!

André
_______________________________________________
Tutor maillist  -  Tutor@python.org
http://mail.python.org/mailman/listinfo/tutor

Reply via email to