Testing Your Code
When you write a function or a class, you can also write tests for that code. Testing proves that your code works as it's supposed to in response to all the input types it's designed to receive.
Testing a Function
A Function to Test
Let's start with a simple function that takes in a first and last name and returns a neatly formatted full name:
# name_function.py
def get_formatted_name(first, last):
"""Generate a neatly formatted full name."""
full_name = f"{first} {last}"
return full_name.title()
Here's a program that uses this function:
# names.py
from name_function import get_formatted_name
print("Enter 'q' at any time to quit.")
while True:
first = input("\nPlease give me a first name: ")
if first == 'q':
break
last = input("Please give me a last name: ")
if last == 'q':
break
formatted_name = get_formatted_name(first, last)
print(f"\tNeatly formatted name: {formatted_name}.")
Unit Tests and Test Cases
Python's unittest
module provides tools for testing your code. A unit test verifies that one specific aspect of a function's behavior is correct. A test case is a collection of unit tests that together prove that a function behaves as it's supposed to within the full range of situations you expect it to handle.
A Passing Test
Here's a test case for get_formatted_name()
:
# test_name_function.py
import unittest
from name_function import get_formatted_name
class NamesTestCase(unittest.TestCase):
"""Tests for 'name_function.py'."""
def test_first_last_name(self):
"""Do names like 'Janis Joplin' work?"""
formatted_name = get_formatted_name('janis', 'joplin')
self.assertEqual(formatted_name, 'Janis Joplin')
if __name__ == '__main__':
unittest.main()
Output:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Key Components of a Test
- Import unittest and the function to test
- Create a class that inherits from
unittest.TestCase
- Write test methods that start with
test_
- Use assertion methods to verify expected behavior
- Include
unittest.main()
to run the tests
A Failing Test
Let's modify our function to handle middle names:
# name_function.py (modified)
def get_formatted_name(first, last, middle=''):
"""Generate a neatly formatted full name."""
if middle:
full_name = f"{first} {middle} {last}"
else:
full_name = f"{first} {last}"
return full_name.title()
Now let's add a test for middle names:
# test_name_function.py (with failing test)
import unittest
from name_function import get_formatted_name
class NamesTestCase(unittest.TestCase):
"""Tests for 'name_function.py'."""
def test_first_last_name(self):
"""Do names like 'Janis Joplin' work?"""
formatted_name = get_formatted_name('janis', 'joplin')
self.assertEqual(formatted_name, 'Janis Joplin')
def test_first_middle_last_name(self):
"""Do names like 'Wolfgang Amadeus Mozart' work?"""
formatted_name = get_formatted_name('wolfgang', 'mozart', 'amadeus')
self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart')
if __name__ == '__main__':
unittest.main()
Testing a Class
A Class to Test
Here's a class that models a survey:
# survey.py
class AnonymousSurvey:
"""Collect anonymous answers to a survey question."""
def __init__(self, question):
"""Store a question, and prepare to store responses."""
self.question = question
self.responses = []
def show_question(self):
"""Show the survey question."""
print(self.question)
def store_response(self, new_response):
"""Store a single response to the survey."""
self.responses.append(new_response)
def show_results(self):
"""Show all the responses that have been given."""
print("Survey results:")
for response in self.responses:
print(f"- {response}")
Testing the AnonymousSurvey Class
# test_survey.py
import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
"""Tests for the class AnonymousSurvey."""
def test_store_single_response(self):
"""Test that a single response is stored properly."""
question = "What language did you first learn to speak?"
my_survey = AnonymousSurvey(question)
my_survey.store_response('English')
self.assertIn('English', my_survey.responses)
def test_store_three_responses(self):
"""Test that three individual responses are stored properly."""
question = "What language did you first learn to speak?"
my_survey = AnonymousSurvey(question)
responses = ['English', 'Spanish', 'Mandarin']
for response in responses:
my_survey.store_response(response)
for response in responses:
self.assertIn(response, my_survey.responses)
if __name__ == '__main__':
unittest.main()
The setUp() Method
The unittest.TestCase
class has a setUp()
method that allows you to create objects once and use them in each of your test methods:
# test_survey.py (improved)
import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
"""Tests for the class AnonymousSurvey."""
def setUp(self):
"""
Create a survey and a set of responses for use in all test methods.
"""
question = "What language did you first learn to speak?"
self.my_survey = AnonymousSurvey(question)
self.responses = ['English', 'Spanish', 'Mandarin']
def test_store_single_response(self):
"""Test that a single response is stored properly."""
self.my_survey.store_response(self.responses[0])
self.assertIn(self.responses[0], self.my_survey.responses)
def test_store_three_responses(self):
"""Test that three individual responses are stored properly."""
for response in self.responses:
self.my_survey.store_response(response)
for response in self.responses:
self.assertIn(response, self.my_survey.responses)
if __name__ == '__main__':
unittest.main()
Assert Methods
The unittest module provides many assert methods to test different conditions:
Method | Use |
---|---|
assertEqual(a, b) | Verify that a == b |
assertNotEqual(a, b) | Verify that a != b |
assertTrue(x) | Verify that x is True |
assertFalse(x) | Verify that x is False |
assertIs(a, b) | Verify that a is b |
assertIsNot(a, b) | Verify that a is not b |
assertIsNone(x) | Verify that x is None |
assertIsNotNone(x) | Verify that x is not None |
assertIn(item, list) | Verify that item is in list |
assertNotIn(item, list) | Verify that item is not in list |
assertIsInstance(a, b) | Verify that isinstance(a, b) |
assertNotIsInstance(a, b) | Verify that not isinstance(a, b) |
Testing Guidelines
When to Write Tests
When you're starting out, write tests for the most critical behaviors of your functions and classes. As you become more experienced, you'll write them more regularly.
Testing Best Practices
- Test one thing at a time: Each test method should test one specific behavior
- Use descriptive test names: Your test method names should clearly indicate what they're testing
- Keep tests simple: Tests should be easy to understand and maintain
- Test edge cases: Don't just test typical inputs; test unusual cases too
- Use setUp() for common setup: If multiple tests need the same setup, use the setUp() method
What to Test
Good candidates for testing include:
- Functions with complex logic
- Functions that handle user input
- Functions that process data from external sources
- Edge cases and error conditions
- Critical business logic
Example: Testing Edge Cases
# calculator.py
def divide(a, b):
"""Divide a by b."""
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
# test_calculator.py
import unittest
from calculator import divide
class TestCalculator(unittest.TestCase):
"""Tests for calculator functions."""
def test_divide_positive_numbers(self):
"""Test dividing positive numbers."""
result = divide(10, 2)
self.assertEqual(result, 5)
def test_divide_negative_numbers(self):
"""Test dividing negative numbers."""
result = divide(-10, 2)
self.assertEqual(result, -5)
def test_divide_by_zero(self):
"""Test that dividing by zero raises ValueError."""
with self.assertRaises(ValueError):
divide(10, 0)
def test_divide_floats(self):
"""Test dividing floating point numbers."""
result = divide(7.5, 2.5)
self.assertEqual(result, 3.0)
if __name__ == '__main__':
unittest.main()
Running Tests
You can run your tests in several ways:
# Run a specific test file
python test_name_function.py
# Run all tests in a directory
python -m unittest discover
# Run tests with more verbose output
python -m unittest -v test_name_function.py
Summary
In this chapter you learned how to:
- Write simple tests using Python's unittest module
- Use assert methods to verify that your functions and classes work correctly
- Use the setUp() method to create objects that can be used in multiple test methods
- Test both functions and classes
- Understand when and what to test
Testing your code is essential for building reliable software. When you write tests, you can be confident that your code will work correctly even as you make changes and improvements to your programs. Testing gives you the freedom to improve your code without worrying about accidentally breaking existing functionality.