python-crash-course-book/chap11_testing.md

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.