Unit Testing in Python - Interview Questions and Answers

Unit testing involves testing individual components (units) of a program to ensure they perform as expected.

It helps identify bugs early, ensures code reliability, simplifies refactoring, and improves development speed.

unittest is Python's built-in module for writing and running unit tests using the xUnit style.

import unittest

def add(x, y):
    return x + y

class TestAdd(unittest.TestCase):
    def test_add(self):
        self.assertEqual(add(2, 3), 5)

if __name__ == '__main__':
    unittest.main()

 

Test method names should start with test_, so the test runner can detect and run them automatically.

  • assertEqual(a, b) checks if a == b.
  • assertTrue(x) checks if x is True.

with self.assertRaises(ValueError):
    int('abc')

@unittest.skip("Skipping this test temporarily")
def test_temp(self):
    ...

setUp() is run before each test method and is used to set initial conditions.

It’s executed after each test method for cleanup activities like closing files or database connections.

By placing them in classes that inherit from unittest.TestCase.

Use unittest.main() in the script, or run it with python -m unittest filename.py.

It's not recommended. Focus on testing public interfaces that use private methods internally.

self.assertIsNone(func())

It measures how much of your code is tested by your test suite.

Popular tools include coverage.py and pytest-cov.

Yes, use the -k flag with test name patterns when using pytest.

Use the warnings module or decorators like @pytest.mark.filterwarnings.

assertAlmostEqual is used for comparing floating-point values with a tolerance.

Fixtures are pre-test setups like sample data or configurations needed for the test.

@patch is used to replace a method, class, or object with a mock during the test execution.

A mock is a simulated object that mimics the behavior of real objects in controlled ways for testing purposes.

from unittest.mock import patch

@patch('module.function_name')
def test_mock_func(mock_func):
    mock_func.return_value = 'mocked'
    assert module.function_name() == 'mocked'

  • Mock: Base class for mocks.
  • MagicMock: Subclass with magic methods like __len__, __getitem__, etc.
  • patch: Context manager or decorator to replace objects during testing.

It allows setting a sequence of return values or even exceptions to be raised.

mock_object.assert_called()
mock_object.assert_called_with(arg1, arg2)

Use tools like pytest-xdist to run tests with pytest -n auto.

It helps run a test multiple times with different input while keeping each as a separate sub-test case.

Use libraries like parameterized or pytest.mark.parametrize with pytest.

Not typically. Unit tests focus on correctness. Use benchmarks or profiling tools for performance testing.

  • Unit testing: Tests individual units in isolation.
  • Integration testing: Tests how multiple units work together.

Each test should run independently without relying on shared state or other tests.

Each test should run independently without relying on shared state or other tests.

Use libraries like freezegun or mock datetime.now().

from unittest.mock import mock_open, patch

with patch('builtins.open', mock_open(read_data='data')) as mock_file:
    open('file.txt').read()

Automatically locating and running all test files using a pattern, e.g., python -m unittest discover.

A collection of test cases or test classes grouped together.

mock_obj.method.assert_called_once()
mock_obj.method.assert_called_with(args)

CI/CD runs automated tests (including unit tests) on every code change to ensure code quality.

A test that sometimes passes and sometimes fails without any changes in code or environment.

By running the same test multiple times or using tools like pytest-rerunfailures.

Mock external requests (using requests-mock) and test your handler’s behavior/response codes.

@patch('requests.get')
def test_get(mock_get):
    mock_get.return_value.status_code = 200
    ...

Use asyncio.run() in tests or pytest-asyncio to test async methods.

@patch.dict(os.environ, {"API_KEY": "test-key"})
def test_env():
    ...

Use self.assertLogs(logger_name, level) context manager.

Temporarily replacing an object or method during runtime to alter its behavior in tests.

from io import StringIO
from contextlib import redirect_stdout

f = StringIO()
with redirect_stdout(f):
    print("hello")
assert "hello" in f.getvalue()

Use test databases or in-memory databases like SQLite with setup and teardown.

Use mocks or join threads inside the test to ensure behavior is predictable.

  • Test one thing at a time.
  • Use mocks for external dependencies.
  • Keep tests independent.
  • Make tests fast and repeatable.
  • Use meaningful test names.

Yes, test the decorated function’s behavior or the decorator logic directly if needed.

A development approach where tests are written before writing the actual code.

with self.assertWarns(UserWarning):
    warnings.warn("Deprecated")

They replace real objects with simplified versions like stubs, mocks, and fakes for testing.

Tests that run the same logic with different inputs, usually using a decorator to iterate.

@pytest.fixture
def sample_data():
    return {'name': 'John'}

def test_name(sample_data):
    assert sample_data['name'] == 'John'

Yes, using parameterization or external test data files (JSON, CSV, etc.).

Simulating DB interactions by mocking the ORM methods or queries.

Yes, but you might need to refactor the legacy code to make it testable.

Use a structured folder hierarchy, consistent naming, and grouping via test classes or modules.