This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Quizzes and Exams

Quiz and exam info will appear on this page.

1 - Quiz 2 sample problems

Sample long-form problems for Quiz 2

You are getting the first edition of all these pages. Please let me know if you find an error!

In class Oct 8

  • Study key terms from slides and labs.

  • Study Knowledge Checks from labs.

  • Emphasis on:

  • Repeat questions from previous quiz are on the table. No CLI.

  • You may use your own written notes during the quiz.

  • Multiple choice, fill-in-the-blank, long answer.

Sample CFG and Testing problems

You will be asked to draw CFGs and write test cases that cover all program paths.

Disclaimer: You will have other knowledge check questions beyond these types of questions.

Sample 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
  1. Draw the CFG for this code using the conventions from class.
  2. List the unique program paths.
  3. Add assert statements to the following test case that exercise all unique program paths.
    def test_is_prime():
       # Your code here.
    

Solution

CFG for is_prime()
unique program paths

The unique edges in the path are highlighted. You do not need to highlight the unique edges on the quiz.

  1. (1, 2, 3)
  2. (1, 2, 4, 7)
  3. (1, 2, 4, 5, 6)
  4. (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)

Sample 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
  1. Draw the CFG for this code using the conventions from class.
  2. List the unique program paths.
  3. Add assert statements to the following test case that exercise all unique program paths.
    def test_generate_fibonacci():
       # Your code here.
    

Solution

CFG for generate_fibonacci()
unique program paths

The unique edges in the path are highlighted. You do not need to highlight the unique edges on the quiz.

  1. (14, 15, 16)
  2. (14, 15, 18-21, 23, 29)
  3. (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)

Sample 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
  1. Draw the CFG for this code using the conventions from class.
  2. List the unique program paths.
  3. Add assert statements to the following test case that exercise all unique program paths.
    def test_factorial():
       # Your code here.
    

Solution

CFG for factorial()
unique program paths

The unique edges in the path are highlighted. You do not need to highlight the unique edges on the quiz.

  1. (36, 38, 39)
  2. (36, 38, 42, 43, 45)
  3. (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)

2 - Quiz 3 - Design Rules and Version Control

Study guide for Quiz 3

In class November 19

Sample Git Repo state questions

Git analysis - sample 1

Consider the following depictions of a Git repository’s state:

Repo state

Conceptual branch history

  1. What is the current active branch?
  2. How many versions will running the command git log show?
  3. How many versions will running the command git log show after running git checkout main?
  4. Suppose the following changes and commands are made in order:
    1. git checkout main
    2. Add some lines to app.py.
    3. git add .
    4. git commit -m "added new functions" Update both the Repo state diagram and the Conceptual branch history diagram to reflect the changes.
Solution for Git sample 1
  1. The current active branch is feature-1, as indicated by the HEAD
  2. git log runs in the active branch by default, and feature-1 has 3 versions.
  3. git log in the main branch will shown only the first version.
Repo state diagram after adding the new version to main
Branch history diagram after adding the new version to main

Git analysis - sample 2

Consider the repo state above as the starting point for each question. Briefly explain the impact of running the following commands in terms of the versions, head, and the workspace files.

  1. git reset .
  2. git reset --hard HEAD.
  3. git reset --hard HEAD~1.
Solution for Git sample 2
  1. The changes to main.py and hello.py will be unstaged (removed from the Index), but will still be present in the workspace.
  2. We discard any changes since the most recent version:
    • All changes will be unstaged.
    • main.py will be replaced with its version from b424cc.
    • hello.py will be as-is in the workspace because it is untracked, meaning it has not yet been added to version control. You can tell that hello.py is untracked because it does not exist in any version. Running git status would tell you that it is untracked.
    • The HEAD will continue to point to version b424cc.
  3. All tracked files in the workspace and the local repository are reset to version 8356ea. Essentially, we are resetting to the point before the most recent version.
    • README.md will be removed from the workspace.
    • main.py will be replaced with its version from 8356ea.
    • Version b424cc will be deleted from the Local Repository.
    • hello.py will be as-is in the workspace because it is untracked.
    • The HEAD will now point to version 8356ea.

Sample low-level design rule questions

Design critique - sample 1

Consider this simple Python program:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def process_order(product, quantity):
    if product == "apple":
        price = 1.0
    elif product == "banana":
        price = 0.5
    else:
        price = 2.0

    if quantity > 0:
        total_cost = price * quantity
        print(f"The total cost for {quantity} {product}(s) is ${total_cost}")
    else:
        print("Quantity must be positive")

Identify which of the 6 low-level design rules this program violates and why. Suggest how these violations might be addressed.

Solution for Sample 1

You could potentially argue for other design rule violations, or a violation in rule of the ones below. In my mind, the rules below are clearly violated.

  1. Violation of “Separate input/output logic from business logic”:
    • Issue: The function process_order mixes calculating the total cost with printing output directly to the console.
    • Solution: Separate the calculation logic into a distinct function and handle input/output operations elsewhere.
  2. Violation of “Avoid magic literals”:
    • Issue: The prices 1.0, 0.5, and 2.0 are hardcoded directly into the function, making them “magic” numbers without context.
    • Solution: Use named constants for these values. They could be placed into a dictionary, for example.
  3. Violation of “Handle errors at the lowest sensible level, and re-raise/re-throw them otherwise”:
    • Issue: The function checks for positive quantities but does not raise a specific error.
    • Solution: Use exception handling to raise a specific error when quantity is non-positive.

Design critique - sample 2

Consider this Python code:

 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
46
class OrderProcessor:
    def __init__(self):
        self.orders = []

    def add_order(self, product, quantity, price):
        self.orders.append({"product": product, "quantity": quantity, "price": price})

    def remove_order(self, product):
        for order in self.orders:
            if order["product"] == product:
                self.orders.remove(order)
                print(f"Order for {product} removed.")
                return
        print(f"No order found for {product}.")

    def process_orders(self):
        total_cost = 0
        for order in self.orders:
            total = order["quantity"] * order["price"]
            total_cost += total
            print(f"Processing order for {order['quantity']} {order['product']}(s) at ${order['price']} each. Total: ${total}")
        print(f"Total cost of all orders: ${total_cost}")

    def print_summary(self):
        print("Order Summary:")
        for order in self.orders:
            total = order["quantity"] * order["price"]
            print(f"{order['quantity']} {order['product']}(s) - ${order['price']} each - Total: ${total}")

    def find_order(self, product):
        for order in self.orders:
            if order["product"] == product:
                total = order["quantity"] * order["price"]
                print(f"Found order: {order['quantity']} {order['product']}(s) at ${order['price']} each. Total: ${total}")
                return
        print(f"No order found for {product}.")

    def update_order(self, product, new_quantity, new_price):
        for order in self.orders:
            if order["product"] == product:
                order["quantity"] = new_quantity
                order["price"] = new_price
                total = new_quantity * new_price
                print(f"Updated order for {product}: {new_quantity} units at ${new_price} each. New total: ${total}")
                return
        print(f"No order found for {product}.")
Solution for sample 2

You could potentially argue for other design rule violations, or a violation in rule of the ones below. In my mind, the rules below are clearly violated.

  1. Violation of “Single Responsibility Rule”:
    • Issue: The OrderProcessor class handles multiple responsibilities: it maintains the list of orders, processes them, and prints order summaries.
    • Solution: Split responsibilities into dedicated classes, such as OrderManager for managing orders, OrderProcessor for processing, etc.
  2. Violation of “DRY (Don’t Repeat Yourself)”:
    • Issue: The calculation total = order["quantity"] * order["price"] is repeated across multiple methods (process_orders, print_summary, find_order, and update_order).
    • Solution: Create a helper method to calculate the total for a given order to avoid repeating this logic.
  3. Violation of “Avoid Magic Literals”:
    • Issue: The product-related attributes such as “product”, “quantity”, and “price” are string literals used throughout the class. If any of these strings are mistyped, or if there is a change in the attribute names, the program may fail without easy traceability.
    • Solution: Use constants or, better yet, use a dedicated Order class with attributes for product, quantity, and price to encapsulate these values.
  4. Violation of “Handle errors at the lowest sensible level, and re-raise/re-throw them otherwise”:
    • Issue: When removing or updating orders, the methods simply print messages if the order is not found rather than handling the error in a structured manner (e.g., raising an exception or returning an error code).
    • Solution: Raise specific exceptions like OrderNotFoundError if an order is not found, and allow higher-level methods to decide how to handle them.
  5. Violation of “Raise specific errors and define custom errors if needed”:
    • Issue: No custom error handling is used for operations that could logically fail (e.g., removing or finding non-existent orders). The use of print statements for error messages is not a robust approach.
    • Solution: Define custom exceptions such as OrderNotFoundError or InvalidOrderError to handle various conditions gracefully.

3 - Final Exam

Study guide for the Final Exam

Where and When

  • Where: Congdon Hall 2055
  • When: Thursday, December 12, 3pm-6pm
    • The exam is not designed to be 3 hours long, but you will have the entire 3 hours at your disposal.
  • Other
    • Please eat a snack before the exam. Eating in the lab is not permitted.
    • Bring a water bottle.
    • You may listen to music using headphones/earbuds at a volume that does not disturb others.

Exam Format and Rules

  • A mix of multiple choice, fill-in-the-blank, and long answer questions.
  • You will not be programming in Visual Studio Code, but you may be asked to write or edit code snippets by hand.
  • You may use your own hand-written notes, class worksheets (but not Git or CLI cheatsheets), and scrap paper. No other resources are permitted.
  • Honor Code violations on the final exam result in a course grade of F.
  • Failure to submit the final exam results in a course grade of F.

Content

Review the three quizzes: