Quiz 4
Study guide for Quiz 4
- The quiz is in class, Tuesday, November 25.
- This quiz is a mix of short & long answer questions on Canvas plus coding in PyCharm.
- This quiz will include optional extra credit involving coding.
- You may bring 1 double-sided sheet of letter paper with your own hand-written notes.
- An Honor Code violation on any quiz or exam results in a course grade of F.
- Failure to submit a quiz or exam results in a course grade of F.
Content
- Study key terms from slides and labs. Look for boldfaced, underlined terms in slides and emphasized terms in labs.
- Study Knowledge Checks from labs.
- 5 Low-level program design rules: (Weeks 11-13 + accompanying Lab)
- You do not need to know PyGame.
- Be prepared to define the 5 rules and explain them.
- Be prepared to analyze a given block of Python code and identify design rule violations.
- Be prepared to fix a block of Python code that violates a design rule.
- Designing and writing unit tests code (Weeks 5-6), including:
- Running
pytest. I will provide instructions for setting up a virtual environment and installing pytest on the lab computers. - Writing
assert statements. - testing if exceptions are raised using
pytest. - Achieving a required amount of branch coverage. I will provide instructions for generating a coverage report.
- Identifying unique program paths through the source code. This will help you to achieve 100% branch coverage.
- Extra credit will involve expanding on the topics above: analyzing and correcting design rule violations, and writing test cases.
Sample design problems
Design sample problem 1
1
2
3
4
5
6
7
8
9
10
11
| def process_order(order_amount):
if order_amount < 0:
raise Exception("Bad value!")
if order_amount > 1000:
shipping = 25
else:
shipping = 10
tax = order_amount * 0.07
return order_amount + shipping + tax
|
- Identify which design rules are violated.
Answer
0.07, 1000, 25, and 10 are magic literals, violating Rule #1: Avoid magic literals.- A generic Exception is raised without specifying what was wrong, violating Rule #5: Raise specific errors and define your own if needed.
- Modify the code to bring it into compliance with the design rules.
Answer
- Assign the literals to “constant” variables, and use the variables in the computations, e.g.,
SHIPPING_THRESHOLD = 1000. Then later: if order_amount > SHIPPING_THRESHOLD: - Alternately, you could transform the literal values into function parameters, e.g.,
def process_order(order_amount: float, shipping_threshold: int, low_ship: int, high_ship: int, tax_rate: float): - Raise a more specific exception type with a better message, like
raise ValueError('order amount must be greater than 0'). You could also define a custom error class and raise it.
Design sample problem 2
1
2
3
4
5
6
7
8
| def create_user_profile():
name = input("Enter your name: ")
age = int(input("Enter your age: "))
with open("profiles.txt", "a") as file: # append to the file.
file.write(f"{name},{age}\n")
print("Profile saved.")
|
- Identify which design rules are violated.
Answer
- This function violates Rule #1: Avoid magic literals. The string literal
profiles.txt is hardcoded. - This function performs input gathering, data formatting, file writing, and output display all at once. This is a violation of Rule #2: Functions should have a single responsibility.
- You could also make some argument that the function is violating Rule #4 by not handling errors at the lowest sensible level. Since the user interface is included in this code, it would be reasonable to check that you are able to append to the file.
- Modify the code to bring it into compliance with the design rules.
Answer
- Make the filename to write to into a function parameter.
- You should separate the code into at least two functions: a user interface and a function to write the data. Or, you could do three functions: one for input, one to write the data, and one to produce output.
- If you want to make the code more robust, you can add some error handling and retry for a FileNotFoundError.
- Note: There is one more input error that can occur. Can you spot it? How would you handle it?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| def save_user_profile(name: str, age: int, filename: str):
with open(filepath, "a") as file: # open() will raise a FileNotFoundError if filepath doesn't exist.
file.write(f"{name},{age}\n")
def create_user_profile():
name = input("Enter your name: ")
age = int(input("Enter your age: "))
filename = ''
while filename == '': # you could also say while not filename:
try:
filename = input("Enter the filename to append to: ")
save_user_profile(name, age, filename)
except FileNotFoundError:
print("Could not find that file. Try again.")
print("Profile saved.")
|
Design sample problem 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| def apply_clearance_discount(price: float) -> float:
if price >= 100:
discount = price * 0.40
else:
discount = price * 0.60
return price - discount
def apply_loyalty_discount(price: float) -> float:
if price >= 100:
discount = price * 0.20
else:
discount = price * 0.10
return price - discount
def run_discount_menu():
while True:
print("\n--- Discount Menu ---")
print("1. Clearance Discount")
print("2. Loyalty Discount")
print("3. Exit")
choice = input("Choose a discount type (1-3): ")
if choice == "3":
print("Goodbye!")
break
try:
price = float(input("Enter the original price: $"))
except ValueError:
print("Invalid price. Please enter a number.")
continue
if choice == "1":
final = apply_clearance_discount(price)
print(f"Applying Clearance discount. Final price: ${final:.2f}")
elif choice == "2":
final = apply_loyalty_discount(price)
print(f"Applying Loyalty discount. Final price: ${final:.2f}")
else:
print("Invalid option. Try again.")
if __name__ == "__main__":
run_discount_menu()
|
- Identify which design rules are violated.
Answer
- Again we violate Rule #1: Avoid magic literals.
100 and all the float values are magic literals. - Of course, there are many other string literals in the user prompts and user output messages. We generally ignore these as magic literals, until you care about internationalization (i18n) and accessibility (a11y) in production systems.
- This code clearly violates Rule #3: Don’t repeat yourself (DRY) as the functions
apply_clearance_discount and apply_loyalty_discount are nearly identical. - You could make the argument that
run_discount_menu is doing too much between showing the menu, getting user input, and displaying user output, possibly violating Rule #2: Functions should have a single responsibility. I think you could argue either way.
- Modify the code to bring it into compliance with the design rules.
Answer
- In this case, we should provide “constant” variables for the literals in our calculation to solve #1.
- To fix Rule #3, we should combine the two
discount functions and parameterize the parts that vary.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| def apply_discount(price: float, discount_threshold: int, above_discount: float, below_discount: float) -> float:
if price >= discount_threshold:
discount = price * above_discount
else:
discount = price * below_discount
return price - discount
DISCOUNT_THRESHOLD = 100
CLEARANCE_ABOVE = 0.4
CLEARANCE_BELOW = 0.6
LOYALTY_ABOVE = 0.2
LOYALTY_BELOW = 0.1
def run_discount_menu():
while True:
print("\n--- Discount Menu ---")
print("1. Clearance Discount")
print("2. Loyalty Discount")
print("3. Exit")
choice = input("Choose a discount type (1-3): ")
if choice == "3":
print("Goodbye!")
break
try:
price = float(input("Enter the original price: $"))
except ValueError:
print("Invalid price. Please enter a number.")
continue
if choice == "1":
final = apply_discount(price, DISCOUNT_THRESHOLD, CLEARANCE_ABOVE, CLEARANCE_BELOW)
print(f"Applying Clearance discount. Final price: ${final:.2f}")
elif choice == "2":
final = apply_discount(price, DISCOUNT_THRESHOLD, LOYALTY_ABOVE, LOYALTY_BELOW)
print(f"Applying Loyalty discount. Final price: ${final:.2f}")
else:
print("Invalid option. Try again.")
if __name__ == "__main__":
run_discount_menu()
|
Sample Testing problems
These sample problems are the same as from Midterm 1. I will not ask you to write Control Flow Graphs for this exam, however, you may benefit from creating them to ensure you test all paths.
Testing Sample Problem 1
1
2
3
4
5
6
7
| def is_prime(number):
if number < 2:
return False
for i in range(2, int(number ** 0.5) + 1):
if number % i == 0:
return False
return True
|
- Add
assert statements to the following test case that exercise all unique program paths.def test_is_prime():
# Your code here.
Solution
unique program paths
The unique edges in the path are highlighted. You do not need to highlight the unique edges on the quiz.
- (1, 2, 3)
- (1, 2, 4, 7)
- (1, 2, 4, 5, 6)
- (1, 2, 4, 5, 4, 7) or (1, 2, 4, 5, 4, 5, 6)
test case
def test_is_prime():
# Some paths can be exercised with multiple input values.
# The goal is to exercise all program paths.
assert is_prime(1) == True # tests path (1, 2, 3)
assert is_prime(2) == True # path (1, 2, 4, 7)
assert is_prime(4) == False # path (1, 2, 4, 5, 6)
assert is_prime(5) == True # path (1, 2, 4, 5, 4, 7)
Testing Sample Problem 2
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| def generate_fibonacci(n):
if n <= 0:
return "Error: Number of terms must be a positive integer"
fibonacci_sequence = [1]
a = 1
b = 2
count = 1
while count < n:
fibonacci_sequence.append(a)
# Update values of a and b to the next numbers in the sequence
a, b = b, a + b
count += 1
return fibonacci_sequence
|
- List the unique program paths.
- Add
assert statements to the following test case that exercise all unique program paths.def test_generate_fibonacci():
# Your code here.
Solution
unique program paths
The unique edges in the path are highlighted. You do not need to highlight the unique edges on the quiz.
- (14, 15, 16)
- (14, 15, 18-21, 23, 29)
- (14, 15, 18-21, 23, 24-27, 23, 29)
test case
def test_generate_fibonnaci():
# Some paths can be exercised with multiple input values.
# The goal is to exercise all program paths.
assert generate_fibonnaci(0) == "Error: Number of terms must be a positive integer" # tests path (14, 15, 16)
assert generate_fibonnaci(1) == [1] # path (14, 15, 18-21, 23, 29)
assert generate_fibonnaci(6) == [1, 1, 2, 3, 5, 8] # path (14, 15, 18-21, 23, 24-27, 23, 29)
Testing Sample Problem 3
36
37
38
39
40
41
42
43
44
45
| def factorial(n):
# Input validation
if not isinstance(n, int) or n < 0:
return "Error: Input must be a non-negative integer"
# Use iterative approach
result = 1
for i in range(1, n + 1):
result *= i
return result
|
- List the unique program paths.
- Add
assert statements to the following test case that exercise all unique program paths.def test_factorial():
# Your code here.
Solution
unique program paths
The unique edges in the path are highlighted. You do not need to highlight the unique edges on the quiz.
- (36, 38, 39)
- (36, 38, 42, 43, 45)
- (36, 38, 42, 43, 44, 43, 45)
test case
def test_factorial():
# Some paths can be exercised with multiple input values.
# The goal is to exercise all program paths.
assert factorial("Alice") == "Error: Number of terms must be a positive integer" # tests path(36, 38, 39)
assert factorial(-1) == "Error: Number of terms must be a positive integer" # also tests path(36, 38, 39)
assert factorial(0) == 1 # tests path(36, 38, 42, 43, 45)
assert factorial(5) == 120 # tests path(36, 38, 42, 43, 44, 43, 45)
Last modified November 21, 2025.