When testing code that should raise an exception, having multiple method calls in the tested code makes it unclear which call is expected to raise the exception.
When writing tests for exception handling, clarity is essential. If you chain multiple method calls within the code being tested for an exception, it becomes ambiguous which specific call is expected to raise that exception.
For example, consider this code:
pytest.raises(IndexError, lambda: get_item().process())
Here, both get_item() and process() could potentially raise an IndexError. The test does not make it
explicit which method is expected to raise the exception. This ambiguity makes the test harder to read and maintain.
By isolating the method call you want to test, you make your test explicit and unambiguous. This improves readability and maintainability, and makes your test suite a better specification of expected behavior.
Writing tests with multiple method calls when testing for exceptions reduces test quality:
When using pytest’s pytest.raises() context manager, extract intermediate method calls into separate variables before the assertion.
This ensures only the final method call that you want to test is inside the exception-testing block.
import pytest
def test_process():
# Which method raises the exception: get_item() or process()?
with pytest.raises(IndexError): # Noncompliant
get_item().process()
import pytest
def test_process():
item = get_item()
with pytest.raises(IndexError):
item.process()
When using unittest’s assertRaises() method, extract intermediate method calls before the assertion. This makes it clear which
specific method is being tested for the exception.
import unittest
class TestMyCode(unittest.TestCase):
def test_process(self):
# Which method raises the exception: get_item() or process()?
self.assertRaises(IndexError, lambda: get_item().process()) # Noncompliant
import unittest
class TestMyCode(unittest.TestCase):
def test_process(self):
item = get_item()
self.assertRaises(IndexError, item.process)