Spotten in Python: unittest.mock, patch en de fouten die tests doen liegen

  1. Home
  2. Blog
  3. Testautomatisering

Testautomatisering

Spotten in Python: unittest.mock, patch en de fouten die tests doen liegen

Met Mocks kun je code testen zonder een echt netwerk, database of klok. Bij onzorgvuldig gebruik lieten ze ook gebroken code door. Hier ziet u hoe u unittest.mock goed kunt gebruiken, waarbij elk voorbeeld wordt uitgevoerd.

8 min leestijd
Spotten in Python: unittest.mock, patch en de fouten die tests doen liegen

Belangrijkste punten

  • Een nep vervangt een echt object, zoals een API-client, zodat tests snel en offline worden uitgevoerd.
  • Gebruik return_value voor een vast antwoord en side_effect voor fouten of een reeks.
  • Plaats de naam waar deze wordt gebruikt, niet waar deze is gedefinieerd. Dit is de meest voorkomende bug.
  • Gebruik spec of autospec zodat typefouten en verkeerde argumenten mislukken in plaats van stilzwijgend voorbij te gaan.

Uw functie roept een weer-API aan. Je test mag niet afhankelijk zijn van internet, een API-sleutel of het weer van morgen. Met de ingebouwde Python unittest.mock kun je die oproep vervangen door een stand-in die precies retourneert wat je kiest.

We hebben een kleine weather module en negen tests geschreven, allemaal uitgevoerd met de unittest van Python 3.13. Alle negen zijn geslaagd en de onderstaande afgedrukte resultaten zijn afkomstig van die run.

Waarom spotten?

  • Snelheid: geen netwerkoproepen of database-instellingen.
  • Betrouwbaarheid: tests mislukken niet omdat een service van derden niet beschikbaar is.
  • Controle: u kunt op verzoek time-outs, fouten en vreemde gegevens simuleren.
  • Isolatie: u test uw logica, niet de server van iemand anders.

Mocks horen vooral thuis in eenheidstests. Integratietests moeten nog steeds praten over echte of realistische afhankelijkheden.

Mock-basisprincipes

from unittest.mock import Mock

api = Mock()
api.get_price.return_value = 499

print(api.get_price("pen"))                # 499
api.get_price.assert_called_once_with("pen")
print(api.get_price.call_args_list)        # [call('pen')]
print(api.anything_at_all())               # <Mock name='mock.anything_at_all()' ...>

A Mock neemt elk gesprek op, zodat u kunt controleren hoe het werd gebruikt. Let echter op de laatste regel. Een eenvoudige Mock bedenkt elk attribuut waar je om vraagt. Die flexibiliteit is handig en ook gevaarlijk, zoals we zullen zien.

MagicMock is hetzelfde idee, maar ondersteunt ook “magische” methoden zoals __len__ en __iter__. patch() gebruikt standaard MagicMock.

side_effect: fouten en reeksen

side_effect bepaalt wat er bij elk gesprek gebeurt. Stel het in op een uitzondering om een fout te simuleren:

api.fetch.side_effect = ConnectionError("down")
api.fetch()   # raises ConnectionError

Stel het in op een lijst om bij opeenvolgende oproepen verschillende resultaten te retourneren. Dit is perfect voor het testen van de logica voor opnieuw proberen:

def fetch_with_retry(api, attempts=3):
    for _ in range(attempts):
        try:
            return api.fetch()
        except ConnectionError:
            continue
    raise ConnectionError("all attempts failed")

api = Mock()
api.fetch.side_effect = [ConnectionError("busy"), {"ok": True}]
print(fetch_with_retry(api))    # {'ok': True}
print(api.fetch.call_count)     # 2

De eerste oproep mislukte, de tweede slaagde en de test bewees dat de functie precies één keer opnieuw werd geprobeerd. Dit simuleren met een echte dienst zou vrijwel onmogelijk zijn.

patch(): vervang iets tijdelijk

Vaak wordt de afhankelijkheid niet doorgegeven. Deze wordt in de module geïmporteerd. patch() wisselt het uit voor de duur van een test en herstelt het daarna:

# weather.py
from http_client import get as requests_get

def report(city):
    data = requests_get(f"https://api.example.com/weather?city={city}")
    return f"{city}: {data['temp_c']}°C"
from unittest.mock import patch

with patch("weather.requests_get") as fake_get:
    fake_get.return_value = {"temp_c": 31}
    print(weather.report("Pune"))   # Pune: 31°C
    fake_get.assert_called_once_with(
        "https://api.example.com/weather?city=Pune")

Je kunt patch op deze manier als contextmanager gebruiken, of als decorateur, die de spot als argument doorgeeft.

Patch waar het wordt gebruikt, niet waar het is gedefinieerd

Dit is de fout die bijna iedereen wel eens maakt. De echte get-functie bevindt zich in http_client, dus het voelt natuurlijk aan om hem daar te patchen:

with patch("http_client.get") as fake_get:
    fake_get.return_value = {"temp_c": 99}
    weather.report("Pune")

# ConnectionError: no network in tests: https://api.example.com/weather?city=Pune

De echte functie liep. Toen weather.py from http_client import get as requests_get deed, creëerde het zijn eigen naam die naar de oorspronkelijke functie verwijst. Patching http_client.get veranderde een andere naam, dus weather heeft de nep nooit gezien. Onze http_client.get raist expres, dus de fout was luid. In een echt project kan het stilletjes een live API aanroepen.

Regel: patch de naam in de te testen module, geschreven als "module_under_test.name". Als de module import http_client deed en http_client.get() aanroept, dan is patchen "http_client.get" correct, omdat het opzoeken plaatsvindt tijdens de oproep.

spec en autospec: maak spot streng

Een eenvoudige Mock accepteert alles, inclusief typefouten. Met een specificatie staan alleen attributen toe die de echte klasse heeft:

api = Mock(spec=weather.WeatherClient)
api.get_temprature("Pune")
# AttributeError: Mock object has no attribute 'get_temprature'

loose = Mock()
loose.get_temprature("Pune")    # returns a Mock, no error at all

De losse mock accepteerde de verkeerd gespelde methode graag, dus een test die deze gebruikte, kon slagen, ook al zou de echte code crashen. autospec gaat verder en controleert de handtekeningen van de methoden:

with patch("weather.WeatherClient", autospec=True) as Fake:
    client = Fake.return_value
    client.get_temperature("Pune", "extra", "args")
# TypeError: too many positional arguments

Geef de voorkeur aan autospec=True of create_autospec() als je de spot drijft met je eigen lessen. Het houdt de spot eerlijk terwijl de echte code verandert.

Datums en tijden bespotten

Code die afhankelijk is van de datum van vandaag is moeilijk te testen. Verpak de klok in een kleine functie en patch die:

def today():
    return datetime.date.today()

def is_weekend():
    return today().weekday() >= 5

@patch("weather.today")
def test_date(self, fake_today):
    fake_today.return_value = datetime.date(2026, 11, 7)
    print(weather.is_weekend())   # True (a Saturday)

Je kunt datetime.date.today niet eenvoudig rechtstreeks patchen, omdat ingebouwde typen niet toestaan dat hun attributen worden ingesteld. Bibliotheken zoals Freezegun en Time Machine kunnen hier beter mee omgaan.

Beweringen over schijnvertoningen

Methode Cheques
assert_called() Minstens één keer gebeld
assert_called_once() Precies één keer gebeld
assert_called_with(...) Bij de laatste aanroep werden deze argumenten gebruikt
assert_called_once_with(...) Precies één telefoontje, met deze argumenten
assert_any_call(...) Bij elke aanroep werden deze argumenten gebruikt
assert_not_called() Nooit gebeld

Als een bewering mislukt, is de boodschap duidelijk. We hebben assert_called_once() aangeroepen op een methode die nooit is uitgevoerd:

AssertionError: Expected 'save' to have been called once. Called 0 times.

Oudere Python-versies laten verkeerd gespelde bevestigingsmethoden, zoals assret_called_once, stilletjes voorbij. Moderne versies geven een foutmelding bij namen die beginnen met assert of veelvoorkomende spelfouten daarvan, maar een specificatie is nog steeds de sterkste bescherming.

pytest en spot

Alles hierboven werkt ook in pytest. Veel pytest-gebruikers gebruiken ook de ingebouwde monkeypatch armatuur voor eenvoudige vervangingen, of de pytest-mock plug-in, die een mocker armatuur biedt die patches automatisch ongedaan maakt. De regel ‘patch waar deze wordt gebruikt’ is op allemaal van toepassing. Zie onze pytest-tutorial voor armaturen en configuratie.

Wanneer je niet moet spotten

  • Maak niet de spot met wat je test. Als de mock-up al het werk doet, bewijst de test niets.
  • Maak geen spot met eenvoudige waardeobjecten. Gebruik het echte werk.
  • Maak niet alles belachelijk. Tests vol schijnvertoningen mislukken vaak als de code wordt aangepast, zelfs als het gedrag correct is.
  • Houd een aantal echte integratietests bij. Mocks coderen uw aannames over een service, en die aannames kunnen verkeerd zijn.

Mocks verhogen ook gemakkelijk de codedekking, omdat nagebootste paden lopen zonder enig echt gedrag erachter.

Veelgemaakte spottende fouten

  • De verkeerde naam herstellen. Hierboven besproken, en nog steeds de belangrijkste oorzaak van “mijn spot wordt genegeerd”.
  • Vergeten return_value in te stellen. De geteste code ontvangt vervolgens een Mock-object in plaats van gegevens. Het kan dwars door je functie heen gaan en per ongeluk een test doorstaan, bijvoorbeeld als je alleen maar controleert of een uitslag waarheidsgetrouw is.
  • Beweren op de schijn in plaats van op de uitkomst. Controleren of save() is geroepen is zwakker dan controleren wat er is opgeslagen. Gebruik assert_called_once_with(...) met echte argumenten, of beter, beweer het resultaat van de functie.
  • Lekkende patches. Als je patcher.start() belt zonder stop(), blijft de nep op zijn plaats voor latere tests. Gebruik de contextmanager, de decorateur of addCleanup(patcher.stop).
  • Gestapelde decorateurs in de verkeerde volgorde. Bij meerdere @patch decorateurs wordt de onderste als eerste argument doorgegeven. Door ze in stilte door elkaar te halen, ontstaat de verkeerde schijnvertoning.
  • Een bibliotheek bespotten die je niet bezit in veel tests. Verpak het in een eigen kleine klas en bespot dat in plaats daarvan. Uw tests beschrijven vervolgens uw behoeften, niet de API van de bibliotheek.

Mocks, stubs en vervalsingen

Deze woorden worden vaak losjes gebruikt, maar de verschillen helpen bij het bespreken van tests. Een stub retourneert alleen standaardantwoorden. Een nep neemt ook gesprekken op, zodat je kunt verifiëren hoe deze is gebruikt. Een nep is een werkende, vereenvoudigde implementatie, zoals een in-memory repository in plaats van een database. Python’s Mock kunnen fungeren als een stomp of een schijnvertoning. Fakes zijn gewone klassen die u zelf schrijft, en ze leiden vaak tot tests die refactoring beter overleven, omdat ze gedrag controleren in plaats van exacte oproepen.

Een goede vuistregel is om de randen van uw systeem te bespotten: het netwerk, de klok, willekeurige getallen, het bestandssysteem en betaalde services van derden. Binnen die randen geef je de voorkeur aan echte objecten. Op deze manier gebouwde tests blijven snel en gebruiken nog steeds de code die u daadwerkelijk hebt geschreven.

Tot slot

Mocking is een nauwkeurig hulpmiddel: vervang langzame of onvoorspelbare afhankelijkheden, controleer wat ze retourneren en controleer hoe uw code ze heeft gebruikt. Twee gewoonten voorkomen de meeste problemen. Patch de naam in de module die wordt getest en gebruik autospec zodat uw nep-oproepen worden afgewezen die het echte object zou afwijzen.

Veelgestelde vragen

Wat is spotten in Python?

Mocking vervangt een echt object, zoals een API-client of database, door een nepobject dat je tijdens een test bestuurt. Python neemt dit op in de unittest.mock-module.

Wat is het verschil tussen Mock en MagicMock?

MagicMock is een Mock die ook magische methoden ondersteunt zoals __len__, __iter__ en __enter__. patch() maakt standaard een MagicMock.

Waarom werkt mijn patch niet?

Meestal omdat u hebt gepatcht waar het object is gedefinieerd in plaats van waar het wordt gebruikt. Patch de naam in de te testen module.

Wat is het verschil tussen return_value en side_effect?

return_value stelt een vast resultaat in. side_effect kan een uitzondering genereren, waarden uit een lijst in volgorde retourneren of een functie aanroepen.

Wat doet autospec?

Het bouwt een namaak van het echte object op, dus ontbrekende attributen en verkeerde argumenten veroorzaken fouten, net zoals ze dat met de echte code zouden doen.

Blijf leren

Werk met Stack Quora

Hulp nodig met testen, WordPress of SEO?

We helpen teams betere software te leveren en hun sites te laten groeien. Vertel ons waar u mee bezig bent en wij stellen een duidelijke volgende stap voor.

Blijf lezen

Meer handleidingen over testen, AI en moderne ontwikkeling

Genoten van dit artikel? Kies een onderwerp en ga direct naar onze nieuwste tutorials.

Kunstmatige intelligentie

AI-hallucinaties uitgelegd: waarom taalmodellen dingen verzinnen en hoe ze te verminderen

AI-hallucinaties uitgelegd: wat ze zijn, waarom taalmodellen ze produceren, veelvoorkomende typen, hoe je ze kunt verminderen met aarding en aanwijz…

Lees artikel →
DevOps

Omgevingsvariabelen uitgelegd: .env-bestanden, geheimen en configuratie goed gedaan

Omgevingsvariabelen uitgelegd met Python-voorbeelden die we hebben uitgevoerd: ze lezen en instellen, waarom elke waarde een string is, .env-bestand…

Lees artikel →
Softwareontwikkeling

API-snelheidsbeperking uitgelegd: algoritmen, headers en afhandeling van 429-fouten

API-snelheidsbeperking uitgelegd met een simulatie die we hebben uitgevoerd: token-bucket, vast venster, schuifvenster, 429 reacties, headers van sn…

Lees artikel →
Testautomatisering

Spotten in Python: unittest.mock, patch en de fouten die tests doen liegen

Spotten in Python met unittest.mock, getest: Mock en return_value, side_effect, patchen waar een naam wordt gebruikt, spec en autospec, spotdatums e…

Lees artikel →
Softwaretesten

Codedekking uitgelegd: lijn versus filiaaldekking en wat 100% werkelijk betekent

Codedekking uitgelegd met een echte dekking.py-run: dekking van lijnen, vertakkingen en condities, een rapport lezen, waarom 100% dekking nog steeds…

Lees artikel →
Databases & SQL

DELETE versus TRUNCATE versus DROP in SQL: verschillen, terugdraaien en wanneer elk te gebruiken

DELETE versus TRUNCATE versus DROP uitgelegd met geteste SQL: wat elk verwijdert, terugdraaien, triggers, identiteitsreset, snelheid en hoe PostgreS…

Lees artikel →
Databases & SQL

SQL-weergaven uitgelegd: creëer, gebruik en vermijd de valkuilen, met voorbeelden

SQL-weergaven uitgelegd met query's die we hebben uitgevoerd: een weergave maken, live resultaten, kolommen verbergen, prestaties, bijwerkbare en ge…

Lees artikel →
Softwareontwikkeling

TypeScript-interface versus type: echte verschillen, getest met de compiler

TypeScript-interface versus type, gecontroleerd met de echte compiler: wat beide kunnen doen, wat slechts één kan doen, samenvoegen van declaraties,…

Lees artikel →
Softwareontwikkeling

Python *args en **kwargs uitgelegd: flexibele functies met duidelijke voorbeelden

Python *args en **kwargs uitgelegd met code die we hebben uitgevoerd: in- en uitpakken, parametervolgorde, argumenten die alleen trefwoorden en posi…

Lees artikel →
Softwareontwikkeling

Python Lambda-functies: syntaxis, echt gebruik en wanneer u ze niet moet gebruiken

Python lambda-functies uitgelegd met code die we hebben uitgevoerd: syntaxis, sorteren met sleutel, kaart en filter, sluitingen, de laat-bindende va…

Lees artikel →
Kunstmatige intelligentie

Wat zijn AI-agenten? Hoe ze werken, met een eenvoudige agentloop die u kunt uitvoeren

Wat zijn AI-agenten? Een duidelijke gids voor de agentenlus, tools, geheugen en vangrails, met een kleine Python-lus die we hebben uitgevoerd, echte…

Lees artikel →
DevOps

Monitoring versus waarneembaarheid: het echte verschil, met logboeken, statistieken en sporen

Monitoring versus waarneembaarheid uitgelegd: bekende en onbekende fouten, logboeken, statistieken en sporen, een walkthrough voor afrekenincidenten…

Lees artikel →
DevOps

Docker-volumes versus bindingsmounts: bewaar containergegevens op de juiste manier

Docker-volumes versus bind-mounts uitgelegd: waarom containergegevens verdwijnen, benoemde volumes, bind-mounts, tmpfs, Compose-voorbeelden, back-up…

Lees artikel →
Testautomatisering

TDD versus BDD: belangrijkste verschillen, één functie die op beide manieren is gebouwd en wanneer deze moet worden gebruikt

TDD versus BDD uitgelegd met één kortingsfunctie die in beide richtingen is gebouwd: rood-groen-refactor met code die we hebben uitgevoerd, gegeven-…

Lees artikel →
Softwareontwikkeling

Git merge vs rebase: verschillen, voorbeelden en wanneer je wat gebruikt

Git merge vs rebase uitgelegd met echte logoutput, een vergelijkingstabel, de gouden regel van rebase en een veilige teamwerkwijze.

Lees artikel →
Softwareontwikkeling

Python list comprehension: syntaxis, voorbeelden en wanneer je het vermijdt

Python list comprehension uitgelegd: syntaxis, filteren, if-else, geneste lussen, gemeten tijden en wanneer een gewone for-lus beter is.

Lees artikel →
Databases & SQL

SQL JOIN uitgelegd met voorbeelden (Inner, Left, Right, Full)

Begrijp SQL joins stap voor stap met voorbeeldtabellen en queries: INNER, LEFT, RIGHT, FULL, CROSS en SELF, plus veelgemaakte fouten en prestatietips.

Lees artikel →