unittest Basics¶
Introduction to Python's built-in unittest framework.
Creating Test Cases¶
Write test classes with unittest.
import unittest
def add(a, b):
return a + b
class TestMath(unittest.TestCase):
def test_add_positive(self):
self.assertEqual(add(2, 3), 5)
def test_add_negative(self):
self.assertEqual(add(-1, -1), -2)
def test_add_zero(self):
self.assertEqual(add(0, 5), 5)
if __name__ == '__main__':
unittest.main(argv=[''], exit=False, verbosity=2)
test_add_negative (__main__.TestMath) ... ok
test_add_positive (__main__.TestMath) ... ok
test_add_zero (__main__.TestMath) ... ok
setUp and tearDown¶
Use setUp and tearDown for test initialization and cleanup.
import unittest
class TestDatabase(unittest.TestCase):
def setUp(self):
# Run before each test
self.db = {}
print("Setting up test database")
def tearDown(self):
# Run after each test
self.db.clear()
print("Cleaning up database")
def test_insert(self):
self.db['user1'] = 'Alice'
self.assertEqual(self.db['user1'], 'Alice')
def test_delete(self):
self.db['user1'] = 'Bob'
del self.db['user1']
self.assertNotIn('user1', self.db)
if __name__ == '__main__':
unittest.main(argv=[''], exit=False, verbosity=2)
Setting up test database
test_delete (__main__.TestDatabase) ... ok
Cleaning up database
test_insert (__main__.TestDatabase) ... ok
Runnable Example: unittest_basics_tutorial.py¶
"""
04_unittest_basics.py
UNITTEST FRAMEWORK BASICS
==========================
Introduction to Python's built-in unittest framework for structured testing.
Learning Objectives:
- Understand unittest.TestCase class
- Write test methods and test classes
- Run tests with unittest.main()
- Use basic unittest assertions
- Organize tests in test classes
Target: Intermediate Level - Week 3
Prerequisites: 01-03 completed
"""
import unittest
# ============================================================================
# PART 1: INTRODUCTION TO UNITTEST
# ============================================================================
"""
unittest is Python's built-in testing framework, inspired by JUnit (Java).
KEY FEATURES:
- Test organization in classes
- Rich set of assertion methods
- Test fixtures (setUp/tearDown)
- Test discovery
- Test runners
- Built into Python (no installation needed)
BASIC STRUCTURE:
1. Import unittest
2. Create class inheriting from unittest.TestCase
3. Write test methods starting with test_
4. Use self.assert* methods
5. Run with unittest.main()
"""
# ============================================================================
# PART 2: FIRST UNITTEST TEST CASE
# ============================================================================
# Functions to test
def add(a, b):
"""Add two numbers."""
return a + b
def multiply(a, b):
"""Multiply two numbers."""
return a * b
# Test class inheriting from unittest.TestCase
class TestBasicMath(unittest.TestCase):
"""Test basic mathematical operations."""
def test_add_positive_numbers(self):
"""Test adding two positive numbers."""
result = add(2, 3)
self.assertEqual(result, 5)
def test_add_negative_numbers(self):
"""Test adding two negative numbers."""
result = add(-2, -3)
self.assertEqual(result, -5)
def test_multiply_positive_numbers(self):
"""Test multiplying two positive numbers."""
result = multiply(3, 4)
self.assertEqual(result, 12)
def test_multiply_by_zero(self):
"""Test multiplying by zero returns zero."""
result = multiply(5, 0)
self.assertEqual(result, 0)
# ============================================================================
# PART 3: COMMON UNITTEST ASSERTIONS
# ============================================================================
class TestUnittestAssertions(unittest.TestCase):
"""Demonstrate common unittest assertion methods."""
def test_assertEqual(self):
"""Test assertEqual: checks if two values are equal."""
self.assertEqual(1 + 1, 2)
self.assertEqual("hello", "hello")
self.assertEqual([1, 2, 3], [1, 2, 3])
def test_assertNotEqual(self):
"""Test assertNotEqual: checks if two values are different."""
self.assertNotEqual(1, 2)
self.assertNotEqual("hello", "world")
def test_assertTrue_assertFalse(self):
"""Test assertTrue and assertFalse: check boolean values."""
self.assertTrue(True)
self.assertTrue(1 == 1)
self.assertFalse(False)
self.assertFalse(1 == 2)
def test_assertIs_assertIsNot(self):
"""Test assertIs and assertIsNot: check object identity."""
a = [1, 2, 3]
b = a # Same object
c = [1, 2, 3] # Different object, same value
self.assertIs(a, b) # Same object
self.assertIsNot(a, c) # Different objects
def test_assertIsNone_assertIsNotNone(self):
"""Test assertIsNone and assertIsNotNone: check for None."""
value = None
self.assertIsNone(value)
value = "something"
self.assertIsNotNone(value)
def test_assertIn_assertNotIn(self):
"""Test assertIn and assertNotIn: check membership."""
self.assertIn(3, [1, 2, 3, 4])
self.assertIn("apple", ["apple", "banana"])
self.assertNotIn(5, [1, 2, 3, 4])
def test_assertIsInstance(self):
"""Test assertIsInstance: check type."""
self.assertIsInstance(42, int)
self.assertIsInstance("hello", str)
self.assertIsInstance([1, 2], list)
# ============================================================================
# PART 4: TESTING WITH MULTIPLE TEST CLASSES
# ============================================================================
class Calculator:
"""Simple calculator for demonstration."""
def __init__(self):
self.history = []
def add(self, a, b):
"""Add two numbers and record in history."""
result = a + b
self.history.append(f"{a} + {b} = {result}")
return result
def subtract(self, a, b):
"""Subtract b from a and record in history."""
result = a - b
self.history.append(f"{a} - {b} = {result}")
return result
def clear_history(self):
"""Clear calculation history."""
self.history.clear()
class TestCalculatorOperations(unittest.TestCase):
"""Test calculator arithmetic operations."""
def test_addition(self):
"""Test calculator addition."""
calc = Calculator()
self.assertEqual(calc.add(2, 3), 5)
self.assertEqual(calc.add(10, 20), 30)
def test_subtraction(self):
"""Test calculator subtraction."""
calc = Calculator()
self.assertEqual(calc.subtract(5, 3), 2)
self.assertEqual(calc.subtract(10, 20), -10)
class TestCalculatorHistory(unittest.TestCase):
"""Test calculator history functionality."""
def test_history_records_operations(self):
"""Test that operations are recorded in history."""
calc = Calculator()
calc.add(2, 3)
calc.subtract(10, 5)
self.assertEqual(len(calc.history), 2)
self.assertIn("2 + 3 = 5", calc.history)
self.assertIn("10 - 5 = 5", calc.history)
def test_clear_history_empties_list(self):
"""Test that clear_history empties the history list."""
calc = Calculator()
calc.add(1, 1)
calc.clear_history()
self.assertEqual(len(calc.history), 0)
# ============================================================================
# PART 5: TEST METHOD NAMING AND ORGANIZATION
# ============================================================================
"""
TEST METHOD REQUIREMENTS:
- Must start with 'test_'
- Should have descriptive names
- One test, one concept
- Can have docstrings
GOOD TEST NAMES:
✓ test_add_positive_numbers()
✓ test_empty_list_returns_zero()
✓ test_invalid_email_raises_error()
BAD TEST NAMES:
✗ test1() # Not descriptive
✗ testAddition() # Wrong naming convention (camelCase)
✗ check_addition() # Doesn't start with 'test_'
"""
class TestNamingExamples(unittest.TestCase):
"""Examples of good test naming."""
def test_empty_string_has_zero_length(self):
"""Test that empty string has length of zero."""
self.assertEqual(len(""), 0)
def test_list_append_increases_length(self):
"""Test that append increases list length by one."""
my_list = [1, 2, 3]
my_list.append(4)
self.assertEqual(len(my_list), 4)
def test_dict_get_returns_none_for_missing_key(self):
"""Test that dict.get() returns None for missing keys."""
my_dict = {"a": 1}
self.assertIsNone(my_dict.get("b"))
# ============================================================================
# PART 6: RUNNING UNITTEST TESTS
# ============================================================================
"""
WAYS TO RUN UNITTEST TESTS:
1. MODULE EXECUTION (using if __name__ == "__main__")
python 04_unittest_basics.py
2. COMMAND LINE - SINGLE FILE
python -m unittest 04_unittest_basics.py
3. COMMAND LINE - SPECIFIC CLASS
python -m unittest 04_unittest_basics.TestBasicMath
4. COMMAND LINE - SPECIFIC TEST
python -m unittest 04_unittest_basics.TestBasicMath.test_add_positive_numbers
5. TEST DISCOVERY (all test files)
python -m unittest discover
6. VERBOSE OUTPUT
python -m unittest -v 04_unittest_basics.py
"""
# ============================================================================
# PART 7: PRACTICAL EXAMPLE - STRING UTILITIES
# ============================================================================
class StringUtils:
"""Utility functions for string manipulation."""
@staticmethod
def reverse(text):
"""Reverse a string."""
return text[::-1]
@staticmethod
def is_palindrome(text):
"""Check if text is a palindrome."""
clean = text.lower().replace(" ", "")
return clean == clean[::-1]
@staticmethod
def count_vowels(text):
"""Count vowels in text."""
vowels = "aeiouAEIOU"
return sum(1 for char in text if char in vowels)
class TestStringUtils(unittest.TestCase):
"""Test StringUtils class methods."""
def test_reverse_simple_string(self):
"""Test reversing a simple string."""
result = StringUtils.reverse("hello")
self.assertEqual(result, "olleh")
def test_reverse_empty_string(self):
"""Test reversing empty string returns empty string."""
result = StringUtils.reverse("")
self.assertEqual(result, "")
def test_is_palindrome_true_case(self):
"""Test palindrome detection with actual palindrome."""
self.assertTrue(StringUtils.is_palindrome("racecar"))
self.assertTrue(StringUtils.is_palindrome("A man a plan a canal Panama"))
def test_is_palindrome_false_case(self):
"""Test palindrome detection with non-palindrome."""
self.assertFalse(StringUtils.is_palindrome("hello"))
self.assertFalse(StringUtils.is_palindrome("world"))
def test_count_vowels_simple_string(self):
"""Test counting vowels in a simple string."""
count = StringUtils.count_vowels("hello")
self.assertEqual(count, 2) # 'e' and 'o'
def test_count_vowels_no_vowels(self):
"""Test counting vowels in string with no vowels."""
count = StringUtils.count_vowels("gym")
self.assertEqual(count, 0)
# ============================================================================
# PART 8: UNITTEST BEST PRACTICES
# ============================================================================
"""
UNITTEST BEST PRACTICES:
1. ONE CONCEPT PER TEST
✓ test_add_positive_numbers()
✗ test_all_calculator_functions() # Too broad
2. USE DESCRIPTIVE NAMES
✓ test_empty_list_returns_zero_length()
✗ test1()
3. TEST ONE THING AT A TIME
- Each test should verify one specific behavior
- Easier to identify failures
4. INDEPENDENT TESTS
- Tests should not depend on each other
- Should work in any order
5. FAST TESTS
- Unit tests should be fast (milliseconds)
- Slow tests reduce test frequency
6. USE SELF.ASSERT* METHODS
✓ self.assertEqual(a, b)
✗ assert a == b # Don't use raw assert in unittest
7. CLEAR ERROR MESSAGES
- Add msg parameter to assertions when helpful
- self.assertEqual(result, expected, "Addition failed")
"""
class TestBestPracticesExample(unittest.TestCase):
"""Example of following best practices."""
def test_single_concept(self):
"""Each test checks one specific thing."""
result = add(2, 3)
self.assertEqual(result, 5, "2 + 3 should equal 5")
def test_is_independent(self):
"""This test doesn't depend on any other test."""
# Create fresh test data
numbers = [1, 2, 3]
# Test one specific behavior
self.assertEqual(len(numbers), 3)
# ============================================================================
# SUMMARY
# ============================================================================
"""
KEY TAKEAWAYS:
1. unittest provides structured testing framework
2. Tests are organized in classes inheriting from TestCase
3. Test methods must start with 'test_'
4. Use self.assert* methods, not raw assert
5. Multiple test classes can test different aspects
6. Tests should be independent and focused
7. Run with python -m unittest or unittest.main()
UNITTEST vs PLAIN ASSERTIONS:
Plain: assert result == expected
unittest: self.assertEqual(result, expected)
ADVANTAGES OF UNITTEST:
- Better error messages
- Test organization and grouping
- Test fixtures (setUp/tearDown)
- Test discovery and runners
- Integration with CI/CD systems
NEXT STEPS:
- Learn more assertion methods (05_unittest_assertions.py)
- Master test fixtures (06_test_fixtures.py)
- Explore test suites (07_test_suites.py)
"""
if __name__ == "__main__":
# Run all tests in this module
unittest.main(verbosity=2)