173 lines
4.7 KiB
Markdown
173 lines
4.7 KiB
Markdown
|
# 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.
|
||
|
|
||
|
```python
|
||
|
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`
|
||
|
|
||
|
```python
|
||
|
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
|
||
|
|
||
|
```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 feature `assert` 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
|
||
|
|
||
|
* class to be tested in *survey.py* have class `AnonymousSurvey`
|
||
|
|
||
|
```python
|
||
|
class AnonymousSurvey:
|
||
|
"""Collect anonymous answers to a survey questions"""
|
||
|
|
||
|
def __init__(self, question):
|
||
|
"""Store a question, and prepare to store response."""
|
||
|
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}")
|
||
|
```
|
||
|
* script to use `AnonymousSurvey` *language_survey.py*
|
||
|
|
||
|
```python
|
||
|
from survey import AnonymousSurvey
|
||
|
|
||
|
# Define a question, and make a survey.
|
||
|
question = "What language did you first learn to speak?"
|
||
|
my_survey = AnonymousSurvey(question)
|
||
|
|
||
|
# Show the questions, and store responses to the question
|
||
|
my_survey.show_question()
|
||
|
print("Enter 'q' at any time to quit.\n")
|
||
|
while True:
|
||
|
response = input("Language: ")
|
||
|
if response == 'q':
|
||
|
break
|
||
|
my_survey.store_response(response)
|
||
|
|
||
|
# Show the survey results
|
||
|
print("\nThank you to everyone who participate in the survey")
|
||
|
my_survey.show_results()
|
||
|
```
|
||
|
|
||
|
If we want to
|