unittest is Python's built-in testing framework. Here's how to use it.
Basic Test
import unittest
def add(a, b):
return a + b
class TestAdd(unittest.TestCase):
def test_add_positive(self):
self.assertEqual(add(2, 3), 5)
def test_add_negative(self):
self.assertEqual(add(-1, 1), 0)
if __name__ == "__main__":
unittest.main()Run:
python -m unittest test_module.py
python -m unittest discover # Find all testsAssertions
class TestAssertions(unittest.TestCase):
def test_equality(self):
self.assertEqual(a, b) # a == b
self.assertNotEqual(a, b) # a != b
def test_truth(self):
self.assertTrue(x) # bool(x) is True
self.assertFalse(x) # bool(x) is False
def test_identity(self):
self.assertIs(a, b) # a is b
self.assertIsNot(a, b) # a is not b
self.assertIsNone(x) # x is None
self.assertIsNotNone(x) # x is not None
def test_membership(self):
self.assertIn(a, b) # a in b
self.assertNotIn(a, b) # a not in b
def test_types(self):
self.assertIsInstance(a, T) # isinstance(a, T)
def test_comparison(self):
self.assertGreater(a, b) # a > b
self.assertLess(a, b) # a < b
self.assertGreaterEqual(a, b)
self.assertLessEqual(a, b)
def test_approximate(self):
self.assertAlmostEqual(a, b, places=2)Testing Exceptions
class TestExceptions(unittest.TestCase):
def test_raises(self):
with self.assertRaises(ValueError):
int("not a number")
def test_raises_with_message(self):
with self.assertRaises(ValueError) as context:
raise ValueError("custom message")
self.assertIn("custom", str(context.exception))
def test_raises_regex(self):
with self.assertRaisesRegex(ValueError, r"invalid.*"):
raise ValueError("invalid input")setUp and tearDown
class TestDatabase(unittest.TestCase):
def setUp(self):
"""Run before each test."""
self.db = Database(":memory:")
self.db.create_tables()
def tearDown(self):
"""Run after each test."""
self.db.close()
def test_insert(self):
self.db.insert({"name": "Alice"})
self.assertEqual(self.db.count(), 1)
class TestWithClassSetup(unittest.TestCase):
@classmethod
def setUpClass(cls):
"""Run once before all tests in class."""
cls.expensive_resource = load_data()
@classmethod
def tearDownClass(cls):
"""Run once after all tests in class."""
cls.expensive_resource.cleanup()Skipping Tests
import sys
class TestSkipping(unittest.TestCase):
@unittest.skip("Not implemented yet")
def test_future_feature(self):
pass
@unittest.skipIf(sys.platform == "win32", "Unix only")
def test_unix_specific(self):
pass
@unittest.skipUnless(sys.platform == "darwin", "macOS only")
def test_macos_specific(self):
pass
@unittest.expectedFailure
def test_known_bug(self):
self.assertEqual(1, 2) # Won't fail the suiteTest Discovery
tests/
├── __init__.py
├── test_users.py
├── test_orders.py
└── integration/
├── __init__.py
└── test_api.py
# Run all tests
python -m unittest discover
# Specific directory
python -m unittest discover -s tests
# Pattern
python -m unittest discover -p "test_*.py"Mocking
from unittest.mock import Mock, patch, MagicMock
class TestMocking(unittest.TestCase):
def test_mock_object(self):
mock = Mock()
mock.method.return_value = 42
result = mock.method("arg")
self.assertEqual(result, 42)
mock.method.assert_called_once_with("arg")
@patch("module.external_api")
def test_patch_decorator(self, mock_api):
mock_api.fetch.return_value = {"data": []}
result = function_using_api()
mock_api.fetch.assert_called()
def test_patch_context(self):
with patch("module.requests.get") as mock_get:
mock_get.return_value.json.return_value = {}
result = fetch_data()Mock Assertions
mock = Mock()
mock("arg1", key="value")
mock.assert_called()
mock.assert_called_once()
mock.assert_called_with("arg1", key="value")
mock.assert_called_once_with("arg1", key="value")
mock.assert_not_called()
# Check call count
self.assertEqual(mock.call_count, 1)
# Check all calls
mock.assert_has_calls([
call("arg1"),
call("arg2"),
])Parameterized Tests
class TestMath(unittest.TestCase):
def test_add_cases(self):
cases = [
(2, 3, 5),
(-1, 1, 0),
(0, 0, 0),
]
for a, b, expected in cases:
with self.subTest(a=a, b=b):
self.assertEqual(add(a, b), expected)Test Organization
# test_user.py
import unittest
from myapp.user import User
class TestUserCreation(unittest.TestCase):
"""Tests for user creation."""
def test_create_with_name(self):
user = User("Alice")
self.assertEqual(user.name, "Alice")
def test_create_requires_name(self):
with self.assertRaises(ValueError):
User("")
class TestUserAuthentication(unittest.TestCase):
"""Tests for user authentication."""
def setUp(self):
self.user = User("Alice")
self.user.set_password("secret")
def test_correct_password(self):
self.assertTrue(self.user.check_password("secret"))
def test_wrong_password(self):
self.assertFalse(self.user.check_password("wrong"))Running Tests
# Run all tests
python -m unittest
# Specific module
python -m unittest test_module
# Specific class
python -m unittest test_module.TestClass
# Specific test
python -m unittest test_module.TestClass.test_method
# Verbose output
python -m unittest -v
# Stop on first failure
python -m unittest -fQuick Reference
import unittest
from unittest.mock import Mock, patch
class TestExample(unittest.TestCase):
def setUp(self):
# Before each test
pass
def tearDown(self):
# After each test
pass
def test_something(self):
self.assertEqual(a, b)
self.assertTrue(x)
self.assertRaises(Error, func)
with self.assertRaises(Error):
func()
@patch("module.func")
def test_with_mock(self, mock_func):
mock_func.return_value = 42
# Test code
if __name__ == "__main__":
unittest.main()unittest is always available. For more features, consider pytest, but unittest handles most testing needs.
React to this post: