testing
Objectives
- Describe what tests are and why they are essential
- Explain what Test Driven Development is
- Test Python code using doctests
- Test Python code using assert
- Explain what unit testing is
- Write unit tests using the unittest module
- Remove code duplication using before and after hooks
Why test?
Why test?
- Reduce bugs in existing code
- Ensure bugs that are fixed stay fixed
- Ensure that new features don't break old ones
- Ensure that cleaning up code doesn't introduce new bugs
- Makes development more fun!
Test Driven Development
- Development begins by writing tests
- Once tests are written, write code to make tests pass
- Once tests pass, a feature is considered complete
Red, Green, Refactor
- Red - Write a test that fails
- Green - Write the minimal amount of code necessary to make the test pass
- Refactor - Clean up the code, while ensuring that tests still pass
Assertions
Assertions
- We can make simple assertions with the
assert
keyword -
assert
accepts an expression - Returns
None
if the expression is truthy - Raises an
AssertionError
if the expression is falsy - Accepts an optional error message as a second argument
Assertions Example
def add_positive_numbers(x, y):
assert x > 0 and y > 0, "Both numbers must be positive!"
return x + y
add_positive_numbers(1, 1) # 2
add_positive_numbers(1, -1) # AssertionError: Both numbers must be positive!
Assertions Warning
If a Python file is run with the -O flag, assertions will not be evaluated!
# Don't write code like this!
def do_something_bad(user):
assert user.is_admin, "Only admins can do bad things!"
destroy_a_bunch_of_stuff()
return "Mua ha ha ha!"
doctests
doctests
- We can write tests for functions inside of the docstring
- Write code that looks like it's inside of a REPL
doctests Example
def add(x, y):
"""add together x and y
>>> add(1, 2)
3
>>> add(8, "hi")
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'int' and 'str'
"""
Run these tests with:
python3 -m doctest -v YOUR_FILE_NAME.py
Test should fail at first - remember "Red, Green, Refactor"
Issues with doctests
- Syntax is a little strange
- Clutters up our function code
- Lacks many features of larger testing tools
- Tests can be brittle
YOUR TURN
Introduction to
unittest
Unit testing
- Test smallest parts of an application in isolation (e.g. units)
- Good candidates for unit testing: individual classes, modules, or functions
- Bad candidates for unit testing: an entire application, dependencies across several classes or modules
unittest
- Python comes with a built-in module called
unittest
- You can write unit tests encapsulated as classes that inherit from
unittest.TestCase
- This inheritance gives you access to many assertion helpers that let you test the behavior of your functions
- You can run tests by calling
unittest.main()
unittest
Example
import unittest
from activities import eat, nap
class ActivityTests(unittest.TestCase):
pass
if __name__ == "__main__":
unittest.main()
tests.py
def eat(food, is_healthy):
pass
def nap(num_hours):
pass
activities.py
Commenting Tests
tests.py
class SomeTests(unittest.TestCase):
def first_test(self):
"""testing a thing"""
self.assertEqual(thing(), "something")
def second_test(self):
"""testing another thing"""
self.assertEqual(another_thing(), "something else")
To see comments, run
python NAME_OF_TEST_FILE.py -v
-
self.assertEqual(x, y)
-
self.assertNotEqual(x, y)
-
self.assertTrue(x)
-
self.assertFalse(x)
-
self.assertIsNone(x)
-
self.assertIsNotNone(x)
-
self.assertIn(x, y)
-
self.assertNotIn(x, y)
- ...and more!
Types of Assertions
class SomeTests(unittest.TestCase):
def testing_for_error(self):
"""testing for an error"""
with self.assertRaises(IndexError):
l = [1,2,3]
l[100]
Testing for Errors
Before and After Hooks
setUp and tearDown
- For larger applications, you may want similar application state before running tests
- setUp runs before each test method
- tearDown runs after each test method
- Common use cases: adding/removing data from a test database, creating instances of a class
Example
class SomeTests(unittest.TestCase):
def setUp(self):
# do setup here
pass
def test_first(self):
# setUp runs before
# tearDown runs after
pass
def test_second(self):
# setUp runs before
# tearDown runs after
pass
def tearDown(self):
# do teardown here
pass
Recap
- Tests help streamline development and reduce bugs
- You can start with tests if doing TDD / Red, Green, Refactor
- You can perform simple checks with
assert
- You can test with doctests, but typically shouldn't
-
unittest
is a feature-rich, OOP style testing library in Python - To reduce code duplication in tests, use before/after hooks!
YOUR TURN
Testing with unittest
By colt
Testing with unittest
- 3,156