Skip to content

Unit Testing Quick Reference

Why Unit Tests?

  • Validation: Automatically check if your code works
  • Specification: Tests show exactly what your function should do
  • Debugging: Failed tests tell you what's wrong
  • Safety net: Catch bugs before submission
  • Professional practice: Real developers write tests first

Basic Structure

Simple Test File Template

import unittest
from your_module import your_function

class TestYourFunction(unittest.TestCase):
    def test_basic_case(self):
        """Test the happy path"""
        result = your_function(input)
        self.assertEqual(result, expected)

    def test_edge_case(self):
        """Test boundary conditions"""
        result = your_function(edge_input)
        self.assertEqual(result, expected)

if __name__ == '__main__':
    unittest.main()

Common Assertions

Assertion Purpose Example
assertEqual(a, b) Check if values are equal self.assertEqual(result, 5)
assertNotEqual(a, b) Check if values differ self.assertNotEqual(x, y)
assertTrue(x) Check if condition is True self.assertTrue(result > 0)
assertFalse(x) Check if condition is False self.assertFalse(result == None)
assertIn(a, b) Check if a is in b self.assertIn("cat", ["cat", "dog"])
assertIsNone(x) Check if value is None self.assertIsNone(result)
assertRaises(Exception, func, args) Check if exception raised self.assertRaises(ValueError, func, bad_arg)

Running Tests

Run all tests in a file:

python -m unittest test_treasure_hunt.py -v

The -v flag shows verbose output (which tests passed/failed)

Run a specific test class:

python -m unittest test_treasure_hunt.TestTreasureHunt -v

Run a specific test method:

python -m unittest test_treasure_hunt.TestTreasureHunt.test_matches_treasures_to_locations -v

Writing Good Tests

1. Test One Thing

Each test should verify one behavior:

# ✅ GOOD - one clear assertion
def test_empty_input(self):
    result = match_treasures_to_locations([], [])
    self.assertEqual(result, [])

# ❌ BAD - testing multiple things
def test_everything(self):
    result1 = match_treasures_to_locations([], [])
    result2 = match_treasures_to_locations([("x", (0,0))], [("y", (0,0))])
    self.assertEqual(result1, [])
    self.assertEqual(result2, [("x", "y")])

2. Clear Test Names

Test names should describe what they test:

# ✅ GOOD - describes what it tests
def test_returns_empty_list_when_no_treasures_match()

# ❌ BAD - vague
def test_function()

3. Arrange-Act-Assert Pattern

def test_matches_treasures_to_locations(self):
    # ARRANGE: Set up test data
    treasures = [("Gold", (0, 0))]
    locations = [("Place", (0, 0))]

    # ACT: Call the function
    result = match_treasures_to_locations(treasures, locations)

    # ASSERT: Check the result
    self.assertEqual(result, [("Gold", "Place")])

4. Test Edge Cases

def test_empty_inputs(self): ...
def test_no_matches_found(self): ...
def test_duplicate_coordinates(self): ...
def test_single_item(self): ...

Debugging with Tests

When a test fails:

  1. Read the error message carefully

    AssertionError: [("Gold", "New York")] != [("Silver", "Paris")]
    
    This tells you your output doesn't match expected
  2. Add print statements to understand flow

    def test_matches(self):
        treasures = [("Gold", (0, 0))]
        locations = [("Place", (0, 0))]
        result = match_treasures_to_locations(treasures, locations)
        print(f"Result: {result}")  # See what you got
        self.assertEqual(result, [("Gold", "Place")])
    
  3. Test simpler cases first

  4. Start with single items
  5. Build to complex cases
  6. Isolate which part fails

  7. Check assumptions

  8. Are you handling coordinates correctly?
  9. Are you comparing the right values?
  10. Are you returning the right type?

Test-Driven Development (TDD)

The recommended workflow:

Step 1: Write Tests (before coding!)

def test_matches_treasures_to_locations(self):
    treasures = [("Gold", (40.741895, -73.989308))]
    locations = [("New York", (40.741895, -73.989308))]
    expected = [("Gold", "New York")]
    self.assertEqual(match_treasures_to_locations(treasures, locations), expected)

Step 2: Run Tests (they fail - that's expected!)

FAIL: test_matches_treasures_to_locations
NameError: name 'match_treasures_to_locations' is not defined

Step 3: Write Minimal Code

def match_treasures_to_locations(treasures, locations):
    return []  # Stub - just enough to run

Step 4: Run Tests Again

FAIL: test_matches_treasures_to_locations
AssertionError: [] != [("Gold", "New York")]

Step 5: Implement Solution

def match_treasures_to_locations(treasures, locations):
    matches = []
    for treasure_name, treasure_coords in treasures:
        for location_name, location_coords in locations:
            if treasure_coords == location_coords:
                matches.append((treasure_name, location_name))
    return matches

Step 6: Run Tests (they pass!)

OK - 1 test passed in 0.002s

Step 7: Add More Tests

Repeat from Step 1 for edge cases


Common Mistakes

❌ Forgetting to import the function

# Missing this:
from treasure_hunt import match_treasures_to_locations

❌ Not running tests before submission

python -m unittest test_treasure_hunt.py  # Do this!

❌ Testing too much in one test

# Separate these into different tests:
def test_everything(self):
    self.assertEqual(...stuff1...)
    self.assertEqual(...stuff2...)
    self.assertEqual(...stuff3...)

❌ Ignoring edge cases

# Remember to test:
- Empty lists
- Single items
- No matches
- All match
- Duplicate data

Resources


Quick Checklist Before Submission

  • I imported my function correctly
  • All my tests run: python -m unittest test_*.py -v
  • Tests pass (all green)
  • I tested edge cases (empty, single item, no matches, etc.)
  • My test file is named test_*.py
  • I included both function.py AND test_function.py in submission
  • My function has clear comments
  • I followed Python naming conventions