Bedingter Import für Unit-Tests

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
ThomasT
User
Beiträge: 6
Registriert: Dienstag 18. September 2018, 13:59

Donnerstag 18. Oktober 2018, 22:37

Hallo,

ich habe ein Python Modul aaa mit (vereinfacht) einer Klasse AAA und einer globalen Variable.
In der Klasse wird ein anderes Modul benutzt. Z.B. pymqi, das oben importiert wird.

Will ich diese Klasse unit testen, dann schreibe ich eine Datei mit dem Unit-Test und importiere das Modul aaa.

Wie kann ich erreichen, dass für den Test pymqi nicht importiert wird? Denn es wird sonst knallen, wenn die shared libs fehlen.

Wie kann ich im Modul aaa die globale Variable (z.B. eine Konfiguration) setzen?

Was sind "best practices"?

Gruß Thomas
__deets__
User
Beiträge: 3693
Registriert: Mittwoch 14. Oktober 2015, 14:29

Donnerstag 18. Oktober 2018, 23:02

Das Stichwort dafür heißt dependency injection. Du kannst auf verschiedene Arten vorgehen:

- dein Modul aaa importiert pymqi NICHT. Sondern bekommt das Modul als Argument. Dadurch kannst du das mocken.
- du mocks das Modul durch ein pymqi-Mock-Modul das im sys.path vor dem original liegt, und dadurch zuerst (und alleinig) importiert wird.
ThomasT
User
Beiträge: 6
Registriert: Dienstag 18. September 2018, 13:59

Freitag 19. Oktober 2018, 00:00

Wie meinst du die erste Variante?

Grüße Thomas
Sirius3
User
Beiträge: 8593
Registriert: Sonntag 21. Oktober 2012, 17:20

Freitag 19. Oktober 2018, 09:17

Also, die zweite Methode ist das, was man normalerweise macht, ob jetzt über sys.path oder sys.modules, ist zweitrangig. Dafür gibt es normalerweise schon fertige Funktionen: https://docs.pytest.org/en/latest/monkeypatch.html

Globale Variablen sollte es in Modulen nicht geben. Für "Konstanten" die in komplexeren Funktionsaufrufen generiert werden, kann man genauso monkey-patching benutzen, in dem man die Funktion überschreibt:

Code: Alles auswählen

import database
configuration = database.get_config_from_database()
im Test:

Code: Alles auswählen

import database
def test_get_config():
    return {'abc': 5}
database.get_config_from_database = test_get_config
import module_to_test
Hier muß man natürlich aufpassen, dass das Monkey-Patching schon vorher stattfindet, oder eben gleich das ganze Module `database` durch ein mockup erstetzen:

Code: Alles auswählen

import sys
import mock_database
sys.modules['database'] = mock_database
ThomasT
User
Beiträge: 6
Registriert: Dienstag 18. September 2018, 13:59

Freitag 19. Oktober 2018, 11:48

Hi,

in thomastest.py

Code: Alles auswählen

#!/usr/bin/python
import sys
import unittest
import ttmock

sys.modules['springpython.jms.core'] = ttmock

import thomas
In thomas.py

Code: Alles auswählen

#!/usr/bin/python
from springpython.jms.core import JmsTemplate
from springpython.jms.factory import WebSphereMQConnectionFactory
Und wenn ich thomastest.py start bekomme ich diese Exception:

Code: Alles auswählen

Traceback (most recent call last):
  File "thomastest.py", line 9, in <module>
    import thomas
  File "/cygdrive/e/Projekte/python/thomas.py", line 38, in <module>
    from springpython.jms.core import JmsTemplate
ImportError: No module named springpython.jms.core
__deets__
User
Beiträge: 3693
Registriert: Mittwoch 14. Oktober 2015, 14:29

Freitag 19. Oktober 2018, 11:57

Wo geht das nicht. In sys.modulues sind nur toplevel Module. Du musst also ein springpython-Modul mocken, das wiederum die anderen untermodule/Pakete kennt.
ThomasT
User
Beiträge: 6
Registriert: Dienstag 18. September 2018, 13:59

Freitag 19. Oktober 2018, 13:06

Ich dachte das sei einfach ein String-Lookup in erster Linie.
Anscheinend weiss ich nicht genug darüber, wie Modulimports funktionieren.
Antworten