seed-skills/python-unittest/SKILL.md
Comprehensive Python unittest skill covering TestCase patterns, assertion methods, mocking with unittest.mock, subtests, fixtures, and test organization for robust Python application testing.
npx skillsauth add PramodDutta/qaskills Python UnittestInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert Python developer specializing in testing with the built-in unittest framework. When the user asks you to write, review, or debug unittest-based tests, follow these detailed instructions to produce reliable, well-structured test suites.
unittest.mock.patch and MagicMock to eliminate network calls, file I/O, and database access from unit tests.test_<method>_<scenario>_<expected> so test output reads as a specification.setUp() and cleanup in tearDown() to ensure consistent test state.self.subTest() to run multiple input variations within a single test method without stopping at first failure.project/
src/
services/
__init__.py
user_service.py
payment_service.py
utils/
__init__.py
validators.py
formatters.py
models/
__init__.py
user.py
order.py
tests/
__init__.py
test_user_service.py
test_payment_service.py
test_validators.py
test_formatters.py
integration/
__init__.py
test_user_payment_flow.py
fixtures/
__init__.py
sample_data.py
setup.cfg
pyproject.toml
[tool:pytest]
# If using pytest as runner for unittest tests
testpaths = tests
[unittest]
start-dir = tests
pattern = test_*.py
# Run all tests
python -m unittest discover -s tests -p "test_*.py"
# Run specific test file
python -m unittest tests.test_user_service
# Run specific test class
python -m unittest tests.test_user_service.TestUserService
# Run specific test method
python -m unittest tests.test_user_service.TestUserService.test_create_user_with_valid_data
# Verbose output
python -m unittest discover -v
import unittest
from src.services.user_service import UserService
class TestUserService(unittest.TestCase):
"""Tests for UserService class."""
def setUp(self):
"""Set up test fixtures before each test method."""
self.service = UserService()
self.valid_user_data = {
'name': 'Alice',
'email': '[email protected]',
'age': 30
}
def tearDown(self):
"""Clean up after each test method."""
self.service = None
def test_create_user_with_valid_data(self):
"""Should create a user when all required fields are provided."""
user = self.service.create_user(self.valid_user_data)
self.assertIsNotNone(user)
self.assertEqual(user.name, 'Alice')
self.assertEqual(user.email, '[email protected]')
def test_create_user_without_email_raises_error(self):
"""Should raise ValueError when email is missing."""
invalid_data = {'name': 'Bob'}
with self.assertRaises(ValueError) as context:
self.service.create_user(invalid_data)
self.assertIn('email', str(context.exception))
def test_create_user_with_invalid_email_raises_error(self):
"""Should raise ValueError for malformed email addresses."""
invalid_data = {'name': 'Bob', 'email': 'not-an-email'}
with self.assertRaises(ValueError):
self.service.create_user(invalid_data)
if __name__ == '__main__':
unittest.main()
class TestAssertionExamples(unittest.TestCase):
"""Demonstrates unittest assertion methods."""
def test_equality_assertions(self):
self.assertEqual(1 + 1, 2)
self.assertNotEqual(1, 2)
self.assertAlmostEqual(0.1 + 0.2, 0.3, places=5)
self.assertNotAlmostEqual(1.0, 2.0)
def test_truth_assertions(self):
self.assertTrue(10 > 5)
self.assertFalse(5 > 10)
self.assertIsNone(None)
self.assertIsNotNone('value')
def test_identity_assertions(self):
a = [1, 2, 3]
b = a
c = [1, 2, 3]
self.assertIs(a, b)
self.assertIsNot(a, c)
def test_type_assertions(self):
self.assertIsInstance(42, int)
self.assertNotIsInstance('hello', int)
def test_collection_assertions(self):
self.assertIn(2, [1, 2, 3])
self.assertNotIn(4, [1, 2, 3])
self.assertCountEqual([1, 2, 3], [3, 1, 2])
def test_string_assertions(self):
self.assertRegex('hello123', r'\d+')
self.assertNotRegex('hello', r'\d+')
def test_comparison_assertions(self):
self.assertGreater(10, 5)
self.assertGreaterEqual(10, 10)
self.assertLess(5, 10)
self.assertLessEqual(5, 5)
from unittest.mock import patch, MagicMock
from src.services.user_service import UserService
class TestUserServiceWithMocks(unittest.TestCase):
@patch('src.services.user_service.database')
def test_get_user_by_id(self, mock_db):
"""Should return user from database."""
mock_db.find_one.return_value = {
'id': 1,
'name': 'Alice',
'email': '[email protected]'
}
service = UserService()
user = service.get_user(1)
mock_db.find_one.assert_called_once_with({'id': 1})
self.assertEqual(user['name'], 'Alice')
@patch('src.services.user_service.database')
def test_get_user_not_found_returns_none(self, mock_db):
"""Should return None when user does not exist."""
mock_db.find_one.return_value = None
service = UserService()
user = service.get_user(999)
self.assertIsNone(user)
@patch('src.services.user_service.email_client')
@patch('src.services.user_service.database')
def test_create_user_sends_welcome_email(self, mock_db, mock_email):
"""Should send welcome email after creating user."""
mock_db.insert_one.return_value = MagicMock(inserted_id=1)
service = UserService()
service.create_user({'name': 'Bob', 'email': '[email protected]'})
mock_email.send.assert_called_once()
call_args = mock_email.send.call_args
self.assertEqual(call_args[1]['to'], '[email protected]')
class TestPaymentService(unittest.TestCase):
def test_process_payment_calls_gateway(self):
"""Should call payment gateway with correct amount."""
with patch('src.services.payment_service.PaymentGateway') as MockGateway:
mock_instance = MockGateway.return_value
mock_instance.charge.return_value = {'status': 'success', 'txn_id': 'abc123'}
service = PaymentService()
result = service.process_payment(amount=50.00, card_token='tok_123')
mock_instance.charge.assert_called_once_with(
amount=50.00,
token='tok_123'
)
self.assertEqual(result['status'], 'success')
def test_process_payment_handles_gateway_error(self):
"""Should raise PaymentError when gateway fails."""
with patch('src.services.payment_service.PaymentGateway') as MockGateway:
mock_instance = MockGateway.return_value
mock_instance.charge.side_effect = ConnectionError('Gateway down')
service = PaymentService()
with self.assertRaises(PaymentError):
service.process_payment(amount=50.00, card_token='tok_123')
class TestValidator(unittest.TestCase):
def test_email_validation_with_valid_emails(self):
"""Should accept various valid email formats."""
valid_emails = [
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
]
for email in valid_emails:
with self.subTest(email=email):
self.assertTrue(validate_email(email))
def test_email_validation_with_invalid_emails(self):
"""Should reject malformed email addresses."""
invalid_emails = [
'',
'not-an-email',
'@domain.com',
'user@',
'user @domain.com',
]
for email in invalid_emails:
with self.subTest(email=email):
self.assertFalse(validate_email(email))
def test_age_validation_with_boundary_values(self):
"""Should validate age is within acceptable range."""
test_cases = [
(0, False),
(1, True),
(17, False),
(18, True),
(120, True),
(121, False),
(-1, False),
]
for age, expected in test_cases:
with self.subTest(age=age, expected=expected):
self.assertEqual(validate_age(age), expected)
class TestDatabaseIntegration(unittest.TestCase):
@classmethod
def setUpClass(cls):
"""One-time setup for entire test class."""
cls.db_connection = create_test_database()
cls.db_connection.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)')
@classmethod
def tearDownClass(cls):
"""One-time cleanup after all tests in class complete."""
cls.db_connection.execute('DROP TABLE users')
cls.db_connection.close()
def setUp(self):
"""Per-test setup: start transaction for rollback."""
self.db_connection.execute('BEGIN')
def tearDown(self):
"""Per-test cleanup: rollback to maintain isolation."""
self.db_connection.execute('ROLLBACK')
def test_insert_user(self):
"""Should insert user into database."""
self.db_connection.execute(
"INSERT INTO users (name) VALUES (?)", ('Alice',)
)
result = self.db_connection.execute("SELECT name FROM users").fetchone()
self.assertEqual(result[0], 'Alice')
import asyncio
import unittest
from unittest.mock import AsyncMock, patch
class TestAsyncService(unittest.IsolatedAsyncioTestCase):
"""Tests for async code using IsolatedAsyncioTestCase (Python 3.8+)."""
async def test_fetch_data_returns_results(self):
"""Should return data from async fetch."""
service = AsyncDataService()
with patch.object(service, 'http_client') as mock_client:
mock_client.get = AsyncMock(return_value={'items': [1, 2, 3]})
result = await service.fetch_data('/api/items')
self.assertEqual(len(result['items']), 3)
async def test_fetch_data_retries_on_failure(self):
"""Should retry failed requests up to 3 times."""
service = AsyncDataService()
with patch.object(service, 'http_client') as mock_client:
mock_client.get = AsyncMock(
side_effect=[
ConnectionError('timeout'),
ConnectionError('timeout'),
{'items': []}
]
)
result = await service.fetch_data('/api/items')
self.assertEqual(mock_client.get.call_count, 3)
self.assertEqual(result['items'], [])
setUp and tearDown consistently -- Initialize shared test objects in setUp and clean up resources in tearDown to ensure each test starts with a clean slate.assertRaises context manager -- Use with self.assertRaises(ExceptionType) to test exceptions cleanly and access the exception instance for further assertions.subTest for data-driven tests -- Instead of writing separate test methods for each input, use self.subTest() to test multiple values while getting individual failure reports.@patch('src.services.user_service.database')) not where they are defined.MagicMock for complex dependencies -- It automatically creates attributes and methods on access, reducing boilerplate for complex mock setups.setUpClass for expensive setup -- Database connections and file system setup that can be shared across tests should use class-level fixtures.python -m unittest discover to automatically find and run all tests rather than importing them manually._private_method() couples tests to implementation; test through the public API instead.assertEqual(True, result) instead of assertTrue -- Use the specific assertion method for better failure messages and readability.tearDown for file handles, database connections, or temporary files causes resource leaks and flaky tests.assertRaises instead.tempfile and os.path.join for portability.-v flag or not reading test names means you miss the specification value of well-named tests.development
Generate test data from the schemas you already have. Read OpenAPI, JSON Schema, SQL DDL, or TypeScript models and produce deterministic factories, boundary and negative cases, relational datasets with valid foreign keys, cleanup scripts, and PII-safe synthetic data. Production records never leave the machine.
development
Diagnose flaky test failures from Playwright reports, traces, and rerun history. Classify each failure as product, test, environment, data, or unknown with cited evidence and a proposed fix. Never auto-modifies code without opt-in.
tools
Test LLM, RAG, MCP, and agentic systems end to end. Build golden datasets, run deterministic checks and LLM judges, score retrieval, probe prompt injection, verify tool use, and gate CI on thresholds. Orchestrates DeepEval, Ragas, promptfoo, and Langfuse.
development
Analyze a git diff, map affected risks, select the tests that matter, detect coverage gaps on changed lines, run configurable quality gates, and produce a go/no-go release report with cited evidence. Recommends only; never merges or deploys.