Refactoring for Cleaner Test Design Writing unit tests for legacy or poorly structured code often feels like a battle against the machine. When a function creates its own dependencies internally, testing that function requires heavy-handed monkey patching and complex mocking strategies. This brittle approach makes tests hard to maintain and even harder to read. The solution isn't just better mocks; it's better code design. By refactoring our functions to be more testable, we simultaneously improve the architecture of our entire application. Implementing Dependency Injection and Protocols The most effective way to break tight coupling is Dependency Injection. Instead of a function instantiating a Payment Processor inside its body, we pass the processor as an argument. This shift gives the caller—and the test suite—full control over the implementation. To keep things flexible, we define a Protocol using Python's `typing` module. This allows us to use duck-typing to create a mock version of the processor that behaves like the real thing without requiring complex inheritance. ```python from typing import Protocol class PaymentProcessor(Protocol): def charge(self, card: CreditCard, amount: int) -> None: ... def pay_order(order: Order, processor: PaymentProcessor, card: CreditCard): if not order.line_items: raise ValueError("Order is empty") processor.charge(card, order.total_price) ``` Streamlining Tests with Pytest Fixtures When multiple tests require the same setup—like a valid Credit Card object—redundancy creeps in. Pytest fixtures solve this by providing standard, reusable objects to your test functions. We can also make these fixtures "future-proof" by calculating dates dynamically. Hard-coding an expiry date of 2024 might work today, but it ensures your tests will break the moment that year passes. ```python import pytest from datetime import date @pytest.fixture def card(): future_year = date.today().year + 2 return CreditCard(number="4111...", expiry_month=12, expiry_year=future_year) def test_pay_order_valid(card, processor_mock): # The card is automatically injected by pytest pay_order(my_order, processor_mock, card) ``` Handling Sensitive Data and Pure Functions Hard-coding API keys is a major security risk and a testing headache. Move these to environment variables using python-dotenv. This keeps secrets out of your repository and allows different keys for development, testing, and production. Finally, simplify your logic by identifying functions that don't need to be tied to a class. A validation utility like the `luhn_checksum` doesn't need `self`. Converting it to a standalone pure function makes it trivial to test in isolation without instantiating a heavy processor object. This separation of concerns is the hallmark of professional software development.
Payment Processor
Concepts
May 2022 • 1 videos
High activity month for Payment Processor. ArjanCodes among the most active voices, with 1 videos across 1 sources.
May 2022
- May 27, 2022