Batteries-included test module in the Python standard library.
Usage
Define your own class inherited from unittest.TestCase.
Filling it with methods with names starting with test_
Run the tests by placing unittest.main() in a file, usually at the bottom:
if__name__=='__main__': unittest.main()
Main terminology:
test
Smallest unit of testing. Function for testing specific (ideally) smallest piece of functionality.
test case
A test case is the collection of tests combined by some common meaning/feature. It checks for a specific response to a particular set of inputs. unittest provides a base class, TestCase.
test fixture
A test fixture represents the preparation needed to perform one or more tests, and any associate cleanup actions.
mocking
An operation of emulating some object's attribute or the result returned by arbitrary method to be able to test the functionality of tested unit in different conditions.
test suite
A test suite is a collection of test cases, test fixtures, or both.
test runner
A test runner is a component which orchestrates the execution of tests and provides the outcome to the user. The runner may use a graphical interface, a textual interface, or return a special value to indicate the results of executing the tests.
x..
----------------------------------------------------------------------
Ran 3 tests in 0.011s
OK (expected failures=1)
Result:
$ python super_script.py -v
test (__main__.SomeTest) ... ok
----------------------------------------------------------
Ran 1 test in 0.000s
OK
Tests have different outcomes:
Status
Decription
.
ok
F
Fail
E
Error
s
skipped 'msg' - @unittest.skip("demo")
x
expected failure - @unittest.expectedFailure
u
unexpected success
from random import choiceclassMonkey: actions = ['saying "Boo boo boo"','dancing rock&roll','jumping like a crazy frog']def__init__(self,name="Dummy Monkey",age=2): self.name = name self.age = agedefaction(self):return" is ".join((self.name, choice(self.actions)))
...
----------------------------------------------------------------------
Ran 3 tests in 0.013s
OK
<unittest.runner.TextTestResult run=3 errors=0 failures=0>
In most cases unit tests should not contain more than one assertion.
The idea is that each unit test should test one thing and one thing only, to further narrow down what the problem is, when the test fails.
Optimum approach:
test function can have any number of trivial assertions, and at most one non-trivial assertion
Each test is a method, each test naturally runs in its own variable scope. We gain a big advantage from keeping the tests isolated
assert<SomeCheckMethod> methods from TestCase. These give us more flexible ways of checking whether values match, and provide more useful error reports, than Python's basic assert statement.
Assert method
Meaning
assertEqual(a, b)
a == b
assertNotEqual(a, b)
a != b
assertAlmostEqual(a, b)
almost equal :))
assertNotAlmostEqual(a, b)
not almost equal
assertTrue(x)
bool(x) is True
assertFalse(x)
bool(x) is False
assertIs(a, b)
a is b
Assert method
Meaning
assertIsNot(a, b)
a is not b
assertIsNone(x)
x is None
assertIsNotNone(x)
x is not None
assertIn(a, b)
a in b
assertNotIn(a, b)
a not in b
assertIsInstance(a, b)
isinstance(a, b)
assertNotIsInstance(a, b)
not isinstance(a, b)
Examples
from unittest import TestCaseclassLordOfTheFails(TestCase):deftest_1_plus_1__1(self): self.assertEqual(1, 1+1)deftest_1_plus_1__2(self): self.assertTrue(1==1+1)
classExceptionShowCase(TestCase):deftest_int_from_string(self): self.assertRaises(ValueError, int,'31415HELPIAMTRAPPED32', base =16)deftest_int_from_string_2(self): fail =lambda: int('31415HELPIAMTRAPPED32', base =16) self.assertRaises(ValueError, fail)deftest_nice_int_from_str(self):with self.assertRaises(ValueError):int('31415HELPIAMTRAPPED32', base =16)
Error not a Fail will be reported in case of exception type mismatch!
More batteries!
What should we use when EVERYTHING in assert methods are not good for us (it's near to impossible btw)?
Just fail the test!
self.fail()
classTimeMachineTest(TestCase):deftest_time_machine(self):from datetime import datetime as dtif dt.now().year !=1986: self.fail("Hmm, Time Machine has been broken!")
Let's fix the third test - with our super knowledge!
deftest_action(self): self.assertEqual(self.monkey.trick(), 'Foo is saying "Boo boo boo"')
How should it look like?
deftest_action(self): self.assertIn( self.monkey.trick(), [f'{self.monkey.name} is {x}'for x in self.monkey.actions] )
Test Fixtures
Fixtures are resources needed by a test.
To configure the fixtures, override setUp() -> will be run BEFORE EACH TEST
To clean up, override tearDown() -> will be run AFTER EACH TEST
classMonkeyTestCase(unittest.TestCase):@classmethoddefsetUpClass(cls): cls.monkey =Monkey('Foo', 3)@classmethoddeftearDownClass(cls):del cls.monkeydeftest_naming(self): self.assertEqual(self.monkey.name, 'Foo')deftest_aging(self): self.assertEqual(self.monkey.age, 3)deftest_action(self): self.assertIn( self.monkey.action(), [f'{self.monkey.name} is {x}'for x in self.monkey.actions] )suite = unittest.TestLoader().loadTestsFromModule(MonkeyTestCase())unittest.TextTestRunner().run(suite)
📟 Output:
...
----------------------------------------------------------------------
Ran 3 tests in 0.007s
OK
<unittest.runner.TextTestResult run=3 errors=0 failures=0>
Test suites
Used to organize many tests
Not used often, better use lib pytest or nose.
Instead of unittest.main(), there are other ways to run the tests with a finer level of control, less terse output, and no requirement to be run from the command line.
For example:
from unittest import TestLoader, TextTestRunnersuite =TestLoader().loadTestsFromTestCase(LordOfTheFails)unittest.TextTestRunner(verbosity=2).run(suite)
Tests structure
Even if it is very heavily project-dependent there are several general tests structures that differ by focusing particular entity in tests:
OUT - Object Under Test
We testing particular object
CUT - Class Under Test
We testing particular class
MUT - Method Under Test
We testing particular method
FUT - Function Under Test
We testing particular function
Our example above was testing a class - so was following "CUT" structure.
Extended example
Let's enrich our code which we test (let's save and call this module as monkey.py):
classMonkey: actions = ['saying "Boo boo boo"',"dancing rock&roll","jumping like a crazy frog"]def__init__(self,name="Dummy Monkey",age=2,actions=None): self.name = name self.age = ageif actions isnotNone: self.actions = actionsdef__str__(self):returnf"Monkey '{self.name}' ({self.age} years old)"defaction(self):from random import choicereturnf"{self.name} is {choice(self.actions)}"defset_name(self,new_name):if new_name: self.name = new_namedefset_age(self,age):if age >0: self.age = agedefset_actions(self,actions):ifisinstance(actions, (list, tuple)): self.actions = actions
......
----------------------------------------------------------------------
Ran 6 tests in 0.012s
OK
<unittest.runner.TextTestResult run=6 errors=0 failures=0>
## Test Case Example
Let's check "kind-of-real-world" example. It is of course mostly "mock" but still it is interesting project.
This would the contents of our app.py module with core logic of the program that will be tested:
import randomimport timeclassDevice:""" Mock Device class """ RESULT ="<{cmd}>: 200, OK" DATA = ["rtyu","asd","asdasdhh","jfghghj"]def__init__(self,serial="0000"): self._serial = serialdef_run_cmd(self,cmd,seconds=0): time.sleep(seconds)return self.RESULT.format(cmd=cmd)defstart(self):return self._run_cmd("start")defreboot(self):return self._run_cmd("reboot")defpool_data(self):return self._run_cmd("pool", 3)defquery(self,data=""):returnf"RESULT is: {random.choice(self.DATA)}"
Let's write BasicTestCase which will be the basis of our actual test cases (let's assume that some app.py holds the main code being tested with core logic).
Here in BasicTestCase we will store constants, define some basic stuff - fixtures: