How can I test a loop with multiple input calls? - python-3.x

I'm trying to test a fuction that dependets a of multiple user inputs to return some value.
I've already looked for multiples unswers here but none was able to resolve my problem. I saw things with parametrize, mock and monkey patch but none helped. I think a lot is because I don't clearly understood the concepts behind what was being done and I couldn't adapt to my problem. I saw suggestion of using external file for this but I don't wont to depend on that. I'm trying with pytest and python 3.7.3
The function that I want to test is something like this
def function():
usr_input = input('please enter a number: ')
while True:
if int(usr_input) < 5:
usr_input = input('please, enter a value less then 5: ')
else:
break
return usr_input
I want to know how can I pass two input values to test the function when the inserted value is not valid. Example: Send value 6 and 2, make an assert expecting value 2 and pass the test. My others tests look like this:
def test_input(monkeypatch):
monkeypatch.setattr('builtins.input', lambda x: 6)
test = function()
assert test == 2
but, for this case, they loop. It's possible to do this only with parametrize or other simple code?
EDIT
I added a int() in my "if", as wim pointed in the accepted answer, just to prevent any confusion for future readers. I'm assuming the cast is possible.

Two problems here, you need to convert the input into a number otherwise the comparison will fail, comparing a string with a number: usr_input < 5. Note that the real input will never return a number, only a string.
Once you've cleared that up, you can monkeypatch input with a callable that can return different values when called:
def fake_input(the_prompt):
prompt_to_return_val = {
'please enter a number: ': '6',
'please, enter a value less then 5: ': '2',
}
val = prompt_to_return_val[the_prompt]
return val
def test_input(monkeypatch):
monkeypatch.setattr('builtins.input', fake_input)
test = function()
assert test == 2
If you install the plugin pytest-mock, you can do this more easily with the mock API:
def test_input(mocker):
mocker.patch('builtins.input', side_effect=["6", "2"])
test = function()
assert test == 2

Related

I am new to python coding and i do not understand how to use a variable from another method

Here is what i have tried:
I do not understand how can i use a variable from another method of same class. Also please explain how can i use a variable from a method in different class. I tried searching but could not find a solution. So what i did to pass the test cases is to copy code from calculate_percentage and paste it in find_grade method. It worked but i think this is the worst method. So please tell a possible solution.
Thanks
#!/bin/python3
#Enter your code here. Read input from STDIN. Print output to STDOUT
class Student:
def __init__(self,roll,name,marks_list):
self.roll=roll
self.name=name
self.marks_list=marks_list
def calculate_percentage(self):
length=len(self.marks_list)
sum=0
for i in self.marks_list:
sum+=i
percent=sum/length
return int(percent)
def find_grade(self,percent):
if percent>=80:
return 'A'
elif percent>=60 and percent<80:
return 'B'
elif percent>=40 and percent<60:
return 'C'
elif percent<40:
return 'F'
if __name__ == '__main__':
roll=int(input())
name=input()
count=int(input())
marks=[]
for i in range(count):
marks.append(int(input()))
s=Student(roll,name,marks)
print(s.calculate_percentage())
print(s.find_grade())
i am getting the error:
print(s.find_grade())
TypeError: find_grade() missing 1 required positional argument: 'percent'
Thanks for every one who answered but i found the answer:
i just need to put self.percent
or percent=self.calculate_percentage() to call the method and use the variables
The assumption would be that the marks are x/100 scale while otherwise your percentage will be incorrect.
As said above, you need to pass the variable percent back to the function since it is not known in the class, it is only returned.
print(s.find_grade(s.calculate_percentage()))
or if the percentage is a class variable you can rewrite it into the class like this:
from statistics import mean
class Student2:
def __init__(self,roll,name,marks_list):
self.roll=roll
self.name=name
self.marks_list=marks_list
def calculate_percentage(self):
self.percent=mean(marks)
return int(self.percent)
def find_grade(self):
if self.percent>=80:
return 'A'
elif self.percent>=60 and self.percent<80:
return 'B'
elif self.percent>=40 and self.percent<60:
return 'C'
elif self.percent<40:
return 'F'
percent = int(0)
# test variables
vRoll = 2
vName = 'student'
vCount= 2
vMarks= [100, 75]
# main
if __name__ == '__main__':
roll=vRoll
name=vName
count=vCount
marks=vMarks
s2=Student2(roll,name,marks)
print(s2.calculate_percentage()) # 87
print(s2.find_grade()) # A

Unittesting for correct 'continue' behaviour

I have a function that asks a user for confirmation via a prompt. It accepts y or n as answers, otherwise it asks again.
Now, I want to write a unittest for this function. I can test the correct behaviour for y or n just fine, but how do I test that my function correctly rejects inacceptable input?
Here's the code for foo.py:
def get_input(text):
"""gets console input and returns it; needed for mocking during unittest
"""
return input(text)
def confirm(message='Confirm?', default=False):
"""prompts for yes or no response from the user. Returns True for yes and
False for no.
'default' should be set to the default value assumed by the caller when
user simply types ENTER, and is marked in the prompt with square brackets.
"""
if default:
message = '%s [y]|n: ' % (message) # default answer = yes
else:
message = '%s y|[n]: ' % (message) # default answer = no
while True:
answer = get_input(message).lower()
if not answer:
return default
if answer not in ['y', 'n']:
print('Please enter y or n!')
continue
if answer == "y":
return True
if answer == 'n':
return False
answer = confirm()
print(answer)
And here is my Test class:
import unittest
import foo
class TestFoo_confirm(unittest.TestCase):
"""testing confirm function
"""
#unittest.mock.patch('foo.get_input', return_value='y')
def test_answer_yes(self, _):
self.assertEqual(foo.confirm(), True) # confirmed if 'y' was entered
So, how do I write a similar test for an input-value like '1' (or how do I need to adjust my confirm() function to make it testeable)?
Currently, if I call foo.confirm() from the unittest file, it just gets stuck in an infinite loop and it doesn't return anything. (I understand why this is happening, just not how to circumvent it.)
Any ideas?
You could try this:
import unittest, unittest.mock
import foo
class TestFoo_confirm(unittest.TestCase):
"""testing confirm function
"""
#unittest.mock.patch('foo.get_input', return_value='y')
def test_answer_yes(self, _):
self.assertEqual(foo.confirm(), True) # confirmed if 'y' was entered
#unittest.mock.patch('builtins.print')
#unittest.mock.patch('foo.get_input', side_effect=['1','yn','yes','y']) # this will make the mock return '1', 'yn' and so on in sequence
def test_invalid_answer(self, mock_input, mock_print):
self.assertEqual(foo.confirm(), True) # it should eventually return True
self.assertEqual(mock_input.call_count, 4) # input should be called four times
mock_print.assert_called_with('Please enter y or n!')
In the second test case, we imitate a user who enters three invalid inputs, and, after being prompted again, finally enters 'y'. So we patch foo.get_input in such a way that it returns 1 the first time it's called, then yn, then yes and finally y. The first three examples should cause the confirm function to prompt the user again. I also patched the print function, so that the 'Please enter y or n!' message wouldn't show up when testing. This isn't necessary.
Then we assert that our mock input was called four times, meaning that the first three times, the confirm function reprompted.
Finally we assert that the print function was called (at least once) with 'Please enter y or n!'.
This does not test if the correct number of print statements were made or if they were in correct order, but I suspect this would be possible too

How efficiently unit test a function with multiple input calls?

I have a unit test that works as intended, however I feel that this is not the best way to test multiple inputs with pytest. It definitely violates the DRY principle. I thinking there's a better way to go about this but I can't figure out what. I'm also not sure what to actually do with the mock. It's not used but it has to be there (see 'mock_choice' parameter in the function in the code below).
I thought perhaps looping through the calls would work but that didn't work as intended. I really couldn't figure out another way besides using side_effects and calling four times to test to make sure I get return value as I intended.
Function To Test
def export_options():
while True:
try:
choice = int(input("\nPlease make a selection"))
if choice in ([option for option in range(1, 5)]):
return choice # This what I'm testing
else:
print("\nNot a valid selection\n")
except ValueError as err:
print("Please enter an integer")
Test Function
#mock.patch('realestate.app.user_inputs.input', side_effect=[1, 2, 3, 4])
def test_export_options_valid_choice(mock_choice): # mock_choice needs to be here but isn't used!
export_option = user_inputs.export_options()
assert export_option == 1
export_option = user_inputs.export_options()
assert export_option == 2
export_option = user_inputs.export_options()
assert export_option == 3
export_option = user_inputs.export_options()
assert export_option == 4
The test works. It passes as the function returns all values between 1 and 4. However, because the code is very repetitive, I would like to know if there's a better way to test multiple input calls as I would like to apply the same to future tests.
You can use a for loop to avoid repeating code.
#mock.patch('realestate.app.user_inputs.input')
def test_export_options_valid_choice(self, mock_choice):
for response in [1, 2, 3, 4]:
with self.subTest(response=response)
mock_choice.return_value = response
export_option = user_inputs.export_options()
self.assertEqual(export_option, response)
# Not in original version, but other tests might need it.
mock_choice.assert_called_once_with("\nPlease make a selection")
mock_choice.reset_mock()
The subtest context manager will tell you which input failed.
Is the only way to do something like this is with sub-test using the unittest module? I know pytest doesn't support subtests, but I was hoping there was a similar type of hack.
You can certainly loop without using subtests, but you may have difficulty telling which input failed. More generally, instead of a loop, you can call a common helper function for each test.
For pytest in particular, you can use the #pytest.mark.parametrize decorator to automate this.
#pytest.mark.parametrize('response', [1, 2, 3, 4])
#mock.patch('realestate.app.user_inputs.input')
def test_export_options_valid_choice(mock_choice, response):
mock_choice.return_value = response
export_option = user_inputs.export_options()
assert export_option == response

It asks to give the values but instead of giving an answer. It is giving me None

Here I created a module.
class Employee:
def __init__(self):
self.name = input("Enter your name: ")
self.account_number = int(input("Enter your account number: "))
def withdraw(self): # it receives values from for
if withdraw1 > current_balance:
print ("You have entered a wrong number: ")
else:
print ("The current balance is: ", current_balance - withdraw1)
import TASK2 # I am importing the module I created
c = TASK2.Employee()
def for(self):
c.withdraw1 = int(input("enter number: "))
c.current_balance = int(input("Enter the current balance: "))
d = method(c.withdraw) # here I am trying to pass the values to withdraw
print (d)
The problem I get is that although it asks for the values instead of giving me an answer it gives me None.
Here's my take on your code.
# TASK2.py
class Employee:
def __init__(self):
self.name = input("Enter your name: ")
self.account_number = int(input("Enter your account number: "))
# make sure you initialise your member variables!
self.withdraw_val = 0 # withdraw1 is ambiguous, so I use withdraw_val instead
self.current_balance = 0
# receives values from for ### no it doesn't, right now, it GIVES values TO your "for" function
def withdraw(self):
if self.withdraw_val > self.current_balance: # remember to use "self." to
# access members within the class
print ("You have entered a wrong number: ")
else:
# again, remember "self."
print ("The current balance is: ", self.current_balance - self.withdraw_val)
# TASK2sub.py
import TASK2
c = TASK2.Employee()
def for_employee(employee): # (1) don't use "self" outside a class
# it's contextually unconventional
# (2) "for" is a keyword in Python, don't use it for naming
# variables/functions, it'll mess things up
employee.withdraw_val = int(input("Enter value to withdraw: "))
employee.current_balance = int(input("Enter the current balance: "))
return employee.withdraw_val # not entirely sure what you want to return
# but you should definitely return something
# if you're going to assign it to some variable
d = for_employee(c.withdraw()) # "for_employee" function needs a return statement
# ".withdraw()" method should also require a return statement
print(d)
Note: I'll be referring to your original for function as for_employee from now on. Also note that I'm still hazy about what you're trying to accomplish and that there is most probably a more suitable name for it.
Since your original for_employee function didn't return anything, it returns None by default. (This explains the output you saw.)
I think you're misunderstanding how functions work in general. For example,
d = for_employee(c.withdraw())
print(d)
Your comment for the .withdraw() method is inaccurate.
"it receives values from for"
More accurately, c.withdraw() will first be computed, then whatever it returns is passed into the for_employee function as a parameter. Instead of "receiving values from", the withdraw method "gives values to" the for_employee function.
Something more reasonable would be
c.withdraw() # on a line by itself, since it doesn't return anything
d = for_employee(c) # pass the entire object, since you'll be using self.withdraw_val and whatnot
print(d)
Another issue is with conventional naming. This is what I get from the IDLE (with Python 3.7) when defining a function named for
>>> def for(a): return a
SyntaxError: invalid syntax
Again, for is a keyword in Python, don't use it for naming your variables, functions, or classes.
With self, it's less severe (but I could see that it's confusing you). self is more of a convention used in class methods. But for_employee isn't a class method. So conventionally speaking, the parameter shouldn't be named self.
(I find the code spaghetti-ish, it might benefit if you refactor the code by moving the for_employee method into the class itself. Then it would completely make sense to use self.)

Python Recrusive Statement Error not Defined

I am trying to test each input then return that the number is cleared then do the math. For Example is a user inputs N instead of a number I want it to output that its not a number whereas if the user inputs 1 then I want it to move to the next function asking for a power then do the same thing and if that passes then goes to the final section which output the answer to the problem.
The program passes both the errors for the non number areas yet when it get to very last function it is telling me base nor power are defined.
Code is written in some Python2 and some Python3. All works fine though. I use python3 mostly.
[Test Picture/Error Msg][1]
# Below we are creating the recursive statement to do the math for us. We are calling Base and Power
# from the main function where the user Inputs the numbers.
def pow(base, power):
if power == 0:
return 1
if power == 1:
return base
else :
return base * pow(base, power - 1)
def determineBase():
while True:
try:
base = int(input ('Please Enter A Base: '))
except ValueError:
print("Please use whole numbers only. Not text nor decimals.")
continue
else:
return base
def determinePower():
while True:
try:
power = int(input ('Please Enter A Power: '))
except ValueError:
print("Please use whole numbers only. Not text nor decimals.")
continue
else:
return power
def main():
determineBase()
determinePower()
pow(base,power)
print("The answer to",base,"to the power of", power,"is", pow(base,power),".")
main()
def main():
determineBase()
determinePower()
pow(base,power)
Here, neither base nor power are defined. What you meant instead was to store the result from those function calls and pass those then:
def main():
base = determineBase()
power = determinePower()
pow(base, power)
The issue isn't inside the recursive function, it's inside your main function.
The problem is arising due to the fact that you are passing base as an argument to the pow() function without defining the variable base first (the same would subsequently be true for power).
In other words you need something along the lines of:
def main():
base = determineBase()
power = determinePower()
pow(base,power) #this line could probably be removed
print("The answer to",base,"to the power of", power,"is", pow(base,power),".")
As currently, you're not storing the values of these two functions.

Resources