En introduktion till Mocking i Python

hur man kör enhetstester i Python utan att testa ditt tålamod

oftare än inte, interagerar programvaran vi direkt med vad vi skulle märka som ”smutsiga” tjänster. I lekmannatermer: tjänster som är avgörande för vår ansökan, men vars interaktioner har avsett men oönskade biverkningar-det vill säga oönskade i samband med en autonom testkörning.,Facebook Facebook-funktionen.

till exempel: kanske skriver vi en social app och vill testa vår nya ”Post till Facebook-funktionen”, men vill inte faktiskt lägga till Facebook varje gång vi kör vår testsvit.

Pythonunittest biblioteket innehåller en underpaket som heterunittest.mock—eller om du förklarar det som ett beroende, helt enkeltmock—vilket ger extremt kraftfulla och användbara medel för att håna och stubba ut dessa oönskade biverkningar.,

notera:mock ingår nyligen i standardbiblioteket från och med Python 3.3; tidigare distributioner måste använda Mock library nedladdningsbara via PyPI.

system Calls vs. Python Mocking

för att ge dig ett annat exempel, och en som vi ska köra med för resten av artikeln, överväga systemsamtal., Det är inte svårt att se att det här är främsta kandidater för mocking: oavsett om du skriver ett skript för att mata ut en CD-enhet, en webbserver som tar bort föråldrade cachefiler från /tmp eller en socket-server som binder till en TCP-port, har alla oönskade biverkningar i samband med dina enhetstester.

som utvecklare bryr du dig mer om att ditt bibliotek framgångsrikt kallade systemfunktionen för att mata ut en CD i stället för att uppleva ditt CD-fack öppet varje gång ett test körs.,

som utvecklare bryr du dig mer om att ditt bibliotek framgångsrikt kallade systemfunktionen för att mata ut en CD (med rätt argument etc.) i motsats till faktiskt upplever din CD tray öppen varje gång ett test körs. (Eller värre, flera gånger, eftersom flera tester refererar till utmatningskoden under en enda enhet-testkörning!)

På samma sätt innebär det att hålla din enhetstest effektiv och prestanda att hålla så mycket ”långsam kod” ur de automatiserade testkörningarna, nämligen filsystem och nätverksåtkomst.,

för vårt första exempel kommer vi att refactor ett standard Python-testfall från originalform till en med mock. Vi ska visa hur skriva ett testfall med hånar kommer att göra våra tester smartare, snabbare och kunna avslöja mer om hur programvaran fungerar.

en enkel borttagningsfunktion

vi behöver alla ta bort filer från vårt filsystem från tid till annan, så låt oss skriva en funktion i Python som gör det lite lättare för våra skript att göra det.,

#!/usr/bin/env python# -*- coding: utf-8 -*-import osdef rm(filename): os.remove(filename)

Självklart ger vårrm – metod vid denna tidpunkt inte mycket mer än den underliggandeos.remove – metoden, men vår kodbas kommer att förbättras, så att vi kan lägga till fler funktioner här.

låt oss skriva ett traditionellt testfall, dvs utan hånar:

vårt testfall är ganska enkelt, men varje gång det körs skapas en tillfällig fil och raderas sedan., Dessutom har vi inget sätt att testa om vår rm metod korrekt skickar argumentet ner till os.remove samtal. Vi kan anta att det gör baserat på testet ovan, men mycket är kvar att önska.

Refactoring med Python Mocks

Låt oss refactor vårt testfall medmock:

med dessa refaktorer har vi fundamentalt förändrat hur testet fungerar. Nu har vi en insider, ett objekt som vi kan använda för att verifiera funktionaliteten hos en annan.,

potentiella Python gäckande fallgropar

en av de första saker som bör sticka ut är att vi användermock.patch metod dekoratör för att håna ett objekt som ligger påmymodule.os, och injicera att håna in i vår testfall metod. Skulle det inte vara mer meningsfullt att bara hånaos själv, snarare än hänvisningen till den på mymodule.os?

Tja, Python är något av en snygg orm när det gäller import och hantering av moduler., Vid körning har mymodule – modulen sin egen os som importeras till sin egen lokala räckvidd i modulen. Således, om vi håna os, vi kommer inte att se effekterna av mock i mymodule modul.

mantrat att fortsätta upprepa är detta:

håna ett objekt där det används, inte där det kom ifrån.,

om du behöver mocka modulentempfile förmyproject.app.MyElaborateClass måste du förmodligen använda mocken tillmyproject.app.tempfile, eftersom varje modul behåller sin egen import.

med denna fallgrop ur vägen, låt oss hålla hånfulla.

att lägga till validering till ’rm’

rm – metoden som definierats tidigare är ganska förenklad. Vi vill ha det validera att en sökväg finns och är en fil innan bara blint försöker ta bort den., Låt oss refactor rm vara lite smartare:

#!/usr/bin/env python# -*- coding: utf-8 -*-import osimport os.pathdef rm(filename): if os.path.isfile(filename): os.remove(filename)

bra. Låt oss justera vårt testfall för att hålla täckningen uppe.

vårt testparadigm har helt förändrats. Vi kan nu verifiera och validera intern funktionalitet av metoder utan några biverkningar.

File-Removal as a Service with Mock Patch

hittills har vi bara arbetat med att leverera mocks för funktioner, men inte för metoder på objekt eller fall där mocking är nödvändigt för att skicka parametrar. Låt oss täcka objektmetoder först.,

vi börjar med en refactor avrm – metoden i en serviceklass. Det finns verkligen inte ett motiverat behov, i sig, att inkapsla en så enkel funktion i ett objekt, men det kommer åtminstone att hjälpa oss att visa nyckelbegrepp i mock. Låt oss refactor:

Du kommer märka att inte mycket har förändrats i vårt testfall:

bra, så vi vet nu attRemovalService fungerar som planerat., Låt oss skapa en annan tjänst som förklarar det som ett beroende:

eftersom vi redan har testtäckning påRemovalService kommer vi inte att validera den interna funktionaliteten hosrm – metoden i våra tester avUploadService. Snarare testar vi helt enkelt (utan biverkningar, förstås) att UploadService anropar RemovalService.rm-metoden, som vi vet ”just works™” från vårt tidigare testfall.

det finns två sätt att gå om detta:

  1. Mock ut RemovalService.rm – metoden själv.,
  2. leverera en hånad instans i konstruktören avUploadService.

eftersom båda metoderna ofta är viktiga vid enhetstestning granskar vi båda.

alternativ 1: Mocking Instance Methods

mock biblioteket har en speciell metod dekoratör för mocking object instance metoder och egenskaper, @mock.patch.object dekoratör:

bra! Vi har validerat attUploadService framgångsrikt anropar vår instansrm metod. Ser du nåt intressant där inne?, Patchmekanismen ersatte faktisktrm – metoden för allaRemovalService – instanser i vår testmetod. Det betyder att vi faktiskt kan inspektera instanserna själva. Om du vill se mer, försök att släppa in en brytpunkt i din mocking kod för att få en bra känsla för hur lappmekanismen fungerar.

Mock Patch Pitfall: Decorator Order

När du använder flera dekoratörer på dina testmetoder är ordning viktig, och det är ganska förvirrande. I grund och botten, när man kartlägger dekoratörer till metodparametrar, arbeta bakåt., Tänk på det här exemplet:

Lägg märke till hur våra parametrar matchas med dekoratörernas omvänd ordning? Det beror delvis på hur Python fungerar. Med flera metod dekoratörer, här är körningsordningen i pseudocode:

patch_sys(patch_os(patch_os_path(test_something)))

eftersom patchen till sys är den yttersta patchen, kommer den att utföras sist, vilket gör den till den sista parametern i den faktiska testmetoden argument. Notera detta väl och använda en debugger när du kör dina tester för att se till att rätt parametrar injiceras i rätt ordning.,

alternativ 2: skapa Mock instanser

i stället för att håna den specifika instansmetoden, kunde vi istället bara leverera en hånad instans till UploadService med sin konstruktör. Jag föredrar alternativ 1 ovan, eftersom det är mycket mer exakt, men det finns många fall där Alternativ 2 kan vara effektivt eller nödvändigt. Låt oss refactor vårt test igen:

i det här exemplet har vi inte ens behövt lappa någon funktionalitet, vi skapar helt enkelt en automatisk spec för klassen RemovalService och injicerar sedan denna instans i vår UploadService för att validera funktionaliteten.,

metodenmock.create_autospec skapar en funktionellt likvärdig instans till den angivna klassen. Vad detta innebär, praktiskt taget, är att när den återvändande instansen interagerar med, kommer det att ta upp undantag om de används på olagliga sätt. Mer specifikt, om en metod kallas med fel antal argument, kommer ett undantag att höjas. Detta är oerhört viktigt när reaktorer händer. Som ett bibliotek ändras, tester bryta och som förväntas. Utan att använda en automatisk spec kommer våra tester fortfarande att passera även om den underliggande implementeringen är trasig.,

fallgrop:mock.Mock ochmock.MagicMock klasser

biblioteketmock innehåller också två viktiga klasser på vilka det mesta av den interna funktionaliteten är byggd på:mock.Mock ochmock.MagicMock -instans, en mock.MagicMock-instans, eller en automatisk spec, favoriserar du alltid med en automatisk spec, eftersom det hjälper till att hålla dina tester friska för framtida ändringar., Detta beror på attmock.Mock ochmock.MagicMock accepterar alla metodsamtal och egendomstilldelningar oavsett underliggande API. Tänk på följande användningsfall:

class Target(object): def apply(value): return valuedef method(target, value): return target.apply(value)

Vi kan testa detta med en mock.Mock instans så här:

denna logik verkar förnuftig, men låt oss ändra metoden Target.apply för att ta fler parametrar:

class Target(object): def apply(value, are_you_sure): if are_you_sure: return value else: return None

Re-kör ditt test, och du kommer att upptäcka att det fortfarande passerar. Det beror på att det inte är byggt mot din faktiska API., Det är därför du alltid ska användacreate_autospec – metoden ochautospec – parametern med@patch och@patch.object dekoratörer.Facebook Facebook API Call

för att avsluta, låt oss skriva en mer tillämplig verkliga python mock exempel, en som vi nämnde i inledningen: posta ett meddelande till Facebook.

Python Mock exempel: Mocking en Facebook API Call

för att avsluta, låt oss skriva en mer tillämplig verkliga python mock exempel, en som vi nämnde i inledningen: posta ett meddelande till Facebook. Vi skriver en fin omslagsklass och ett motsvarande testfall.,

här är vårt testfall, som kontrollerar att vi skickar meddelandet utan att faktiskt skicka meddelandet:

som vi har sett hittills är det väldigt enkelt att börja skriva smartare test med mock I Python.

slutsats

Pythonsmock bibliotek, om lite förvirrande att arbeta med, är en spelväxlare för enhetstestning. Vi har visat gemensamma användningsfall för att komma igång med mock I enhetstestning, och förhoppningsvis kommer den här artikeln att hjälpa Python-utvecklare att övervinna de första hindren och skriva utmärkt, testad kod.,

Share

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *