Write Better Unit Test
A single unittest case should have:
- Cover only one path through code.
- Have better asserts and documentation.
- Providing informative failure messages.
To avoid reinventing the wheel, using test fixtures:
test fixture: represents the preparation needed to perform on or more tests, and any associated cleanup actions. This may involve, for example, creating temporary or proxy databases, directories, or starting a server process.
graph TD; setUpModule --> setUpClass; setUpClass --> setUp; setUp --> test_*; test_* --> tearDown; tearDown --> setUp; tearDown --> tearDownClass; tearDownClass --> setUpClass; tearDownClass --> tearDownModule;
Write Testable Code
Some important techniques to make code easier to test:
- Documentation.
- Dependencies.
- Decomposition.
- Graceful and informative failure.
Google Blog Writing Testable Code.
Dependency Replacement
Test Double: A simplified replacement for any dependency of a system under test.
You should use test doubles if the real thing:
- Isn’t available
- Won’t return the results needed
- Would have undesirable side effects
- Would be too slow
Types of test doubles:
What it does | When to use | |
---|---|---|
Placeholder | Does nothing. Passed around but never used |
You need a placeholder. |
Stub | Provides canned answers. | You want the same result every time. |
Spy | A stub that remembers how it was called. |
You want to verify functions were called the right way. |
Mock | Configurable mimic of a particular object. |
Can behave like a dummy, stub, or spy. |
Fake | A simplified version of the real thing |
Interacting with a complicated system. |
Other Fakes
You can find on Internet such as MySQL fake, etc.
Unittest Module
Python unittest module: The python unittest module: https://docs.python.org/3/library/unittest.html#module-unittest
Basic example: https://docs.python.org/3/library/unittest.html#basic-example
How to organize the code: setUp()
/tearDown()
for each test case:
https://docs.python.org/3/library/unittest.html#organizing-test-code
Also it has class and module level fixtures: https://docs.python.org/3/library/unittest.html#class-and-module-fixtures
Assertion methods: https://docs.python.org/3/library/unittest.html#assert-methods
Assert methods allow custom messages, for example:
1 | self.assertEqual( |
Command-Line interface: https://docs.python.org/3/library/unittest.html#command-line-interface
1 | # see list of options |
test_module
is a python file, such as my_module.py
and my_module
can be
imported by other python programs.
Unittest Parameterized
Parameterized module, installation and examples: https://github.com/wolever/parameterized
When using with mock.patch
decorator, the order matters, for example:
https://github.com/wolever/parameterized?tab=readme-ov-file#using-with-mockpatch
1 | import unittest |
Unittest Mock
Unittest mock library, it is extremely important: https://docs.python.org/3.8/library/unittest.mock.html#
Usually Mock, MagicMock and patch decorator are enough for most cases, please check quick guide for quick onboarding: https://docs.python.org/3.8/library/unittest.mock.html#quick-guide
There is a separate document for using mock, it is more advanced: https://docs.python.org/3.8/library/unittest.mock-examples.html#
A mock object can pretend to be anything. It can:
- Return expected values when called.
- Keep track of how it was called.
- Have attributes.
- Call other functions.
- Raise exceptions by side effect.
A Mock object can have assertion for how it has been used, for example:
1 | mock = Mock() |
The mock assert methods are under Mock class with examples: https://docs.python.org/3.8/library/unittest.mock.html#the-mock-class
Some other examples:
1 | m = mock.Mock() |
Mock vs Magic Mock
Basically, MagicMock
is a subclass of Mock with default implementations of most of the magic methods
such as __init__
. You can use MagicMock without having to configure the magic
methods yourself.
Return Valud Vs Side Effect
The return_value
is static, but side_effect
is versatile, it can configure not
only return value dynamically but also arising exception, please see examples:
https://docs.python.org/3.8/library/unittest.mock.html#unittest.mock.Mock.side_effect
they can be used together.
Patch Decorator
This is a commonly used technique, eapecially the object
patch, for example,
in your method you call a class instance method, the patcher can help set up the
return value or side effect for it.
For object patch, the object you specify will be replaced with a mock (or other object) during the test and restored/undo when the test ends:
1 | # the autospec=True: the mock will be created with a spec from the object being |
Order when multiple patch decorators, it is bottom-up:
1 | from unittest.mock import patch |
Helpers
The commonly used helpers:
- mock.ANY