Unittests (Best practices)

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
Pekh
User
Beiträge: 482
Registriert: Donnerstag 22. Mai 2008, 09:09

Hallo zusammen,

ich brauche mal wieder Anregungen bezüglich Unittests. Und zwar habe ich ein Package von sich untereinander importierenden Modulen. Für jedes Modul existiert ein Testmodul in der ich die TestCases definiere. Dann gibt es noch ein Modul 'runtests', in dem ich alle Testmodule importiere und in eine Suite einfüge, die dann ausgeführt wird.

Das hat bislang ganz gut funktioniert. Mit dem erstgenannten Package bin ich aber in Probleme gerannt, weil ich in den einzelnen Testmodulen Namen manipuliere um Funktionalitäten möglichst unabhängig von anderen Modulen testen zu können. Ein Beispiel: Der Testkandidat importiert ein Modul 'av3', welches z.B. Schnittstellen zur Programmkonfiguration ('config') und zur abgefragten Datenbank ('db') enthält. Um jetzt einfacher testen zu können, ersetze ich testkandidat.av3.config durch ein Mockobjekt. Wenn ich jetzt nicht höllisch aufpasse und diesen 'Patch' nach Ende der Tests (in diesem Modul) rückgängig mache, beeinflußt mir diese Änderung unter Umständen eines der späteren Testmodule. Und nicht immer ist es mit einem einfachen reload(av3) getan.

Was mich jetzt wiederum zum Nachdenken angeregt hat: Ist es wirklich so sinnvoll, importierte Module in der oben beschriebenen Weise zu manipulieren? Vielleicht ist mein Denkansatz ja völlig falsch? Ich finde im Netz leider wenig zu Best practices. Wie geht ihr in solchen Fällen vor?

Schönen Dank schon mal

Edit: Titel und Rechtschreibung
CM
User
Beiträge: 2464
Registriert: Sonntag 29. August 2004, 19:47
Kontaktdaten:

Pekh hat geschrieben:Was mich jetzt wiederum zum Nachdenken angeregt hat: Ist es wirklich so sinnvoll, importierte Module in der oben beschriebenen Weise zu manipulieren?
Nein. Du sagst ja selber, dass Du "höllisch" aufpassen mußt, um da noch Übersicht zu halten. Dann findest Du ja schon, dass es nicht optimal ist. Also gibt es einen sinnvolleren Weg. (Tolle Logik, oder? ;-) )
Pekh hat geschrieben:Vielleicht ist mein Denkansatz ja völlig falsch?
Völlig falsch. Na ja, das vielleicht nicht ...
Pekh hat geschrieben:Wie geht ihr in solchen Fällen vor?
Ganz ähnlich. Deine Beschreibung des Testsuiteaufbaus finde ich gar nicht mal so schlecht - eigentlich völlig normal. Nur das mit dem Mockobjekt verstehe ich nicht: Ist das wirklich notwendig? Wirklich? Reicht die Trennung der Namespaces durch die versch. Testmodule nicht aus? Dann ist es vielleicht Zeit zum Refactoring. Oder sonst lass doch mal ein solches Testmodul sehen: Vielleicht kommt uns eine gute Idee, wo das Problem zu suchen ist.

HTH
Christian
Pekh
User
Beiträge: 482
Registriert: Donnerstag 22. Mai 2008, 09:09

CM hat geschrieben: Nur das mit dem Mockobjekt verstehe ich nicht: Ist das wirklich notwendig? Wirklich? Reicht die Trennung der Namespaces durch die versch. Testmodule nicht aus?
Das mit der Notwendigkeit ist die Frage, die mich seit Tagen beschäftigt. Aber ich drehe mich irgendwie immer im Kreis und komme irgendwie zu keiner wirklich besseren Lösung.

Ganz typisch sind bei mir im Moment solche Konstrukte, natürlich mehr oder weniger angepaßt:

Code: Alles auswählen

import unittest

import mock
from sqlalchemy.exceptions import OperationalError

import av3

class TestAV3(unittest.TestCase):

    def setUp(self):
        cd = mock.Mock()
        cd.get_var.return_value = u"sqlite:///tests/testdaten/av3_test.sqlite3"
        av3.config = cd

    def tearDown(self):
        reload(av3.alchemist)
        reload(av3)
Problematisch wird es zum Beispiel dann, wenn ich wie oben z.B. den Alchemisten neu laden muß, aber andere von av3 importierte Module auf den Klassen des Alchemisten aufsetzen (Pluginsystem und polymorphe Mappings für SQLAlchemy :roll: ) Dann muß ich praktisch alle Module, die den Alchemisten verwenden, neu laden. Und das ist schnell nicht mehr nachvollziehbar.

Noch mal zur Verdeutlichung:
av3 importiert alchemist und plugins (als Package organisiert)
die Plugins sind wiederum Module bzw. Packages mit definierten Eigenschaften und können z.B. Klassen für polymorphe Mappings bereitstellen. Dazu müssen sie ebenfalls den Alchemisten importieren (weil da die Ur-Klasse drinsteckt)

Wenn ich jetzt den Alchemisten neu laden, weil ich im Test was manipuliert habe, dann breche ich mir die Subclass-Beziehung zwischen Plugin und Ur-Klasse.

Insofern: Es ist was faul in meinem Staat. :) Wahrscheinlich sogar mehrere Dinge. Spontan fallen mir da ein: Organisation der Module, Ungünstiges Plugin-Konzept, falsche Herangehensweise an die Unittests.
Wahrscheinlich könnte man das Thema auch auf allgemeine Organisations-Praktiken erweitern, ich möchte mich aber trotzdem gerne auf die Organisation der Unittests konzentrieren.

Hm. Oder vielleicht auch nicht :roll:
Benutzeravatar
helduel
User
Beiträge: 300
Registriert: Montag 23. Juli 2007, 14:05
Wohnort: Laupheim

Moin,

du kannst auch Mock-Objekte gezielt in Funktionen/Methoden injizieren. Wenn du eine Funktion/Methode testen willst und diese ein bestimmtes, bereits importiertes Modul benutzt, dann könntest du folgendes machen:

Code: Alles auswählen

import mymodule
import mock

class MyModuleTest(unittest.TestCase):
    def test_function(self):
        mymodule.my_function.func_globals['to_be_mocked_module'] = mock.Mock()
        try:
            self.assertWhatEver(mymodule.my_function(), 'whatever')
        finally:
            del mymodule.my_function.func_globals['to_be_mocked_module']
Musst halt dran denken, die Mock-Objekte wieder aus den func_globals zu entfernen. Der Vorteil ist, dass nicht gleich ganze Module verwurstelt werden.

Ich mach das so und es funktioniert richtig gut. Mit Kontext-Managern könnte man das vielleicht sogar eleganter hinbekommen. Ich selbst bin aber noch auf Python2.4 festgenagelt :cry: .

Gruß,
Manuel
Pekh
User
Beiträge: 482
Registriert: Donnerstag 22. Mai 2008, 09:09

Ja, das sieht wirklich gut aus. Werde trotzdem noch mal durch den Code gehen, und die Funktionen / Methoden so anpassen, daß sie nach Möglichkeit auch separat getestet werden können. Bin zwar eigentlich davon ausgegangen, daß das schon der Fall wäre (seit ich unittest verwende, hat sich mein Entwurfsstil schon stark verändert), aber es fallen mir doch immer noch Stellen auf, die man (in dieser Hinsicht) besser lösen könnte.

Trotzdem gehe ich davon aus, daß einige Stellen bleiben werden, an die ich anders nicht herankomme. Und da ist die von dir vorgeschlagene Methode sicherlich besser, als meine bisherige.

Schönen Dank euch beiden!
Antworten