Python unit test failure: should raise value error but not - python

Here is my code trying to learn unit testing.
Create a Student class for the purpose of testing. The test invalid test case constantly failed.
FAIL: test_invalid (__main__.TestStudent)
----------------------------------------------------------------------
Traceback (most recent call last):
File "mystudent.py", line 46, in test_invalid
s1.get_grade()
AssertionError: ValueError not raised
above is from running result.
Could anyone help me to figure out why I have this failure while I think I have put the right 'Raise Error' code there....
import unittest
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def get_grade(self):
try:
if self.score >= 60 and self.score < 80:
return 'B'
if self.score >= 80 and self.score <= 100:
return 'A'
if self.score >= 0 and self.score <60:
return 'C'
if self.score < 0 or self.score > 100:
raise ValueError('Invalid score value')
except Exception as e:
print('Value error!')
class TestStudent(unittest.TestCase):
def test_invalid(self):
s1 = Student('Bob', -1)
s2 = Student('Bat', 101)
with self.assertRaises(ValueError):
s1.get_grade()
with self.assertRaises(ValueError):
s2.get_grade()
if __name__ == '__main__':
unittest.main()
Thanks

You're catching the ValueError inside the function. You need to either remove the try/except block in the function or re-raise it after doing whatever you want inside:
def get_grade(self):
try:
if self.score >= 60 and self.score < 80:
return 'B'
if self.score >= 80 and self.score <= 100:
return 'A'
if self.score >= 0 and self.score <60:
return 'C'
if self.score < 0 or self.score > 100:
raise ValueError('Invalid score value')
except Exception as e:
print('Value error!')
raise # Passes the exception up

Related

how can I use multiple else statements?

I want to use a few else statements in my class method:
class Musician :
def __initiate__(self,name, instrument, years_playing, practice_hours):
self.name = name
self.instrument =instrument
self.years_playing = years_playing
self.practice_hours = practice_hours
# self.level = int(self.practice_hours*self.years_playing)
def level(self):
if self.practice_hours <= 1 and self.years_playing <=1:
print (self.name,'is a beginner')
else:
self.practice_hours <=2 and self.years_playing <=2
print (self.name,'is an intermediate')
else:
self.practice_hours <= 3 and self.years_playing <=3
return (self.name, 'is an expert')
player_1 = Musician('Don', 'guitar', 1,3)
player_1.level()
There are many things wrong with your code. The actual question you have seems to be about using elif where you tried to use else; the general structure is
if condition:
something should happen
elif other_codition:
things which should be done in this case
elif third_condition:
other things
else:
unconditionally do this if none of the conditions above were true
where all the branches after the first are optional.
You are mixing print with return, and I'm guessing you meant __init__. Here's an attempt to fix the code.
class Musician:
def __init__(self, name, instrument, years_playing, practice_hours):
self.name = name
self.instrument =instrument
self.years_playing = years_playing
self.practice_hours = practice_hours
# self.level = int(self.practice_hours*self.years_playing)
def level(self):
if self.practice_hours <= 1 and self.years_playing <= 1:
return "beginner"
elif self.practice_hours <= 2 and self.years_playing <= 2:
return "intermediate"
elif self.practice_hours <= 3 and self.years_playing <= 3:
return "expert"
player_1 = Musician('Don', 'guitar', 1, 3)
lvl = player_1.level()
print(f"the level of {player_1.name} is {lvl}")
__init__ is a reserved name for the method which gets invoked when you create a new instance; you can't use a different name and hope for it to be invoked under the shme circumstances.
The level function will still return None if none of its conditions are true, but how to fix that depends on criteria I can't know. Perhaps a better design would be to check for "expert" or "intermediate" and then otherwise always return "beginner" if neither of those are true.
def level(self):
if self.practice_hours > 2 and self.years_playing > 2:
return "expert"
elif self.practice_hours > 1 and self.years_playing > 1:
return "intermediate"
# else
return "beginner"
You can use bisect to achieve the same, without using if
import bisect
def level(self):
level_dict = {0 : 'is a beginner', 1 : 'is an intermediate', 2 : 'is an expert'}
grade_ranges = [(0, 1), (1, 2), (2, 3)]
points, grade = zip(*grade_ranges)
return self.name + level_dict[bisect.bisect(points, self.practice_hours)-1]

Car class problem- break and acceleration functions?

I need to code an accelerate function, accelerating speed by 5 mph and a brake function decelerating speed by 5 mph for a car class with the following logic:
-when braked, speed must be >= 0. if speed < 0 then reset to 0 and display error
-when accelerated, speed be <= 130. if speed > 130 then reset to 130 and display error
I think I've gotten so far as the parameters but not sure how to proceed with the functions themselves.
Tester program:
from Car import *
def main():
my_car = Car("2008", "Honda Accord")
print(f"my_car after instantiating: {my_car}")
my_car.setSpeed(116)
print(f"my_car after my_car.setSpeed(116): {my_car}")
my_car.setSpeed(136)
print(f"my_car after my_car.setSpeed(136): {my_car}")
print ("*** car is accelerating ***")
for i in range(4):
my_car.accelerate()
print ("Current speed: ", my_car.getSpeed())
my_car.setSpeed(11)
print(f"my_car after my_car.setSpeed(11): {my_car}")
print ("*** car is braking ***")
for i in range(3):
my_car.brake()
print ("Current speed: ", my_car.getSpeed())
print(f"my_car at program end: {my_car}")
if __name__ == "__main__":
main()
This is the class:
class Car:
__make_model = ""
__year = ""
__speed = 0
def __init__(self, param_year, param_make_model):
self.__make_model = param_make_model
self.__year = param_year
self.__speed = 0
def setMake_model(self, param_make_model):
self.__make_model = param_make_model
def getMake_model(self):
return self.__make_model
def setYear(self, param_year):
self.__year = make
def getYear(self):
return self.__year
def setSpeed(self, inp_speed):
if inp_speed < 0 or inp_speed > 130:
print(f"Speed cannot be {inp_speed} mph.",
"Speed be must be 0-130 mph")
else:
self.__speed = inp_speed
def getSpeed(self):
return self.__speed
def __str__(self):
ret_val = f"Year : {self.__year}"
ret_val += f", Make and Model : {self.__make_model}"
ret_val += f", Speed : {self.__speed}"
return ret_val
I don't know how to proceed adding this to the class, specifically in getting the class to reset to the limit I want to set.
There is a lot of ways to skin this cat, but here is an option:
class Car:
__make_model = ""
__year = ""
__speed = 0
def __init__(self, param_year, param_make_model):
self.__make_model = param_make_model
self.__year = param_year
self.__speed = 0
def setMake_model(self, param_make_model):
self.__make_model = param_make_model
def getMake_model(self):
return self.__make_model
def setYear(self, param_year):
self.__year = make
def getYear(self):
return self.__year
def setSpeed(self, inp_speed):
if inp_speed < 0 or inp_speed > 130:
print(f"Speed cannot be {inp_speed} mph.",
"Speed be must be 0-130 mph")
else:
self.__speed = inp_speed
def getSpeed(self):
return self.__speed
def accelerate(self):
if self.__speed <= 125:
self.__speed += 5
else:
print("error max speed is 130")
self.__speed = 130
def brake(self):
if self.__speed >= 5:
self.__speed -= 5
else:
print("error reseting to 0")
self.__speed = 0
def __str__(self):
ret_val = f"Year : {self.__year}"
ret_val += f", Make and Model : {self.__make_model}"
ret_val += f", Speed : {self.__speed}"
return ret_val

Name Error despite having defined it earlier?

def categorise_sourceIP(df):
df_sIPf = pd.df.sourceIP.value_counts()
df_sIPf['counts'] = np.array(df.sourceIP.value_counts())
df_sIPf['sourceIP'] = df_sIPf.index
df_sIPf.reset_index(level=0,inplace=True,drop=True)
counts_cate = []
for num in df_sIPf['counts']:
if num in range(0,21):
counts_cate.append('<20')
elif num in range(21,201):
counts_cate.append('21-200')
elif num in range(201,401):
counts_cate.append('201-400')
elif num > 400:
counts_cate.append('>400')
counts_cate=df_sIPf['categorised_count']
The error call back is the following
NameError Traceback (most recent call last)
<ipython-input-11-9622f76efabe> in <module>
27 elif num > 400:
28 counts_cate.append('>400')
---> 29 counts_cate=df_sIPf['categorised_count']
NameError: name 'df_sIPf' is not defined
How do I fix this? At a key stage in my problem set.
Essentially trying to build a relationship between clusters of two different variables in the dataframe so a similar piece of code will be written for the second set.
You need to return df_sIPf from your function if you want it to be accessible outside that function:
def categorise_sourceIP(df):
df_sIPf = pd.df.sourceIP.value_counts()
df_sIPf['counts'] = np.array(df.sourceIP.value_counts())
df_sIPf['sourceIP'] = df_sIPf.index
df_sIPf.reset_index(level=0,inplace=True,drop=True)
counts_cate = []
for num in df_sIPf['counts']:
if num in range(0,21):
counts_cate.append('<20')
elif num in range(21,201):
counts_cate.append('21-200')
elif num in range(201,401):
counts_cate.append('201-400')
elif num > 400:
counts_cate.append('>400')
return df_sIPf
counts_cate = categorise_sourceIP(df)['categorised_count']

Name 'x' is not defined / Global variable?

I'm learning to program with python and I came across this issue: I'm trying to make a Guessing Game, and while trying to check for the win condition, the function doesn't recognise the input variable, which I made sure I returned with a previous function. So i get the 'name << 'first_input' is not defined' >> error. I thought it had something to do with the variable not being global or sth like that.
import random
ran_int = random.randint(1,100)
guesses = 0
# here you input the number and it keeps asking unless you do so with 1 to 100
def ask():
first_input = 0
while first_input < 1 or first_input > 100:
first_input = int(input('Enter a number between 1 and 100: '))
return first_input
# this is just to increment the number of guesses stored for showing at the end # of the game
def guesses_inc():
global guesses
guesses += 1
return guesses
# here is where i get the error, as if my ask() function didn't return
# the value properly or as if I assigned it wrongly
def check_win_1():
if first_input == ran_int:
guesses_inc()
print(f'BINGO!\nYou guessed correctly after {guesses} times.')
elif (abs(ran_int - first_input) <= 10):
guesses_inc()
print('WARM!')
ask2()
elif first_input < 1 or first_input > 100:
print('Out of bounds!')
ask2()
else:
guesses_inc()
print('COLD!')
ask2()
ask()
check_win_1()
And here is the error
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-11-bfd5497995df> in <module>
----> 1 check_win_1()
NameError: name 'first_input' is not defined
I didn't paste the whole code because while testing it it returned the error at this stage so I didn't think the rest mattered for this particular problem. I tried making the var input global and stuff like that but i don't think I did it properly.
Your method call is not correct. You should call your functions like this
def check_win_1(first_input):
if first_input == ran_int:
guesses_inc()
print(f'BINGO!\nYou guessed correctly after {guesses} times.')
elif (abs(ran_int - first_input) <= 10):
guesses_inc()
print('WARM!')
ask2()
elif first_input < 1 or first_input > 100:
print('Out of bounds!')
ask2()
else:
guesses_inc()
print('COLD!')
ask2()
first_input = ask()
check_win_1(first_input)
The error is there because you are trying to use first_input somewhere (i.e. inside check_win_1()).
A possible, not recommended, solution is to qualify your variable as global, which should be used VERY sparingly.
Instead, it is recommended to use function parameters, so as to encapsulate your code in self-contained blocks, e.g.:
def func(a, b):
return a + b
x = func(10, 5)
rather than:
def func():
global a, b
return a + b
a = 10
b = 5
x = func()
For your that may mean doing something like:
def check_win_1(first_input, ran_int):
...
and use them accordingly, e.g.:
first_input = ask()
check_win_1(first_input, ran_int)
etc.
EDIT
Following the above principle, your code could have looked like:
import random
MIN_VAL = 1
MAX_VAL = 100
WARM_LIMIT = 10
def ask_number(
min_val=MIN_VAL,
max_val=MAX_VAL):
guess = None
while guess is None:
guess = int(input(f'Enter a number between {min_val} and {max_val}: '))
if guess < min_val or guess > max_val:
print('Out of bounds!')
guess = None
return guess
def check_guess(
guess,
target,
num_guesses,
warm_limit=WARM_LIMIT):
if guess == target:
print(f'BINGO!\nYou guessed correctly after {num_guesses} times.')
return True
else:
if (abs(guess - target) <= warm_limit):
print('WARM!')
else:
print('COLD!')
return False
# : main
target = random.randint(MIN_VAL, MAX_VAL)
num_guesses = 0
won = False
while not won:
guess = ask_number()
num_guesses += 1
won = check_guess(guess, target, num_guesses)

Calling a method within another method in the context of #property

With the following sample code containing #property decorators:
class Grade_Calculator(object):
def __init__(self,score):
self.score = score
#property
def grade(self):
if self.score in range(60,70):
grade = 'D'
elif self.score in range(70,80):
grade = 'C'
elif self.score in range(80,90):
grade = 'B'
elif self.score in range(90,101):
grade = 'A'
return grade
#property
def failure(self):
if self.score < 60:
print 'See me'
grade = 'F'
return grade
and an instance:
g = Grade_Calculator(28)
g.grade returns an UnboundLocalError. I would like to call failure() within grade() to avoid this error.
With an instance:
g = Grade_Calculator(89)
g.failure() fails silently. I would like to call grade() within failure() to act as a fail-safe in this situation.
I've seen a lot of references to just doing self.method() calls, but they're not working for me and I think the presence of the decorator is screwing me up somehow:
#property
def grade(self):
if self.score < 60:
self.failure()
elif self.score in range(60,70):
grade = 'D'
elif self.score in range(70,80):
grade = 'C'
elif self.score in range(80,90):
grade = 'B'
elif self.score in range(90,101):
grade = 'A'
return grade
g = Grade_Calculator(28)
g.grade
See me
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-667-fb10e6cf27d4> in <module>()
----> 1 g.grade
./sample.py in grade(self)
6 def grade(self):
7 if self.score < 60:
----> 8 self.failure()
9 elif self.score in range(60,70):
10 grade = 'D'
TypeError: 'str' object is not callable
What am I not understanding?
You have defined failure as a property. Therefore, you can't call it using () notation; in fact, not doing that is kinda the whole point of using a property. self.failure returns 'F' and you then try to call it, which is why you get the error about not being able to call a string. It's as if you wrote 'F'() which is an obvious mistake.
Also, you're throwing away the value you get from failure and you're only returning a value in the case of failure (which means you get None otherwise). Of course you only access failure if you have a failing grade to begin with... in which case you don't need to check that condition inside failure, now do you?
Also, printing stuff in properties is pretty bad style. You don't expect something to be printed when you access an attribute. Separation of concerns: something outside of the class should probably be doing the printing, or you should have a separate method to call to print whatever is returned by grade.
I'd rewrite your class as follows:
class Grade_Calculator(object):
def __init__(self, score):
self.score = score
#property
def grade(self):
if self.failure:
return 'F'
if 60 <= self.score < 70:
return 'D'
if 70 <= self.score < 80:
return 'C'
if 80 <= self.score < 90:
return 'B'
if 90 <= self.score < 100:
return 'A'
#property
def failure(self):
return self.score < 60 # returns True or False
def print_grade(self):
print self.grade, "- see me" * self.failure
if __name__ == '__main__':
c = Grade_Calculator(71)
c.print_grade()
class Grade_Calculator(object):
def __init__(self, score):
self.score = score
#property
def grade(self):
grade = self.failure
if 60 <= self.score < 70:
grade = 'D'
elif 70 <= self.score < 80:
grade = 'C'
elif 80 <= self.score < 90:
grade = 'B'
elif 90 <= self.score < 100:
grade = 'A'
return grade
#property
def failure(self):
if self.score < 60:
print 'See me'
grade = 'F'
return grade
if __name__ == '__main__':
a = Grade_Calculator(71)
print a.grade
You're basically creating a new attribute to your object with the failure method. And you're never trying to get its value, that is why you'll never see 'See me'
By the way, you're overriding the value of grade which was initially an integer. You converted it to a string ('A', 'B', 'C', ...)
The code I provided works, but I changed a few things. There is no need to call range (this is expensive). You can use value < variable < other_value in Python

Categories