6.6 KiB
Chapter 11. Testing Your Code
Learn how to test code using Python's unittest
module.
Testing a Function
Function to be tested as shown below.
def get_formatted_name(first, last):
"""Generate a neatly formatted full name."""
full_name = f"{first} {last}"
return full_name.title()
A script that should use get_formatted_name
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}.")
To make sure get_formatted_name
function properly, using unittest
module of Python
import unittest
from name_function import get_formatted_name
class NamesTestCase(unittest.TestCase):
"""Test 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()
Key points:
- Import
unittest
module as shown - Import function to be tested
- Here,
unittest
's most useful featureassert
is used.assertEqual
can compare two objects __name__
is a special variable, which is set when the program is executed. If the file is being run as the main program, the value of__name__
is set to'__main__'
Result of running this test:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
- First dot represent the single test passed.
A Failing Test
Failure case returns
F
======================================================================
FAIL: test_first_last_name (__main__.NamesTestCase)
Do names like 'Janis Joplin' work?
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_name_function.py", line 10, in test_first_last_name
self.assertEqual(formatted_name, 'Janis Joplins')
AssertionError: 'Janis Joplin' != 'Janis Joplins'
- Janis Joplin
+ Janis Joplins
? +
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
Highlight:
FAIL: test_first_last_name
tell's where the error coming from.
Fix them
If employing TDD, we can first create test case, then develop code to be test against.
Testing a Class
e.g. above shown how to write test case for single function, we also need to write test for class.
A Variety of Assert Methods
Python unittest
module provide various assert methods in unittest.TestCase
class.
List of available assert methods
Method | Use |
---|---|
assertEqual(a,b) |
Verify a == b |
assertNotEqual(a,b) |
Verify that a != b |
assertTrue(x) |
Verify that x is True |
assertFalse(x) |
Verify that x is False |
assertIn(item, list) |
Verify that item is in list |
assertNotIn(item, list) |
Verrify that item is not in list |
A Class to Test
Steps to write test case:
- Importing
unittest
module and class to test - Creating a test class to inherit from
unittest.TestCase
- Creating test function under class
- Create an instance of the class to be tested.
- Verify its operation
e.g.
- class to be tested in survey.py have class
AnonymousSurvey
- script to use class language_survey
- test script as shown below:
import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
"""Tests for the class AnonymouseSurvey"""
def test_store_single_response(self):
"""Tests 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']
# Store responses
for response in responses:
my_survey.store_response(response)
# Test my_survey operation
for response in responses:
self.assertIn(response, my_survey.responses)
if __name__ == '__main__':
unittest.main()
We can add more test functions to test different scenario. But it's too repetitive. Use setUp()
The setUp() Method
We can use setUp()
to setup initial states of self.my_survey
, which can reduce repetitions.
import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
"""Tests for the class AnonymouseSurvey"""
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 test 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()
- Use
setUp()
can make test methods easier to write. - We can make one set of instances and attributes in
setUp()
and then use these instances in all your test methods.
Note of unittest
- When a test case is running, Python prints one character for each unit test as it's completed.
- A passing test prints a dot
.
- A test that results in an error prints an
E
- A test that results in failed assertion prints an
F
. - Hence, we can see a different number of dots and characters on the first line of output when you run your test cases.