unittest
why unit test ?
unit test is to test the components of a program automatically. a few good reasons of unit tests:
- test driving practice
- early sanity checking
- for regression test, that the new changes won’t break early work
- unit test is to test the isolated piece of code, usually white box test written by developer; functional test is to test the function requirements, usually black box test written by testers.
the testcase output can be OK
, FAIL
, ERROR
.
assertion functions
basic boolean asserts:
assertEqual(arg1, arg2, msg=None) assertIsNot(arg1, arg2, msg=None)
comparative asserts:
assertAlmostEqual(first, second, places=7, msg=None, delta=None)
asserts for collections:
assertListEqual(list1, list2, msg=None) assertTupleEqual(tuple1, tuple2, msg=None) assertSetEqual(set1, set2, msg=None) assertDictEqual(dic1, dic2, msg=None)
command line interface
run all unittests
python3 -m unittest discover -v -c
run single test module
python3 -m unittest -v -c tests/test_XXXX.py
run individual test case
python3 -m unittest -v tests.test_XXX.TestCaseXXX.test_XXX
how to write a unittest
unittest.TestCase
any user defined test class should first derived from unittest.TestCase
setUp() & tearDown()
setUp() provides the way to set up things before running any method starting with test_xx()
in user defined test class. tearDown() is where to end the setting ups
depends on other modules/class
usually the class/methods we try to test have depends on other modules/class, but as unit test want to isolate these depends, so either mock the other methods or fake data. in general there are three types of depends:
pure variable
this is the most simple case, then you can directly define the same pure type variable in your test_xx()
method
methods in other modules
here need to use patch
, to mock the method and define its return value is your test_xx()
method, then in the class where call the real method will be replace by this mocked one.
methods in other class
here need to use mock
, either MagicMock or Mock will works.
mock & patch
mock module is built in unittest.mock
after a later Python version, if not, mock
module can be installed by pip install mock, then directly import mock
in code.
the philosophy of unit test is to isolate classes test, but in reality most classes have some instances of other classes, so to test the ego class isolated from other classes, that’s where mock
helps.
|
|
Mock
helps to mock the method in a class, while patch
helps to mock a global method in a module.
automatical unittest generator
there are projects assit to generate unittest code automatically, e.g auger
test with while Loop
TODO
test with raise Error
TODO
coverage.py
(one time only) install coverage.py
pip3 install –user coverage
run all tests with coverage
~/.local/bin/coverage run -m unittest discover
generate html report
~/.local/bin/coverage html –omit “~/.local/“,”tests/“
ps, output is in htmlcov/index.html
logging
logging()
supports more detail message type(info, warn, error, debug) than print()
, and the message format is more strict with \%
. while print()
works both formatted message and message as string simply:
|
|
the following is a general log class, which can plug in any existing Python project:
import logging
import os
class log(object):
def __init__(self, logger_name):
self.log = logging.getLogger(logger_name)
self.log.setLevel(logging.DEBUG)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
self.log.addHandler(console_handler)
def set_output_file(self, filename):
file_handler = logging.FileHandler(filename)
file_handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
self.log.addHandler(file_handler)
def main():
test = log('scenario-mm')
file_name = "record.log"
dir_name = "/home/python_test/"
try:
os.makedirs(dir_name)
except OSError:
pass
file_path = os.path.join(dir_name, file_name)
test.set_output_file(file_path)
test.log.debug("debug in episode 1...")
test.log.info("info ...")
test.log.warning("warn ...")
test2 = log("zjjj")
test2.set_output_file('record.log')
test2.log.info("test2 info")
test2.log.warning("test2 warn")
if __name__ == "__main__":
main()