[Python] return value van mock uitlezen

Pagina: 1
Acties:

Vraag


  • DHH
  • Registratie: Augustus 2014
  • Laatst online: 07-09-2024
Ik ben bezig met een projectje waarin ik Excel bestanden lees via Python 3.6 met xlwings. Ik wil graag meer leren van TDD en wil dat deze tests kunnen draaien zonder Excelbestanden te hoeven openen d.m.v. mocks.

Dit werkt:
Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from unittest import mock
import xlwings as xw


def read_content(filename, sheet_name, cell):
    """Opens Excel file and returns the content of 'cell' in 'sheet_name'"""
    app = xw.App(visible=False)
    wb = xw.Book(filename, read_only=True)
    ws = wb.sheets[sheet_name]
    val = ws[cell].value
    try:
        app.quit()
    except:
        pass
    return val


print(f'Real call: {read_content(r"Tests/Sample Excel file.xlsx", "Sheet1", "A1")}')
# Real call: actual content of cell A1


Maar het testen geeft me niet het gewenste resultaat:
Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
@mock.patch("xlwings.Book")
@mock.patch("xlwings.App")
def test_read_content(patched_app, patched_book):
    patched_app.return_value = None
    patched_book.sheets["some_sheet"].return_value["A1"].value.return_value = "x"
    print(f'Mocked call: {read_content("something.xlsx", "some_sheet", "A1")}')


test_read_content()
### Returns:
# Mocked call: <MagicMock name='Book().sheets.__getitem__().__getitem__().value' id='19605530263104'>
### Expected:
# Mocked call: x


Ik zou graag de waarde 'x' zien i.p.v. de <MagicMock>. Een 'normale' functie mocken lukt me wel, maar xlwings maakt het wat complexer met de workbooks, sheets en value attributes.
Voor m'n gevoel zit ik er heel dicht bij, maar mis ik net even het laatste stukje. Heeft iemand een hint?

Alvast bedankt!

Alle reacties


Acties:
  • 0 Henk 'm!

  • DevWouter
  • Registratie: Februari 2016
  • Laatst online: 17:33

DevWouter

Creator of Todo2d.com

Ik ben niet bekend met python, maar wel unit testen. Mijn eerste advies is om je code kleiner te maken (en om asserts/expect te gebruiken ipv printf).

Jouw huidige functie doet namelijk het volgende: Aanmaken app, workbook lezen, worksheet ophalen en cell ophalen en waarde van cell uitlezen. Terwijl in jouw test code die stappen niet allemaal gedaan worden.

Een bekende afkorting is AAA: Arrange, Act and Assert

Mijn aanpak: Maak een functie voor alleen die laatste stap (een waarde uit een cel lezen) en test of dat werkt. Zelfs als dat niet werkt haalt dat veel ruis van de lijn af. Uiteindelijk heb je dan 5 kleine functies waar jij vertrouwen in hebt en vervolgens één grote functie waarbij je checkt of het jouw 5 kleine functies op de juiste manier en volgorde aanroept.

En oh... TDD betekent dat je eerst je test schrijft en dan je code. Kijkend naar de code weet ik vrij zeker dat je andersom hebt gedaan ;)

"Doubt—the concern that my views may not be entirely correct—is the true friend of wisdom and (along with empathy, to which it’s related) the greatest enemy of polarization." -- Václav Havel


Acties:
  • 0 Henk 'm!

  • DHH
  • Registratie: Augustus 2014
  • Laatst online: 07-09-2024
Ik zie dat m'n verkapte 'bump' tot een reactie leidt :+ , bedankt @DevWouter!
DevWouter schreef op vrijdag 26 november 2021 @ 15:28:
Ik ben niet bekend met python, maar wel unit testen. Mijn eerste advies is om je code kleiner te maken (en om asserts/expect te gebruiken ipv printf).

Jouw huidige functie doet namelijk het volgende: Aanmaken app, workbook lezen, worksheet ophalen en cell ophalen en waarde van cell uitlezen. Terwijl in jouw test code die stappen niet allemaal gedaan worden.
Ah, misschien dat dit in andere talen anders werkt, maar ik meende dat de stappen namelijk wel worden uitgevoerd. Immers is de laatste stap van mijn test-functie namelijk het aanroepen van de oorspronkelijke 'read_content()'.

De regels daarvóór moeten alleen voorkomen dat het daadwerkelijke Excel-bestand wordt geopend maar dat er fictieve waarden worden opgehaald, dus in goed Nederlands: "Stel dat ik een Excel bestand heb met waarde 'x' in cel A1, krijg ik dan 'x' als uitkomst van functie read_content()?". Ik was in de veronderstelling dat mocks zo werken.

Natuurlijk heb je gelijk dat de uiteindelijke functie met asserts zal moeten werken. Ik gebruikte print nu even om te zien wat de uitkomst is, maar dat is dus de <MagicMock...> ipv 'x'.
DevWouter schreef op vrijdag 26 november 2021 @ 15:28:
Mijn aanpak: Maak een functie voor alleen die laatste stap (een waarde uit een cel lezen) en test of dat werkt. Zelfs als dat niet werkt haalt dat veel ruis van de lijn af. Uiteindelijk heb je dan 5 kleine functies waar jij vertrouwen in hebt en vervolgens één grote functie waarbij je checkt of het jouw 5 kleine functies op de juiste manier en volgorde aanroept.
Ah, ik denk dat daar het probleem zit voor mij: vanwege de werking van xw.App / xw.Book / xw.Book.sheets vind ik het lastig om deze functie op te delen in kleine functies. Ik zie zo even niet hoe ik een stand-alone test kan maken die de ws[cell].value aanroept: ik weet niet hoe ik alleen een workbook.sheets-object kan mocken zonder het op deze manier te doen.

Overigens werkt de functie prima (read_content() geeft inderdaad de juiste waarde voor de gevraagde combinatie van sheet/cell, ik wil 'm alleen opnemen in een test.
DevWouter schreef op vrijdag 26 november 2021 @ 15:28:
En oh... TDD betekent dat je eerst je test schrijft en dan je code. Kijkend naar de code weet ik vrij zeker dat je andersom hebt gedaan ;)
Haha, touché. Alleen om tests te kunnen schrijven moet je wel weten wát je moet schrijven. Als beginner/medior vind ik dit het lastigste deel van TDD leren.

Nogmaals dank voor je moeite!

Acties:
  • 0 Henk 'm!

  • DevWouter
  • Registratie: Februari 2016
  • Laatst online: 17:33

DevWouter

Creator of Todo2d.com

DHH schreef op vrijdag 26 november 2021 @ 17:42:
Ik zie dat m'n verkapte 'bump' tot een reactie leidt :+ , bedankt @DevWouter!
Ik heb niks gezien :+
Ah, misschien dat dit in andere talen anders werkt, maar ik meende dat de stappen namelijk wel worden uitgevoerd. Immers is de laatste stap van mijn test-functie namelijk het aanroepen van de oorspronkelijke 'read_content()'.
Je stappen worden uitgevoerd, want dat bewijst jouw handmatig test (wat trouwens ook een unit test is).
De regels daarvóór moeten alleen voorkomen dat het daadwerkelijke Excel-bestand wordt geopend maar dat er fictieve waarden worden opgehaald, dus in goed Nederlands: "Stel dat ik een Excel bestand heb met waarde 'x' in cel A1, krijg ik dan 'x' als uitkomst van functie read_content()?". Ik was in de veronderstelling dat mocks zo werken.
Een mock is een replica of een imitatie. Zeker bij grotere en complexere systeem waar je veel afhankelijkheden wil je niet continue alle onderliggende systemen testen. Dan zet je namelijk een replica in die geen onverwacht gedrag vertoont.
Natuurlijk heb je gelijk dat de uiteindelijke functie met asserts zal moeten werken. Ik gebruikte print nu even om te zien wat de uitkomst is, maar dat is dus de <MagicMock...> ipv 'x'.
Dat was duidelijk ;)
Ah, ik denk dat daar het probleem zit voor mij: vanwege de werking van xw.App / xw.Book / xw.Book.sheets vind ik het lastig om deze functie op te delen in kleine functies. Ik zie zo even niet hoe ik een stand-alone test kan maken die de ws[cell].value aanroept: ik weet niet hoe ik alleen een workbook.sheets-object kan mocken zonder het op deze manier te doen.
Hint: Probeert een test te bedenken die het zonder mocks doet.
spoiler: antwoord
Je kan ook test-files hebben die vervolgens ingeladen wordt en waar jij het antwoord al van weet.


De reden voor het opsplitsen is om ervoor te zorgen dat je er achter komt waar het precies fout gaat. Zoals ik zei doet jouw code 5 dingen.
Zie het als volgt: Als jouw computer niet aangaat, is dat omdat de hele computer kapot is of een klein onderdeel? En hoe kom je er achter welk onderdeel dat exact is?
Overigens werkt de functie prima (read_content() geeft inderdaad de juiste waarde voor de gevraagde combinatie van sheet/cell, ik wil 'm alleen opnemen in een test.
Dus een handmatig waarmee je al bewezen dat het werkt.
Haha, touché. Alleen om tests te kunnen schrijven moet je wel weten wát je moet schrijven. Als beginner/medior vind ik dit het lastigste deel van TDD leren.
TDD staat voor Test Driven Design. In andere woorden: Hoe jij je code gaat testen bepaalt hoe jij de code schrijft. Vaak kom je bij het uitdenken van je test al tot een conclusie dat iets niet werkt. Het is dus meer een methode om code te ontwerpen dan om te bewijzen dat het werkt.
Probeer maar voor de grap een rekenmachine te schrijven (een hele simpele) puur TDD en voor en na elke test die je schrijft nadenkt over welke code je kan weghalen.

Wat betreft jouw specifieke probleem, kan ik je helaas niet verder helpen.

"Doubt—the concern that my views may not be entirely correct—is the true friend of wisdom and (along with empathy, to which it’s related) the greatest enemy of polarization." -- Václav Havel