217 lines
6.6 KiB
Markdown
217 lines
6.6 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
|
|
|
|
Steps to write test case:
|
|
|
|
1. Importing `unittest` module and class to test
|
|
2. Creating a test class to inherit from `unittest.TestCase`
|
|
3. Creating test function under class
|
|
1. Create an instance of the class to be tested.
|
|
2. Verify its operation
|
|
|
|
e.g.
|
|
|
|
* class to be tested in [survey.py](src/chap11/survey.py) have class `AnonymousSurvey`
|
|
* script to use class [language_survey](pcc_2e/chapter_11/language_survey.py)
|
|
* test script as shown below:
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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. |