Python unittest and unittest.mock
source link: https://www.chunyangwen.com/blog/python/python-unittest.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Each language has its own known testing framework which makes writing test cases easier. In
Python, the standard and built-in are unittest
module. Together with pytest
or coverage
,
we can get a statistics of all the tests which can be displayed in GUI.
test cases in unittest
- Implement a class which inherits from
unittest.TestCase
- Add tests
- Call related
assert*
methods to check results
- Call related
- Add an entrance for the testing module
import operator
import unittest
class SimpleTestCase(unittest.TestCase):
def test_add(self):
a = 2
b = 3
self.assertEqual(5, operator.add(2, 3))
if __name__ == "__main__":
unittest.main()
Running the test
We have added __name__
related code, so we can directly call the module. Assuming previous
code is saved in a file named simple_test.py
, we can run:
# Run all the tests in the module
python -m unittest simple_test[.py]
# Run a single test
python -m unittest simple_test.SimpleTestCase.test_add
If simple_test.py
is saved in a different folder which means in a different module, we need
to make sure that the testing module is importable. If simple_test
is saved under a folder
named tests
, then there should be a __init__.py
under that.
python -m unittest tests/simple_test.py
Before and after a test
If there are codes we need to run before a test (initialization code) or after a test (cleanup
code), we can override setUp
and tearDown
.
import operator
import unittest
class SQLDB(object):
def connect_db(self):
pass
def read_record(self):
return 1
def disconnect_db(self):
pass
class SimpleTestCase(unittest.TestCase):
def setUp(self):
self._db = SQLDB()
self._db.connect_db()
def tearDown(self):
self._db.disconnect_db()
def test_add(self):
self.assertEqual(1, self._db.read_record())
if __name__ == "__main__":
unittest.main()
Skip certain tests
If there are tests we want to skip under certain condition, we can skip it instead of emitting
an error by running it. We can skip a whole test and any single test by decorating with
unittest.skipIf
import operator
import os
import unittest
try:
import SQLDB
except ModuleNotFoundError:
SQLDB = None
class SQLDB(object):
def connect_db(self):
pass
def read_record(self):
return 1
def disconnect_db(self):
pass
@unittest.skipIf(SQLDB is None, "SQLDB is not available")
class SimpleTestCase(unittest.TestCase):
def setUp(self):
self._db = SQLDB()
self._db.connect_db()
def tearDown(self):
self._db.disconnect_db()
@unittest.skipIf("DB_TEST" not in os.environ, "Not a testing environment")
def test_add(self):
self.assertEqual(1, self._db.read_record())
if __name__ == "__main__":
unittest.main()
There are other skipping decorators:
skip(reason)
- Used to write customized skipping decorators
# Refer to https://www.chunyangwen.com/blog/python/python-descriptor-decorator.html
# for more details about a decorator
def skipUnlessHasattr(obj, attr):
if hasattr(obj, attr):
return lambda func: func
return unittest.skip("{!r} doesn't have {!r}".format(obj, attr))
skipUnless(condition, reason)
skipIf(condition, reason)
unittest.expectedFailure
: A failure is a success.
Asserting methods
Common practices
# assert whether certain log happens
with self.assertLogs(logger, level="INFO") as cm:
# bla bla
self.assertRegex(" ".join(cm.output), ".*xxx.*")
with self.assertRaises(ValueError):
# bla bla
unittest.mock
Mock is very important is tests.
- You don’t want your test cases fail due to an unrelated service.
- You don’t want your test cases coupled with each other:
- Testing functionalities of
a
whileb
may fail.
- Testing functionalities of
A simple case
Usually we will use unittest.mock.patch
to help us write tests. We can patch:
- constants
- Actually I think this is easy done without
mock
.
- Actually I think this is easy done without
- a module function
- a class method
- a static method
- an object method
# my_module.py
import os
def get_more_magic_values():
return [42] * 2
def get_magic_value():
return get_more_magic_values()[0]
def listdir(p):
return os.listdir(p)
class ObjectToBeMocked(object):
HelloWorld = 3
def hello(self):
return ObjectToBeMocked.HelloWorld
def hi(self):
return self.hello() + 1
@property
def beer(self):
return -1
@classmethod
def oops(cls):
return "oops"
We save above code into a file named my_module.py
. Most of the functions and methods
are just naively implemented. So how we can test it?
import unittest
from unittest import mock
from unittest.mock import patch
from my_module import ObjectToBeMocked
import my_module
class MyModuleTest(unittest.TestCase):
@patch("my_module.ObjectToBeMocked.HelloWorld", 42)
def test_hello(self):
self.assertEqual(42, my_module.ObjectToBeMocked().hello())
@patch.object(my_module.ObjectToBeMocked, "hello", return_value=42)
def test_hi(self, hello):
o = ObjectToBeMocked()
self.assertEqual(43, o.hi())
@patch.object(my_module.ObjectToBeMocked, "hello")
def test_hi_1(self, hello):
hello.return_value = -1
o = ObjectToBeMocked()
self.assertEqual(0, o.hi())
@patch("my_module.get_more_magic_values")
def test_get_magic_value(self, get_more_magic_values):
get_more_magic_values.return_value = [41]
self.assertEqual(41, my_module.get_magic_value())
@patch("my_module.ObjectToBeMocked.beer", new_callable=mock.PropertyMock())
def test_property_beer(self, prop):
prop.return_value = -2
self.assertEqual(-2, my_module.ObjectToBeMocked().beer())
@patch("my_module.os")
def test_mock_os(self, mock_os):
mock_os.listdir.return_value = ["hi", "hello"]
self.assertListEqual(["hi", "hello"], my_module.listdir("."))
Notes
- We have to mock the module where it is used. NOT from a general place
- Please refer to
mock_os
- Please refer to
- When it comes to an instance,
patch.object
is used instead ofpatch
. - Difference between
patch
andpatch.object
:patch.object
mocks a single method or attributes, target is also the class
classes
unittest.mock.Mock
unittest.mock.MagicMock
unittest.mock.PropertyMock
unittest.mock.AsyncMock
Common practices
# create a side-effect
with patch(Clz, "method", side_effect=ValueError) as mock_method:
ins = Clz()
self.assertRaise(ValueError):
ins.method()
from unittest.mock import create_autospec
# automatically behaves like `Clz`
MockObject = create_autospec(Clz)
Reference
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK