I'm absolutely brand new to Python unit test. I need to use it for a project I have to submit. I sort of have an idea of where to begin, it looks like we basically put in test parameters to functions we have defined in our program and we enter the expected result. If the expected result is output, we get OK, otherwise we will get Failure, or an error.
So my problem is that I have multiple user inputs stored into variables that are within for loops or while loops. I don't know where to even begin with this to set test values in for them.
Here is all of my code:
studentTripExpenses = {}
def dictCreate(studentAmount):
for i in range(0, studentAmount):
studentName = input("What is the name of the student? ")
expenseList = []
print("Enter 'done' to move to the next student.")
while True:
expense = input("What is the cost of this expense? ")
if expense.lower() == 'done':
break
elif (float(expense) >= 0) or (float(expense) < 0):
expenseList.append(float(expense))
elif not expense.isdigit():
print("Please enter a number or enter 'done' to move on.")
studentTripExpenses[studentName] = expenseList
return studentTripExpenses
def studentCost(dct):
for i in dct:
#Variable for individual costs of student
personalCost = 0
#Determines the total cost for each student
for x in dct[i]:
personalCost = personalCost + x
#Sets each students value to their total cost to two decimal places
dct[i] = float("%.2f" % personalCost)
return dct
def amountsDue(expenseLst, studentAvgPrice):
#Runs through the dictionary of students and individual total trip costs
for key in expenseLst:
maxPerson = max(expenseLst, key=expenseLst.get)
costDifference = 0
#Determines who owes who how much money
if max(expenseLst.values()) > expenseLst[key]:
costDifference = studentAvgPrice-expenseLst[key]
if (costDifference < 0):
costDifference = costDifference * -1
print("%s owes %s $%.2f" % (key, maxPerson, costDifference))
def main():
numOfStudents = int(input("How many students are going on the trip? "))
studentCostDict = dictCreate(numOfStudents)
studentTripExpenses = studentCost(studentCostDict)
totalCost = 0
#Gets the total cost for all students
for key in (studentTripExpenses):
totalCost = totalCost + studentTripExpenses[key]
#Changes the total cost to 2 decimal places
totalCost = float("%.2f" % totalCost)
#Determines the average amount spent per student
avgCost = float("%.2f" % (totalCost/len(studentTripExpenses)))
amountsDue(studentTripExpenses, avgCost)
main()
You can use mocking, where you replace a function or class with a test-supplied version. You can do this with the unittest.mock() module.
In this case, you can patch the input() name in your module; instead of the built-in function, the mock object will be called:
from unittest import mock
from unittest import TestCase
import module_under_test
class DictCreateTests(TestCase):
#mock.patch('module_under_test.input', create=True)
def testdictCreateSimple(self, mocked_input):
mocked_input.side_effect = ['Albert Einstein', '42.81', 'done']
result = dictCreate(1)
self.assertEqual(result, {'Albert Einstein': [42.81]})
Because input doesn't exist in your module (it is a built-in function), I told the mock.patch() decorator to create the name; now this input will be used instead of the built-in function.
The side_effect attribute lets you state multiple results; each time the mock is called, it'll return the next value in that list. So the first time 'Albert Einstein' is returned, the next time '42.81', etc.
Together, this lets you simulate actual user inputs.
If you do your test right, you'll notice that there is a bug in your function; the float() call will throw a ValueError exception when anything other than done or a valid numeric value is entered. You need to rework your code to account for that. Try with mocked_input.side_effect = ['Albert Einstein', 'Not an expense', '42.81', 'done'] to trigger the bug.
In case we do not have classes.
In the names.py file, we have the get_names function.
def get_names() -> list:
names = [str(input("Enter name: "))]
while str(input("Do you want to add another name")) == "Y":
names.append(str(input("Enter name: ")))
return categories
In the test_names.py file, we can write test like the following
import numpy as np
from unittest import mock
from src.main.names import get_names
#mock.patch('src.main.names.input', create=True)
def test_should_get_names_from_users(mocked_input):
mocked_input.side_effect = ["John", "Y", "Robert", "N"]
actual_names = get_names()
expected_names = ['John', "Robert"]
assert actual_names == expected_names
Related
class Budget:
def __init__(self):
self.wants_perc = 0
self.wants_amt = 0
self.wants_left = 0
self.needs_perc = 0
self.needs_amt = 0
self.needs_left = 0
self.food_perc = 0
self.food_amt = 0
self.food_left = 0
while True:
try:
self.monthly_income = float(input("Enter your monthly income after taxes: "))
break
except ValueError:
print("Invalid Input : Please enter a number")
continue
while True:
print("Enter desired percentage of income spent for each category (do not include %): ")
try:
self.wants_perc = float(input("Wants: "))
self.needs_perc = float(input("Needs: "))
self.food_perc = float(input("Food: "))
if self.wants_perc + self.needs_perc + self.food_perc not in range(95, 105):
print("Invalid Input : Must add to 100%")
continue
else:
break
except ValueError:
print("Invalid Input : Please enter a number")
continue
def deposit(self):
dep_loc = input("Where would you like to deposit? ")
while True:
try:
if dep_loc.lower() == "wants":
self.wdep_amt = float(input("Deposit amount: "))
self.wants()
break
elif dep_loc.lower() == "needs":
self.ndep_amt = float(input("Deposit amount: "))
self.needs()
break
elif dep_loc.lower() == "food":
self.fdep_amt = float(input("Deposit amount: "))
self.food()
break
else:
print("Invalid Input")
break
except ValueError:
print("Invalid Input : Please enter a number")
continue
def wants(self):
self.wants_max = (self.wants_perc / 100) * self.monthly_income
self.wants_amt += self.wdep_amt
self.wants_left = self.wants_max - self.wants_amt
print(f"Amount spent on wants: ${self.wants_amt} \nAmount left to spend: ${round(self.wants_left,2)}")
def needs(self):
self.needs_max = (self.needs_perc / 100) * self.monthly_income
self.needs_amt += self.ndep_amt
self.needs_left = self.needs_max - self.needs_amt
print(f"Amount spent on needs: ${self.needs_amt} \nAmount left to spend: ${round(self.needs_left,2)}")
def food(self):
self.food_max = (self.food_perc / 100) * self.monthly_income
self.food_amt += self.fdep_amt
self.food_left = self.food_max - self.food_amt
print(f"Amount spent on food: ${self.food_amt} \nAmount left to spend: ${round(self.food_left,2)}")
wyatt = Budget()
while True:
wyatt.deposit()
I know this is a very general question, but is it possible to reduce the amount of repeated code I use? I feel like there has to be a way to use one general variable in a loop for each of the categories. My three functions that use the food, wants, and needs variables are all the exact same besides the names. I thought of getting user input and adding it to a list and indexing that list to get each category, but I couldn't fully figure it out. This may be too broad for stack overflow and if it is I apologize. Thanks!
In general there is no mechanism for reducing the amount of code - if there was, it would already be part of the language. Making a dictionary instead of member variables doesn't really help, because all it will do is replace your 9 member variables with 9 dictionary items.
However, there is an opportunity for factoring out repeated operations. Your instinct is correct that repeated blocks of code are a sign of poor design. I would suggest you start with a class structure something like this:
class BudgetItem:
def __init__(self, name):
self.name = name
self.perc = 0
self.amt = 0
self.left = 0
def get_perc(self):
self.perc = float(input(f"{self.name}: "))
class Budget:
def __init__(self, monthly_income):
self.monthly_income = monthly_income
self.wants = BudgetItem("Wants")
self.needs = BudgetItem("Needs")
self.food = BudgetItem("Food")
self.all_items = (self.wants, self.needs, self.food)
def gather_percentages(self):
print("Enter desired percentage of income"
" spent for each category (do not include %): ")
for b in self.all_items:
b.get_perc()
if 95.0 <= sum(b.perc for b in self.all_items) <= 105.0:
print("Precentages must sum to 100")
# etc.
Create a separate class to represent a budget category, since the logic for each is identical. Add methods to the little class to capture that logic. Now re-write your main class in terms of those three individual items. I won't take the time to refactor your whole program, but I hope you get the idea. You should end up with a much shorter program and no significant repeated logic.
One other thing: I think that putting an "input" statement in a class constructor is a terrible idea. Constructors should not contain complicated loops or extended logic. I would put it outside the class and pass the data into the class as arguments, or as method calls.
Im making a "game" for practice. Its a basic guess game, but i wanted to make the game only with functions. This is my problem (for ex.):
function 1:
variablaA
#some other code
function 2:
variableB
variableC = varibleA + variableB
#some other code
I have tried too goole about objects and classes but im not sure i understand what im doing right now.
import random
import sys
min = 1
max = 99
guessed_number = random.randint(min, max)
class functions:
def __init__(game, difficulty, lifes):
game.difficulty = difficulty
game.lifes = lifes
def GameDiff(hardness):
#Setting game difficulty
print "Select difficulty : \n 1; Easy \n 2; Medium \n 3; Hard \n"
difficulty = raw_input()
if difficulty == "1":
print "Its just the beginning"
lifes = 15
elif difficulty == "2":
lifes = 10
elif difficulty == "3":
lifes = 5
else:
print "This isn't an option try again"
GameDiff(hardness)
def core(basic):
#The core of the game
print "I guessed a number..."
player_number = int(raw_input("Whats the number I thinking of?"))
constant = 1
GameTime = 1
while GameTime == constant:
if player_number < guessed_number:
print "Your number is smaller than my guessed number"
print "Try to duplicate your number (But maybe Im wrong)"
player_number = int(raw_input("Make your tip again\n"))
elif player_number > guessed_number:
print "Your number is bigger than my guessed number"
print "Try to half your number (But maybe Im wrong)"
player_number = int(raw_input("Make your tip again\n"))
else:
GameTime = 0
print "You guessed it! Congratulations"
def main(self):
#The whole game only with functions
functions.GameDiff()
functions.core()
Function = functions()
Function.main()
if you are defining function with parameters, you need to pass data(parameters) into a function when you call it
example:
def my_function(name):
print("my name is " + name)
my_function("Kevin")
in your case you define:
def GameDiff(hardness):
def core(basic):
which are expecting parameters
and when you are calling those funcitions, you are doing that on wrong way:
def main(self):
#The whole game only with functions
functions.GameDiff()
functions.core()
Function = functions()
you need to pass parameters
example:
functions.GameDiff(5)
functions.core(1)
Function = functions(1,5)
NOTE: good practice will be to use self instead of game
def __init__(self, difficulty, lifes):
self.difficulty = difficulty
self.lifes = lifes
they are just two different kinds of class elements:
Elements outside the init method are static elements; they belong
to the class. They're shared by all instances.
Elements inside the init method are elements of the
object (self); they don't belong to the class.Variables created inside init (and all other method functions) and prefaced with self. belong to the object instance.
I'm new here and I have a problem with too much if, else statement in while loop. I want to refactor it to function, but I don't have any idea how to do it.
My code:
brand = input("Please select a brand...")
if brand.lower() == "XX" or sex == "1":
print("You selected a XX...")
while True:
product = input()
if product.lower() == "apple" or product == "1":
print("You selected Apples!\n")
while True:
size_schema = input()
if size_schema.lower() == "in" or size_schema.lower() == "inch" or size_schema == "1":
while True:
apple_size = float(input())
if 8.5 <= apple_size <= 12.0:
real_apple_size = round(apple_size, 2)
print("Your apple size is {} inch!".format(real_apple_size))
cursor = size_guide.find({})
for document in cursor:
a = document['Product']['Apple']['INCH']
try:
b = [float(x) for x in a if x != '']
result = min(enumerate(b), key=lambda i: abs(i[1] -
float(real_apple_size)))
c = str(result[1])
except ValueError:
pass
real_apple_size = str(real_apple_size)
if real_apple_size in document['Product']['Apple']['INCH']:
index = document['Product']['Apple']['INCH'].index(real_apple_size)
print("We have this apples from {} brand!"
.format(document['Brand']))
elif c in document['Product']['Apple']['INCH']:
last_list_value = next(s for s in reversed(a) if s)
index = document['Product']['Apple']['INCH'].index(c)
real_apple_size = float(real_apple_size)
print("SORRY! We don't have exactly your size, "
"but we have similar size from {} brand!"
.format(document['Brand']))
else:
print("Sorry, We don't have apples for you from {} brand!"
"Check our other products!"
.format(document['Brand']))
else:
print("Please select your apple size in range 8.5-12.0 inch!")
continue
break
I want to reduce this code and insert it in function.
Better (though probably not best) functional code would be a set of functions that are reusable, and each do one (or a very small number) of things. For example:
def get_product():
brand=input("What brand?")
#input validation logic
product=input("What product?")
#input validation for product given brand
size=input("What size?")
#input validation given brand and product
color=input("What color? (enter 'none' for no color)")
#That's right, more validation
return brand, prod, size, color
def prod_lookup(brand, prod, size, color):
cursor = size_guide.find({})
for document in cursor:
#lookup product with logic as in your post
if __name__ == "__main__":
brand, prod, size, color = get_product()
prod_lookup(brand, prod, size, color)
Again, this is just an example of one way to do it that would be much less messy. If you need to update your list of available products, for example, you only have to adjust one part of one function, rather than choosing from a deeply nested bunch of conditionals and loops.
I'm sure there are better ways, but hopefully this gives you some idea where to start thinking.
Adding one possible implementation of input validation with product lookup. This way, your brand will always be the product number rather than the string, which is usually a faster lookup:
brand_dict={'xx':'1','yy':'2'}
while True:
brand=input("Enter brand: ").lower()
if brand in brand_dict.keys():
brand=int(brand_dict[brand])
break
elif brand in brand_dict.values():
brand=int(brand)
break
else:
print("Brand not recognized. Try again!")
First, just wrap the whole thing in one function
def foo():
brand = input("Please select a brand...")
if brand.lower() == "XX" or sex == "1":
# etc.
Now, note that your first if statement encompasses the rest of the function, and there is no else clause. That is, if the condition fails, you'll fall through to the end of the function and implicitly return. So just return explicitly if the condition does not* hold. This lets you immediately dedent the bulk of your code.
def foo():
brand = input("Please select a brand...")
if brand.lower() != "XX" and sex != "1":
return
print("You selected a XX...")
# etc
Repeat this process, either returning or breaking out of the enclosing infinite loop, for each of your else-less if statements.
OK - I am trying to get a Python function to accept variables from two other functions. Is this possible ?
A sample of what I am trying to do it below (I have simmed down the original code - for input here). Hopefully you get theidea of what I am trying to do. In a nutshell, I have Rectangle () which calls Extras() and the I want the output from Rectangle and Extras to be sent to the Calculate_Deposit ().
Is this possible ?
def calculate_deposit(total_cost, extras):
deposit_percent = float(raw_input("Enter Deposit % (as a decimal) of Total Cost: "))
months_duration = float(raw_input("Enter the number of months client requires: "))
if deposit_percent >0:
IN HERE JUST SOME CALCULATIONS
else:
print "The total amount required is: ", total_cost
def rectangle(width, height, depth, thickness):
type = raw_input("Enter lowercase c for concrete: ")
if type == 'c':
output = IN HERE JUST COME CALCULATIONS
else:
return raw_input("Oops!, something went wrong")
print output + extras()
total_cost = calculate_deposit(output, extras)
def extras():
type = float(raw_input("Enter 1 for lights: "))
if type == 1:
light = 200
print "The cost of lights are: ", light
return light
else:
return raw_input("No extras entered")
In rectangle, you call extras(), then you send just the function extras to calculate_deposit(). You want to send the result of the extras() call, not a reference to the function itself. You can make a minor change and save that value, referring to it when you print and when you go into calculate_deposit.
Change this:
print output + extras()
total_cost = calculate_deposit(output, extras)
To this:
extra = extras()
print output + extra
total_cost = calculate_deposit(output, extra)
I am trying to print a variable of a score and name to a .txt file using python.
import random
import csv
import operator
import datetime
now = datetime.datetime.now() ## gets the exact time of when the user begins the test.
def main():
global myRecord
myRecord = []
name = getNames()
myRecord.append(name)
record = quiz()
def getNames(): ## this function grabs first and lastname of the user
firstName = input ("Please enter your first name") ## asks for users name
surName = input("Please enter your surname") ## asks for users suername
space = " "
fullName = firstName + space +surName ## puts data of name together to make full name
print("Hello")
print (fullName)
myRecord.append(fullName)
return fullName ## this is a variable returned to main
def quiz():
print('Welcome. This is a 10 question math quiz\n')
score = 0 ## sets score to 0.
for i in range(10): ## repeats question 10 times
correct = askQuestion()## if the statement above if correct the program asks a question.
if correct:
score += 1## adds one to the score
print('Correct!\n')## prints correct if the user gets a question correct.
else:
print('Incorrect!\n') ## prints incorrect if the user gets a question wrong.
return 'Your score was {}/10'.format(score)
def randomCalc():
ops = {'+':operator.add, ## selects one of the three operators
'-':operator.sub, ## selects one of the three operators
'*':operator.mul,} ## selects one of the three operators
num1 = random.randint(0,12) ## samples a number between 0 and 12
num2 = random.randint(1,10) ## zero are not used to stop diving by zero
op = random.choice(list(ops.keys()))
answer = ops.get(op)(num1,num2)
print('What is {} {} {}?\n'.format(num1, op, num2)) ## puts together the num1, the operator and num2 to form question
return answer
def askQuestion():
answer = randomCalc()
guess = float(input())
return guess == answer
def myfileWrite (myrecord):
with open('Namescore.txt', 'w') as score:
score.write(fullName + '\n')
main()
here is the full code it should ask the users name, print 10 maths questions and then save the time name and score to a txt file
if you can help please do
many thanks
Your indentation is incorrect and you never actually call the function:
with open('Namescore.txt', 'w') as score:
score.write(fullName + '\n')
the code you wrote would IIRC re-make the file each time you ran the code. I believe this is the correct way to do it:
with open("Namescore.txt", "a") as file:
file.write(name, score, "\n") # I don't know what your vars are called
This will append to the file rather than rewrite :)
If you want to do it your way, the correct way would be:
def writeScore(name, score):
file = open("Namescore.txt", "a")
file.write(name, score, "\n")
file.close()
writeScore("Example Examlpus", 201)