Python Unit Tests

Terry Reedy tjreedy at udel.edu
Sat Sep 28 14:47:20 EDT 2013


On 9/28/2013 12:52 AM, melwin9 at gmail.com wrote:
[How can I test...]

> import random
>
> intro = 'I have chosen a number from 1-10'
> request = 'Guess a number: '
> responseHigh = "That's too high."
> responseLow  = "That's too low."
> responseCorrect = "That's right!"
> goodbye = 'Goodbye and thanks for playing!'
>
> print(intro)
>
> def main():
>      guessesTaken = 0
>      number = random.randint(1, 10)
>      while guessesTaken < 5:
>          print(request)
>          guess = input()
>          guess = int(guess)
>
>          guessesTaken = guessesTaken + 1
>
>          if guess < number:
>              print(responseLow)
>
>          if guess > number:
>              print(responseHigh)
>
>          if guess == number:
>              break
>
>      if guess == number:
>              guessesTaken = str(guessesTaken)
>              print(responseCorrect + '! You guessed my number in ' + guessesTaken + ' guesses!')
>
>      if guess != number:
>          number = str(number)
>          print(goodbye + ' The number I was thinking of was ' + number)

> if __name__ == '__main__':
>      main()

To expand on Dave's answer, I would refactor main() as below.

Note 1. I add 'allowed' so you can easily change the number or let the 
user decide the difficulty. One can always guess right in at most 4 tries.

Note 2. I am presuming that you are using 3.x.

allowed = 5

def getguess(target, allowed):
   tries = 0
   while tries < allowed:
     tries += 1
     guess = int(input(request))
     if guess < target:
       print(response_low)
     elif guess > target:
       print(response_high)
     else:
       return guess, tries

def main(target)
   guess, tries = getguess(target, allowed)
   if guess == number:
     print(responseCorrect + '! You guessed my number in ' + tries + ' 
guesses!')
   else:
     print(goodbye + ' The number I was thinking of was ' + number)

if __name__ == '__main__':
   main(random.randint(1, 10))

To test a function, you must be able to control inputs and access 
outputs. Unfortunately, this makes testing simple beginner programs that 
turn user input and random numbers input into screen output harder, in a 
way, than testing complicated math functions, such as one that 
approximates the derivative of a function as a point.

One way to control user input for getguess is to put something like the 
following (untested) in your test module. (Ignore print for the moment.)

class IntInput:
   "Replace input() that should return int as string."
   def __init__(self, ints, print=None)
     "Ints must be a sequence of ints"
     self.i = -1  # so 0 after first increment
     self.ints = ints
     self.print = print
   def input(prompt):
     "Maybe save prompt, return str(int)."
     if self.print:
       self.print(prompt)
     i = self.i + 1
     self.i = i
     return str(self.ints[i])

In test methods, inject a mock input into the tested module with 
something like
         g.input = IntInput((5,3,2,1)).input
where the sequence passed is appropriate for the target and the response 
you want. This will be sufficient to test most of the operation of getguess.

(I am aware that some would say that IntInput should be a context 
manager with an exit method that restores g.input. I do not think that 
this complication is needed for this post.)

To test the getguess prompts and main output, collect output lines with 
something like

class Screen:
   def __init__(self):
     self.lines = []
   def print(self, line):
     self.lines.append(line)

   screen = Screen()
   g.input = IntInput((5,3,2,1), screen.print).input
   # Test that screen.lines is as it should be.
   # Be careful that actual and expected both have
   # or both do not have terminal \n.

For testing main, in test_xxx methods,
     screen = Screen
     g.print = screen.print
     # test screen.lines in

Another approach is to replace sys.stdin/out as is done in 
test.support.capture_stdin/out, but the latter are considered internal 
functions not for general use, and this method seems more complicated.

random and random.randint could be mocked, but this in not needed for 
this program with the randint call moved out of main().

---
Terry Jan Reedy





More information about the Python-list mailing list