Interface Segregation with Fake Test Implementations in Python
Defines segregated abstract interfaces (Printer, Scanner) and uses a FakePrinter to record calls for unit testing without real resources.
Python code
46 linesfrom abc import ABC, abstractmethod
class Printer(ABC):
@abstractmethod
def print_document(self, doc: str) -> str:
pass
class Scanner(ABC):
@abstractmethod
def scan_document(self) -> str:
pass
class MultiFunctionPrinter(Printer, Scanner):
def print_document(self, doc: str) -> str:
return f"Printed: {doc}"
def scan_document(self) -> str:
return "Scanned content"
class SimplePrinter(Printer):
def print_document(self, doc: str) -> str:
return f"Printed: {doc}"
class FakePrinter(Printer):
"""Fake implementation for testing — records calls without real printing."""
def __init__(self):
self.calls = []
def print_document(self, doc: str) -> str:
self.calls.append(doc)
return f"Fake printed: {doc}"
if __name__ == "__main__":
fake = FakePrinter()
result = fake.print_document("Report.pdf")
print(result)
print("Recorded calls:", fake.calls)
assert isinstance(fake, Printer), "FakePrinter must implement Printer"
assert fake.calls == ["Report.pdf"], "Fake should record the print call"
print("Interface segregation + fake test passed")
Output
Fake printed: Report.pdf
Recorded calls: ['Report.pdf']
Interface segregation + fake test passed
How it works
The ABC and abstractmethod from the abc module enforce the interface contract: any subclass must implement the abstract methods. By splitting MultiFunctionPrinter into separate Printer and Scanner interfaces, clients depend only on the methods they actually use — this is the Interface Segregation Principle. The FakePrinter satisfies Printer while tracking each call in self.calls, making it easy to assert behavior in tests without real hardware. The assert isinstance(fake, Printer) confirms the fake implements the interface, and checking calls verifies the interactions.
Common mistakes
- Making fake implementations that forget to record calls, so tests can't verify interaction.
- Creating one monolithic interface that forces classes to implement unused methods.
- Forgetting to call `super().__init__()` when the base class has state that the fake needs.
- Using `print` in a fake instead of returning a value expected by the interface.
Variations
- Use `unittest.mock.Mock` to auto-create fakes that record calls.
- Use `pytest` fixtures to provide fake instances to tests.
Real-world use cases
- Unit testing a report scheduler that depends on a printer interface, using a fake to capture printable documents.
- Testing a document scanning workflow with a fake scanner that returns canned scan data, avoiding real hardware.
- Verifying that a service calls the correct interface methods by inspecting the fake's recorded call list.
Sponsored
More from Testing & modern typing
- Capture stdout and stderr with pytest capsys easy
- Characterization Test for Legacy Python Code medium
- Dataclass with Type Hints Fields in Python easy
- Dependency Injection in Python for Testability easy
- Design Data Helpers with Python TypedDict and Literal easy
- Fix and Test a Regression Bug in Python with Unit Tests easy
Keep learning
Related tutorials and quizzes for this topic.