Python Unit Tests

M

melwin9

Hey,

How do i go about coming up/coding tests for this program below. Not sure how to even approach writing about 5 tests for it.

Initially I had this for the test but its not working well.

Test was name test_guess.py (Code Below)

Code:
from unittest import TestCase
import pexpect as pe

import guess as g
import random

random_number = random.randrange(1, 10)
correct = False

class GuessTest(TestCase):
def setUp(self):
self.intro = 'I have chosen a number from 1-10'
self.request = 'Guess a number: '
self.responseHigh = "That's too high."
self.responseLow  = "That's too low."
self.responseCorrect = "That's right!"
self.goodbye = 'Goodbye and thanks for playing!'

def test_main(self):
#cannot execute main now because it will
#require user input
from guess import main

def test_guessing_hi_low_4(self):
# Conversation assuming number is 4
child = pe.spawn('python guess.py')
child.expect(self.intro,timeout=5)
child.expect(self.request,timeout=5)
child.sendline('5')
child.expect(self.responseHigh,timeout=5)
child.sendline('3')
child.expect(self.responseLow,timeout=5)
child.sendline('4')
child.expect(self.responseCorrect,timeout=5)
child.expect(self.goodbye,timeout=5)


def __init__(self):
self.number = random.randint(0,10)
HIGH = 1
LOW = 2
OK = 3

def guess(self, number):
if number > self.number:
return self.HIGH
if number < self.number:
return self.LOW
return self.OK

def test_guesstoolow(self):
while not correct:
guess = input("What could it be?")
if guess == random_number:
print "Congrats You Got It"
correct = True
elif guess > random_number:
print "To High"
elif guess < random_number:
print "To Low"
else:
print "Try Again"

Python code for game below

Code:
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)

##def main():
#    print(intro)
#   user_input = raw_input(request)
#  print(responseHigh)
#  print(request)
#  user_input = raw_input(request)
#  print(responseLow)
#  user_input = raw_input(request)
#  print(responseCorrect)
#  print(goodbye)

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

Dave Angel


What version of Python are you using? I'll assume 2.7, but you really
should specify it (and any other meaningful environment dependencies) in
your original query, the beginning of your thread.

For 2.7, the docs for unittest are at:

http://docs.python.org/2/library/unittest.html
How do i go about coming up/coding tests for this program below. Not sure how to even approach writing about 5 tests for it.

Initially I had this for the test but its not working well.

Test was name test_guess.py (Code Below)

Code:
from unittest import TestCase
import pexpect as pe

import guess as g
import random

random_number = random.randrange(1, 10)
correct = False

class GuessTest(TestCase):
def setUp(self):
self.intro = 'I have chosen a number from 1-10'
self.request = 'Guess a number: '
self.responseHigh = "That's too high."
self.responseLow  = "That's too low."
self.responseCorrect = "That's right!"
self.goodbye = 'Goodbye and thanks for playing!'[/QUOTE]

These strings don't change from one test to another.  Thus they could be
set in the __init__() method, and indeed could just be globals.  Or even
(horrors) imported from the guess module.  So in your tests, you might
refer to  g.intro, rather than self.intro   Or you might decide that the
content of the strings is not what you're testing, but rather the flow
of the logic.

[QUOTE]
def test_main(self):
#cannot execute main now because it will
#require user input
from guess import main[/QUOTE]

No need for an import here.  Assuming you will be adding a call to
main(), you can just call it as  g.main(), since you imported g already
as a global.
[QUOTE]
def test_guessing_hi_low_4(self):
# Conversation assuming number is 4
child = pe.spawn('python guess.py')
child.expect(self.intro,timeout=5)
child.expect(self.request,timeout=5)
child.sendline('5')
child.expect(self.responseHigh,timeout=5)
child.sendline('3')
child.expect(self.responseLow,timeout=5)
child.sendline('4')
child.expect(self.responseCorrect,timeout=5)
child.expect(self.goodbye,timeout=5)


def __init__(self):
self.number = random.randint(0,10)
HIGH = 1
LOW = 2
OK = 3[/QUOTE]

Those 3 statements do nothing useful.  They set 3 local variables which
are then immediately forgotten.  Presumably you meant:

self.HIGH = 1
self.LOW = 2
self.OK = 3
[QUOTE]
def guess(self, number):
if number > self.number:
return self.HIGH
if number < self.number:
return self.LOW
return self.OK

def test_guesstoolow(self):[/QUOTE]

Nothing in this method tests any of the actual program
[QUOTE]
while not correct:
guess = input("What could it be?")
if guess == random_number:
print "Congrats You Got It"
correct = True
elif guess > random_number:
print "To High"
elif guess < random_number:
print "To Low"
else:
print "Try Again"

Python code for game below

Code:
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)

##def main():
#    print(intro)
#   user_input = raw_input(request)
#  print(responseHigh)
#  print(request)
#  user_input = raw_input(request)
#  print(responseLow)
#  user_input = raw_input(request)
#  print(responseCorrect)
#  print(goodbye)

if __name__ == '__main__':
main()

According to https://en.wikipedia.org/wiki/Unit_testing:
"...one can view a unit as the smallest testable part of an application"

This is frequently a function, but certainly nothing smaller. So your
program is too monolithic to write multiple unit tests for. You need to
refactor your program to make it more testable.

1) have a way to eliminate the calls to random.randint().
2) separate out the calculation from the input and output
There has to be a better way to do this than pexpect.
One way (probably still not the best) is to replace the print
statement and the input calls with function calls to something that the
test overrides.

Now the question of what could you possibly do in 6 tests.

Once the code has been refactored into several functions, one of those
functions probably picks the original random number. So you can test
that function by repeatedly calling it and making sure that every
return value is a number within the specified range.

A second test might be to make sure that you get exactly 5 wrong
guesses.

A third test might be to make sure that several pre-selected sequences
of guesses elicit the right responses. So the test might be driven from
a tuple consisting of the number, then a series of tuples where each of
the subtuples is a guess and an expected response.

A fourth test might be to stuff invalid data into the simulated input,
and make sure the program behaves as expected. Probably throwing an
exception like it does now isn't the expected behavior.

I've run out of ideas. But several of these could be considered
multiple tests anyway.
 
T

Terry Reedy

On 9/28/2013 12:52 AM, (e-mail address removed) 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)

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().
 
M

melwin9

Hi Terry & Dave,

Thanks for the suggestions. I am running Python 2.7 and when I tried the code above it said def main(target) <- invalid syntax. I was told to use pexpect by my professor which is why I started to use that for the tests. As for the test suggestions, I will try to come up wit those tests with my current code but again idk how to do it in pexpect which was asked of me.

On 9/28/2013 12:52 AM, mel 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!'



def main():
guessesTaken = 0
number = random.randint(1, 10)
while guessesTaken < 5:

guess = input()
guess = int(guess)

guessesTaken = guessesTaken + 1

if guess < number:


if guess > number:


if guess == number:


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__':



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)



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().
 
S

Steven D'Aprano

Hi Terry & Dave,

Thanks for the suggestions. I am running Python 2.7 and when I tried the
code above it said def main(target) <- invalid syntax.


Did you fix the invalid syntax? Until you fix it, you're blocked.

As a programmer, it is completely normal to occasionally make a typo or
other trivial mistake that leads to invalid syntax. It's not worth
mentioning. You just fix it and move on.

It's a bit like being a cook. The recipe says, "add one tablespoon of
sugar", and you say "well I tried, but the spoon bangs on the lid of the
jar." The solution is to take the lid off first. The recipe doesn't
mention this, just like the recipe doesn't say "take the spoon out of the
cutlery drawer". You just do it.

If you don't understand the syntax error you got, firstly try to compare
the bad syntax

def main(target)
blah blah blah


with working functions that don't give syntax errors

def function(arg):
blah blah blah


Can you spot the difference? Hint: the syntax error will show an arrow ^
pointing at the spot where it notices a problem. Can you fix the problem?
If so, great, move on! If not, ask for help, but remember to COPY AND
PASTE the entire traceback, starting with the line

Traceback (most recent call last)

all the way to the end of the error message.
 
M

melwin9

Hi Dave,

Yeah I found the silly mistake lol thanks for that. making progress.


Guess a number: 5
That's too high.
Guess a number: 4
That's too high.
Guess a number: 3
Traceback (most recent call last):
File "guess.py", line 34, in <module>
main(random.randint(1, 10))
File "guess.py", line 29, in main
print(responseCorrect + '! You guessed my number in ' + tries + 'guesses!')
TypeError: cannot concatenate 'str' and 'int' objects

Code:
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 correct!"
responseWrong = "Wrong, The correct number is "
guessTaken = "Your number of guesses were "
goodbye = ' Goodbye and thanks for playing!'

allowed = 5

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

def main(target):
guess, tries = getguess(target, allowed)
if guess == target:
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))
 
T

Terry Reedy

Hi Dave,

Yeah I found the silly mistake lol thanks for that. making progress.


Guess a number: 5
That's too high.
Guess a number: 4
That's too high.
Guess a number: 3
Traceback (most recent call last):
File "guess.py", line 34, in <module>
main(random.randint(1, 10))
File "guess.py", line 29, in main
print(responseCorrect + '! You guessed my number in ' + tries + 'guesses!')
TypeError: cannot concatenate 'str' and 'int' objects

This is what 'untested' means ;-)
Code:
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 correct!"
responseWrong = "Wrong, The correct number is "
guessTaken = "Your number of guesses were "
goodbye = ' Goodbye and thanks for playing!'

allowed = 5

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

def main(target):
guess, tries = getguess(target, allowed)
if guess == target:
print(responseCorrect + '! You guessed my number in ' + tries + 'guesses!')[/QUOTE]

Either change 'tries' to 'str(tries)' or replace the statement with

print(responseCorrect, 'You guessed my number in', tries,
'guesses!')

There is no need to create a single string before the print.
[QUOTE]
else:
print(goodbye + ' The number I was thinking of was ' + number) 
ditto


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

melwin9

Lol, im starting to get the hang out of, onto the next hurdle, i looked up the error and it says the data is none?

Traceback (most recent call last):
File "guess.py", line 34, in <module>
main(random.randint(1, 10))
File "guess.py", line 27, in main
guess, tries = getguess(target, allowed)
TypeError: 'NoneType' object is not iterable


Hey,



How do i go about coming up/coding tests for this program below. Not sure how to even approach writing about 5 tests for it.



Initially I had this for the test but its not working well.



Test was name test_guess.py (Code Below)



Code:
from unittest import TestCase

import pexpect as pe



import guess as g

import random



random_number = random.randrange(1, 10)

correct = False



class GuessTest(TestCase):

def setUp(self):

self.intro = 'I have chosen a number from 1-10'

self.request = 'Guess a number: '

self.responseHigh = "That's too high."

self.responseLow  = "That's too low."

self.responseCorrect = "That's right!"

self.goodbye = 'Goodbye and thanks for playing!'



def test_main(self):

#cannot execute main now because it will

#require user input

from guess import main



def test_guessing_hi_low_4(self):

# Conversation assuming number is 4

child = pe.spawn('python guess.py')

child.expect(self.intro,timeout=5)

child.expect(self.request,timeout=5)

child.sendline('5')

child.expect(self.responseHigh,timeout=5)

child.sendline('3')

child.expect(self.responseLow,timeout=5)

child.sendline('4')

child.expect(self.responseCorrect,timeout=5)

child.expect(self.goodbye,timeout=5)





def __init__(self):

self.number = random.randint(0,10)

HIGH = 1

LOW = 2

OK = 3



def guess(self, number):

if number > self.number:

return self.HIGH

if number < self.number:

return self.LOW

return self.OK



def test_guesstoolow(self):

while not correct:

guess = input("What could it be?")

if guess == random_number:

print "Congrats You Got It"

correct = True

elif guess > random_number:

print "To High"

elif guess < random_number:

print "To Low"

else:

print "Try Again"



Python code for game below



Code:
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)



##def main():

#    print(intro)

#   user_input = raw_input(request)

#  print(responseHigh)

#  print(request)

#  user_input = raw_input(request)

#  print(responseLow)

#  user_input = raw_input(request)

#  print(responseCorrect)

#  print(goodbye)



if __name__ == '__main__':

main()
 
M

MRAB

Lol, im starting to get the hang out of, onto the next hurdle, i looked up the error and it says the data is none?

Traceback (most recent call last):
File "guess.py", line 34, in <module>
main(random.randint(1, 10))
File "guess.py", line 27, in main
guess, tries = getguess(target, allowed)
TypeError: 'NoneType' object is not iterable
[snip]
Look at 'getguess'.

If the user guess correctly within the number of allowed attempts, the
function returns (guess, tries), but what if the user runs out of
attempts?
 
D

Dave Angel

Lol, im starting to get the hang out of, onto the next hurdle, i looked up the error and it says the data is none?

Traceback (most recent call last):
File "guess.py", line 34, in <module>
main(random.randint(1, 10))
File "guess.py", line 27, in main
guess, tries = getguess(target, allowed)
TypeError: 'NoneType' object is not iterable

Please don't top-post. Further, if you insist on using a buggy app like
googlegroups, at least remove all the stupid double-spacing.

Do you know how to interpret this error message? The line that fails is
guess, tries = getguess(target, allowed)


So can you tell what the None data is? You're doing a tuple-unpack on
the left side of the equals, so the right side needs to be a tuple,
list, or equivalent. In specific, an iterable.

Now you reread the error, and realize that the getguess() function is
returning None.

If I were a novice, I'd start by splitting up the line with the error:

temp = getguess(target, allowed)
guess, tries = temp

Then when the error complains about the second line, I'd add a print
statement:

temp = getguess(target, allowed)
print(repr(temp))
guess, tries = temp

Lacking the source code, I'm going to guess that some path through your
code is missing a return expression.

For example, you might have

def getguess(a, b):
if a < b:
return a, a*b

So it'll return a tuple of two items in the if body, but without an else
clause, it simply falls off the end, and returns None. (All functions
return something, so if you don't provide a value, None is used)
 
T

Terry Reedy

Lol, im starting to get the hang out of, onto the next hurdle, i looked up the error and it says the data is none?

Traceback (most recent call last):
File "guess.py", line 34, in <module>
main(random.randint(1, 10))
File "guess.py", line 27, in main
guess, tries = getguess(target, allowed)
TypeError: 'NoneType' object is not iterable

If you have not figured it out yet, add a copy of the return within the
while loop,
return guess, tries
after the while loop also, so it does not return the default of None.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top