Exercises: Testing and Debugging
These exercises build your testing and debugging skills from basic assertions through full TDD workflows. All code should use pytest conventions.
Difficulty Guide: - ⭐ Foundational (5-10 min each) - ⭐⭐ Intermediate (10-20 min each) - ⭐⭐⭐ Challenging (20-40 min each) - ⭐⭐⭐⭐ Advanced/Research (40+ min each)
Part A: Testing Fundamentals ⭐
A.1. Write a function is_even(n) that returns True if n is even, False otherwise. Then write at least three pytest tests for it: one with a positive even number, one with a positive odd number, and one with zero.
A.2. Given this function:
def reverse_string(s):
return s[::-1]
Write five tests that cover: a normal string, an empty string, a single character, a palindrome, and a string with spaces.
A.3. Explain in your own words the difference between a test case and a test suite. Give an example of each in the context of testing a calculate_tip(bill_amount, tip_percent) function.
A.4. What's wrong with this test? (There are two problems.)
def test_addition():
result = 2 + 3
print(result)
A.5. Convert this unittest-style test to pytest style:
import unittest
class TestStringMethods(unittest.TestCase):
def test_upper(self):
self.assertEqual("hello".upper(), "HELLO")
def test_isupper(self):
self.assertTrue("HELLO".isupper())
self.assertFalse("Hello".isupper())
def test_split(self):
self.assertEqual("a,b,c".split(","), ["a", "b", "c"])
A.6. Write a test that verifies int("abc") raises a ValueError. Use pytest.raises().
Part B: Test Design and Edge Cases ⭐⭐
B.1. Write a function clamp(value, low, high) that restricts a number to a range:
- If value < low, return low
- If value > high, return high
- Otherwise, return value
Write at least six tests: value below range, value above range, value in range, value exactly at low boundary, value exactly at high boundary, and low == high.
B.2. Here is a function. Write a complete test suite (at least 8 tests) covering normal cases, edge cases, and error cases:
def count_vowels(text):
"""Count the number of vowels (a, e, i, o, u) in text. Case-insensitive."""
return sum(1 for char in text.lower() if char in "aeiou")
B.3. Write a function validate_password(password) that returns True if the password:
- Is at least 8 characters long
- Contains at least one uppercase letter
- Contains at least one lowercase letter
- Contains at least one digit
Use TDD: write the tests first, then implement the function. Show your Red-Green-Refactor steps.
B.4. For each of the following functions, list at least four edge cases you would test (you don't need to write the tests — just describe them):
a) find_max(numbers) — returns the largest number in a list
b) remove_duplicates(items) — returns a list with duplicates removed
c) split_name(full_name) — splits "First Last" into a tuple ("First", "Last")
d) calculate_bmi(weight_kg, height_m) — computes body mass index
B.5. The following test passes but is a bad test. Explain why:
def test_sort():
data = [1, 2, 3, 4, 5]
result = sorted(data)
assert result == [1, 2, 3, 4, 5]
Rewrite it to be a meaningful test.
B.6. Write a function fizzbuzz(n) that returns:
- "FizzBuzz" if n is divisible by both 3 and 5
- "Fizz" if n is divisible by 3 only
- "Buzz" if n is divisible by 5 only
- str(n) otherwise
Write tests that cover all four cases plus edge cases (0, negative numbers, large numbers).
Part C: TDD Practice ⭐⭐-⭐⭐⭐
C.1. Use TDD to build a WordCounter (just a group of functions — no classes yet):
a) count_words(text) — returns the number of words in a string (split by whitespace)
b) most_common_word(text) — returns the most frequently occurring word (case-insensitive)
c) word_frequencies(text) — returns a dict mapping each word to its count
For each function, write at least 3 tests first, then implement. Show which tests you wrote and in what order.
C.2. Use TDD to build a temperature_converter module with:
a) celsius_to_fahrenheit(c) — the formula is F = C * 9/5 + 32
b) fahrenheit_to_celsius(f) — the formula is C = (F - 32) * 5/9
c) is_freezing(temp, scale="C") — returns True if the temperature is at or below the freezing point of water
Write tests for known conversions (0°C = 32°F, 100°C = 212°F, -40°C = -40°F), edge cases, and invalid inputs (like scale="X").
C.3. A colleague hands you this function and says "it works." Use TDD to write a test suite that proves it has bugs, then fix the bugs:
def calculate_shipping(weight, destination):
"""Calculate shipping cost.
Domestic: $5 base + $0.50 per pound
International: $15 base + $1.50 per pound
Free shipping for domestic orders over 50 pounds.
"""
if destination == "domestic":
if weight > 50:
return 0
return 5 + weight * 0.50
elif destination == "international":
return 15 + weight * 1.50
Hint: What happens with weight=0? Weight=-5? destination="Domestic" (capitalized)? What does the function return if destination is "mars"?
Part D: Fixtures and Test Organization ⭐⭐⭐
D.1. Write a pytest fixture called student_records that returns a list of 5 student dictionaries, each with keys "name", "scores" (list of ints), and "grade" (string). Then write three tests that use this fixture:
- Test that all records have the required keys
- Test that all scores are between 0 and 100
- Test that the number of records is correct
D.2. Write a fixture that creates a temporary CSV file with this content:
name,score,grade
Alice,95,A
Bob,82,B
Charlie,67,D
Then write tests that: a) Verify the file has 3 data rows (not counting the header) b) Read the CSV and verify Alice's score is 95 c) Verify all grades are valid (A, B, C, D, or F)
D.3. Refactor the following tests to use a shared fixture. The fixture should provide a BankAccount-like dictionary with a starting balance:
def test_deposit():
account = {"owner": "Alice", "balance": 100.0}
account["balance"] += 50
assert account["balance"] == 150.0
def test_withdraw():
account = {"owner": "Alice", "balance": 100.0}
account["balance"] -= 30
assert account["balance"] == 70.0
def test_insufficient_funds():
account = {"owner": "Alice", "balance": 100.0}
if account["balance"] < 200:
result = "insufficient"
assert result == "insufficient"
Part E: Debugging Challenges ⭐⭐⭐
E.1. The following function is supposed to return the nth Fibonacci number (0, 1, 1, 2, 3, 5, 8, ...). It has a bug. Find and fix it using print debugging — show the print statements you added and what they revealed:
def fibonacci(n):
if n <= 0:
return 0
if n == 1:
return 1
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
E.2. This function is supposed to remove all occurrences of a value from a list. It sometimes doesn't remove all of them. Find the bug:
def remove_all(lst, value):
for i in range(len(lst)):
if lst[i] == value:
lst.pop(i)
return lst
Write a test that demonstrates the failure, then fix the function and verify your fix passes.
E.3. Elena's report script calculates year-over-year growth rates. The function below produces incorrect results for some inputs. Use the binary search debugging strategy: describe at which step you'd place your first check, what you'd look for, and narrow down to the bug.
def calculate_growth_rates(yearly_data):
"""Given a dict of {year: revenue}, return {year: growth_rate}."""
growth = {}
years = sorted(yearly_data.keys())
for i in range(1, len(years)):
current = yearly_data[years[i]]
previous = yearly_data[years[i - 1]]
rate = (current - previous) / current * 100
growth[years[i]] = round(rate, 2)
return growth
Part F: Advanced Testing ⭐⭐⭐⭐
F.1. Write a parametrized test using @pytest.mark.parametrize that tests a letter_grade(score) function with at least 10 different score/grade pairs in a single test function.
F.2. Write a complete test suite (at least 12 tests) for a ShoppingCart represented as a list of dicts:
# Each item: {"name": "Widget", "price": 9.99, "quantity": 2}
Test functions for: add_item(), remove_item(), get_total(), apply_discount(), get_item_count(). Include edge cases: empty cart, removing item not in cart, 100% discount, negative quantity.
F.3. Research the concept of mocking in testing. Write a short explanation (3-5 sentences) of what a mock is, why you'd use one, and give one concrete example where mocking would be necessary in TaskFlow (e.g., testing a function that reads the current date/time).
F.4. Write tests for a function that reads configuration from a JSON file. Use the tmp_path fixture and monkeypatch (pytest's built-in mock) to test:
- Normal config loading
- Missing file behavior
- Malformed JSON behavior
- Missing required keys in the config