I'm currently testing my python code and have a question about raw_input. This is my function:
def answer():
ans = raw_input('enter yes or no')
if ans == 'yes':
print 'you entered yes'
return 'yes'
if ans == 'no':
some_value = raw_input('enter some value: ')
print 'you entered no'
return some_value
I'm testing the first if statement this way:
with mock.patch('__builtin__.raw_input', return_value= 'yes'):
assert answer() == 'yes'
But how do I check the no statement ? How do I make mock inside a mock ?
Using side_effect:
with mock.patch('__builtin__.raw_input', side_effect=['yes']):
assert answer() == 'yes'
with mock.patch('__builtin__.raw_input', side_effect=['no', 'maybe']):
assert answer() == 'maybe'
According to mock documentation:
If side_effect is an iterable then each call to the mock will return the next value from the iterable.
The side_effect can also be any iterable object. Repeated calls to the mock will return values from the iterable (until the iterable is exhausted and a StopIteration is raised):
>>>
>>> m = MagicMock(side_effect=[1, 2, 3])
>>> m()
1
>>> m()
2
>>> m()
3
>>> m()
Traceback (most recent call last):
...
StopIteration
Using side effect should do the trick, I find the following quite clear and avoid multiple with-block:
def my_side_effect(*args): # each argument will be the return_value of one call
for el in args:
yield el # we use a generator to return different value each time
with mock.patch('__builtin__.raw_input') as mocked: # here the mocked object is accessible in the block
mocked.side_effect = my_side_effect('yes') # here one call that return 'yes'
assert answer() == 'yes'
mocked.side_effect = my_side_effect('no', 'maybe') # two calls, the first return 'no', the second 'maybe'
assert answer() == 'maybe'
If you just mock raw_input to return 'no', it will return 'no' both times, meaning you can assert that the function returns 'no':
with mock.patch('__builtin__.raw_input', return_value='yes'):
assert answer() == 'yes'
with mock.patch('__builtin__.raw_input', return_value='no'):
assert answer() == 'no'
If you want to test what happens if, say, the first input is 'no' and the second one is 'maybe', you have to mock it with a function that returns different things the first time it's called and the second time it's called, and then you can assert that it returns 'maybe'. Something like this (not tested because I don't have mock installed here… but it should give you the idea):
def fake_raw_input(once=[False]):
if not once[0]:
once[0] = True
return 'no'
return 'maybe'
with mock.patch('__builtin__.raw_input', return_value='yes'):
assert answer() == 'yes'
with mock.patch('__builtin__.raw_input', new_callable=fake_raw_input):
assert answer() == 'maybe'
Related
I'm learning how to use Python. I have a function with a conditional inside of it, if an invalid input is provided, it should restart the loop until a valid input is provided.
Unfortunately, this "restarting" behavior is causing an infinite loop within my tests (it circularly provides the wrong input). How can I pause, or break, or limit the output to one instance so I can test the returned string?
function:
def confirm_user_choice(choice: str):
while True:
user_response = input(f"\nYou chose '{choice}', is this correct? y/n ")
if user_response == "y":
return True
elif user_response == "n":
return False
else:
print("\nSelect either 'y' (yes) or 'n' (no)")
test:
import unittest
from unittest import mock
from src.utils.utils import addValues, confirm_user_choice
class TestConfirmUserChoice(unittest.TestCase):
def test_yes(self):
with mock.patch("builtins.input", return_value="y"):
result = confirm_user_choice("y")
self.assertEqual(result, True)
def test_no(self):
with mock.patch("builtins.input", return_value="n"):
result = confirm_user_choice("n")
self.assertEqual(result, False)
def test_invalid_input(self):
with mock.patch("builtins.input", return_value="apple"): <-- triggers func else case
result = confirm_user_choice("apple")
self.assertEqual(result, False)
You have a partial function: on a proper input, it will return a Boolean value, but it may not return at all, and you can't test that an infinite loop is indeed infinite.
To make it more testable, allow the function to take an optional iterable value that defaults to sys.stdin, allowing you to control what the function reads (and how long it will attempt to do so.)
def confirm_user_choice(choice: str, responses: Optional[Iterable[str]] = None):
if responses is None:
# An infinite stream of calls to input()
responses = iter(lambda: input(f"\nYou chose '{choice}', is this correct? y/n "), None)
for user_response in responses:
if user_response == "y":
return True
elif user_response == "n":
return False
else:
print("\nSelect either 'y' (yes) or 'n' (no)")
else:
# Note: cannot be raised from the default value of responses
raise ValueError("Unexpected end of responses")
Now your test can simply pass canned lists of responses, and either catch the expected ValueError, or look at the returned Boolean value.
import unittest
from src.utils.utils import addValues, confirm_user_choice
class TestConfirmUserChoice(unittest.TestCase):
def test_yes(self):
result = confirm_user_choice("y", ["y"])
self.assertTrue(result)
def test_eventual_yes(self):
result = confirm_user_choice("y", ["apple", "pear", "y"])
self.assertTrue(result)
def test_no(self):
result = confirm_user_choice("y", ["n"])
self.assertFalse(result)
def test_no_valid_input(self):
with self.assertRaises(ValueError):
result = confirm_user_choice(["apple"])
continue does nothing in your code
continue alows you to ignore a part of the code for some instance of the loop.
For example :
for i in range(2):
if i < 1:
continue
print(i)
Output :
1
For what you want to do, don't forget while is suppose to end when a condition is meet. Hence bypassing the condition using while True: and then using a if to exit your loop is a bit counter productive.
Just use the while condition :
user_response = ""
while user_response not in ["y", "n"]:
user_response = input("y/n ? ")
print(user_response)
Happy programming
I'm new to Python myself but in my understanding, unit tests investigate how function handle different inputs based on the function's return value or exceptions raised (if any).
Your function only exits when the user inputs either "y" or "n" or when an error is raised (for instance, if the user provides Crtl-Z). Your while loop does not break when a user inputs 'apple.' There is no return value for pytest (or the like) to inspect.
If you really want to test this, you'd have to rewrite your function so that's a little more modular. It would have to feature at least three different return values, including one that implies that the input was invalid.
I am writing a program that has multiple functions to execute, and the user selects which one runs by inputting a number. I also want the user to be able to let the user cancel the request by typing "cancel".
Right now this is my code:
func = input("Requested Operation: ")
if func == 'Cancel' or func == 'cancel':
break
elif func == '' or func == ' ' or func == '0':
func = 0
elif type(int(func)) is int:
func = int(func)
else:
fail = True
Context: Function 0 displays a list of the available items to choose from, so I want whitespace or 0 to work as displaying the project list. If the user types "Cancel" or "cancel" it will end the program.
The problem I am having is line 6 (the 2nd elif). My goal is to set the fail variable to True if the user inputs a string that isn't a cancel command, so the code breaks right there and starts over. The problem is, how do I preemptively check if a string can be converted to an integer in the first place? My current iteration returns the error invalid literal for int() with base 10: 'asdg' (asdg being the random nonsense that should make fail = True).
Also, I understand this method is probably super inefficient. Essentially, I want the conditional to be "if func is cancel, break. If func is whitespace or '0', then it equals 0. If func is some non-0 integer, convert the string to an integer and continue. Otherwise, set fail to True and break."
My knowledge of python is minimal so I would very much appreciate a full explanation or link to documentation so I can learn as much as possible.
Thanks in advance :)
Edit: This is the entire module
import projects.dice_app as dice_app
import projects.text_to_math as text_to_math
def main():
f = open("readme_files/index.txt")
p = open("readme_files/projects.txt")
print(f.read())
func = 0
while True:
fail = False
func = input("Requested Operation: ")
if func == 'Cancel' or func == 'cancel':
break
elif func == '' or func == ' ' or func == '0':
func = 0
elif type(int(func)) is int:
func = int(func)
else:
fail = True
break
if func == 0:
p = open("readme_files/projects.txt")
print(p.read())
elif func == 1:
dice_app.dice_func()
elif func == 2:
text_to_math.ttm_func()
else:
print("Invalid operation. Please try again.")
if __name__ == "__main__":
fail = False
main()
while fail == True:
main()
elif func.isnumeric():
func = int(func)
try :
func = int(func)
except ValueError:
print('not a number')
This should work
Is it possible to assign a variable to an return result of an function ?
First I want to acquire the website from email like for e.g xxxxx#hotmail.com will return only hotmail.com. Then if that website is equal to 'hotmail.com' return 'Yes' if not equal return 'No'.
def mail(var):
x = return var.split('#')[1]
if x == 'hotmail.com':
return 'Yes'
else:
return 'No'
I know it's not the right code but I hope You get the idea. Thanks for your help !
I think what you want is this:
def mail(var):
return 'Yes' if var.split('#')[1] == 'hotmail.com' else 'No'
x = mail('foo#hotmail.com')
print(x)
Note:
If var does not contain '#' this will fail with IndexError
You have to remove your first return, beacause then your function immediately exits, then it works:
def mail(var):
x = var.split('#')[1]
if x == 'hotmail.com':
return 'Yes'
else:
return 'No'
print(mail('user#hotmail.com'))
print(mail('user#gmail.com'))
Output:
Yes
No
I suggest to validate the email in parameter to avoid exception by checking if it contains '#' in argument and checking for dot ('.') within index of '#' to the last char, if it's invalid then return 'is not valid email'. If it's a valid email then assign the value of variable x by indicing from next index of '#' (same result as split). And the last, check if x is fit to the condition that you want (in this example: 'hotmail.com')
def mail(var):
if '#' in var and '.' in var[var.index('#'):]:
x = var[var.index('#')+1:]
print(x)
return 'Yes' if x == 'hotmail.com' else 'No'
else:
return f'{var} is not valid email'
print(mail('xxxxx#gmailjp'))
print(mail('xxxxxgmail.com'))
print(mail('xxxxxgmailnl'))
print(mail('xxxxx#gmail.us'))
print(mail('xxxxx#hotmail.com'))
# xxxxx#gmailjp is not valid email
# xxxxxgmail.com is not valid email
# xxxxxgmailnl is not valid email
# gmail.us
# No
# hotmail.com
# Yes
But I prefer this:
def mail(var):
return 'Yes' if 'hotmail.com' in var else 'No'
print(mail('xxxxx#gmail.us'))
print(mail('xxxxx#hotmail.com'))
# No
# Yes
def reverse(string):
return string[::-1]
def isPalindrome(string):
temp=reverse(string)
if temp==string:
return True
else:
return False
string='tanmay' # input('enter a word')
ans=isPalindrome(string)
if ans==1:
print' Yes palindrome'
else:
print' no its not a palindrome'
if I ask for an input from the user the error what I got was Traceback (most recent call last):
File "C:/Python27/prac06-2.py", line 10, in <module>
string=input('enter a word')
File "<string>", line 1, in <module>
NameError: name 'tanmay' is not defined
but when I enter a string by myself the program is executed successfully
in python 2.7 input() evaluates the given input, you should use raw_input() to read in the data as a string. On another note, temp==string evaluates to a boolean so you do not need to put it in an if statement you can simply return temp==string
def reverse(string):
return string[::-1]
def isPalindrome(string):
temp=reverse(string)
return temp==string
string=raw_input('enter a word')
if isPalindrome(string):
print(' Yes palindrome')
else:
print(' no its not a palindrome')
You can simplify isPalindrome() further by removing reverse() to:
def isPalindrome(string):
return string == string[::-1]
You are returning a boolean True or False and you are trying to compare the result with a value 1. Here is how you should invoke it.
ans = isPalindrome(string)
if ans: # you can also do (if ans == True)
print 'Yes, it is a palindrome'
else:
print 'No, it is not a palindrome'
On Code Academy there is this course where in the example they show
def speak(message):
return message
if happy():
speak("I'm happy!")
elif sad():
speak("I'm sad.")
else:
speak("I don't know what I'm feeling.")
The above example will NOT be related to the rest of the code I show. That was just an example for the if statement. Now I was under the impression that when ever writing an if statement it had to end in an ():like the above example.
However when doing the assignments this does not work:
def shut_down(s):
if s == "yes"():
return "Shutting down"
elif s == "no"():
return "Shutdown aborted"
else:
return "Sorry"
However this works:
def shut_down(s):
if s == "yes":
return "Shutting down"
elif s == "no":
return "Shutdown aborted"
else:
return "Sorry"
My question is how come the () is not needed next to the "yes" and "no" but :is still needed. I thought whenever writing an if statement it will automatically have to end with ():. In that very first example, that's how it is shown. Do you understand my confusion.
In the example given, happy() and sad() are functions, and as such require parentheses. The if itself does not need parentheses at the end (and it shouldn't have them)
No, if has nothing to do with ()
happy is a function. happy() is a call to that function. So, if happy(): tests if the happy function returns true when called.
In other words, if happy(): speak("I'm happy!") is equivalent to
result_of_happy = happy()
if result_of_happy:
speak("I'm happy!")
As has been mentioned happy() / sad() are functions so they require (). In example two of your question you are comparing your value to the string "yes" because it is a string it does not require ().
Within an if statement you can use parentheses to make the code more readable and ensure certain operations are evaluated before others.
if (1+1)*2 == 4:
print 'here'
else:
print 'there'
Differs from:
if 1+1*2 == 4:
print 'here'
else:
print 'there'
Because string objects are not callable so what are you expecting then:
Then use lambda not that efficient tho:
def shut_down(s):
if (lambda: s == "yes")():
return "Shutting down"
elif (lambda: s == "no")():
return "Shutdown aborted"
else:
return "Sorry"