TestCases in TestKlasse automatisiert erfassen.

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
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Hi all,

folgendes Szenario:

Ich hab ne eigene TestCase Klasse und ich habe mehrere Funktionen darin definiert,
jedoch stellt nicht jede Funktion einen eigenen, validen TestCase dar.

In meiner TestKlasse hab ich ganz unten stehen:

Code: Alles auswählen

if __name__ == '__main__':

    def suite():
        tests = [ 
            'moonlight',
            'sunshine'
        ]   
        suite = unittest.TestSuite(map(MyTestCase, tests))
        return suite

    unittest.TextTestRunner().run(suite())
Das habe ich von der Stdlib auf http://docs.python.org/2/library/unittest.html.

Mir gefällt dieses Konstrukt aber nicht und ich will, dass meine TestKlasse selber sagt,
welche die validen TestCases (Funktionen) sind.

Wie ich mir das vorgestellt habe (aber es nicht funktioniert!), seht ihr hier:

Code: Alles auswählen

class MyTestCase(unittest.TestCase):

    TestCases = set()

    def __init__(self):
        unittest.TestCase.__init__(self)
        for i in dir(self):
            if i.startswith('TestCase_'):
                self.TestCases.add(i)

    def TestCase_sunshine():
        pass # run this specific TestCase

    def TestCase_moonlight():
        pass # run this specific TestCase

    def others():
        pass # not a real testcase, might be shared/used by real TestCases...
Wie schon erwähnt, das funktioniert so nicht ganz (TypeError Exception in der __init__()).

Verwenden wollen würde ich es so, zumindest stell ich es mir so vor...:

Code: Alles auswählen

if __name__ == '__main__':

    def suite():
#       tests = [ 
#           'moonlight',
#           'sunshine'
#       ]   
        suite = unittest.TestSuite(map(MyTestCase, MyTestCase.TestCases))
        return suite

    unittest.TextTestRunner().run(suite())
Und wer weiß, möglicherweise gibt's da ne komplett andere Lösung, wie man seine (echten) TestCases in einer TestKlasse von (misc) Funktionen unterscheiden und automatisiert "inventorisieren" kann.

Ich will nicht manuell angeben, welche TestCases es gibt und ich will
das MyTestCase.TestCases set() auch nicht manuell ausfüllen, das soll automatisch befüllt werden "as I code along".

Wer kann 'was dazu sagen? Ist's überhaupt möglich (denke schon)?

Vielen Dank für eure Antworten & Ideen im Voraus.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo,

du solltst dir vielleicht noch einmal die Dokumentation zum unittest-Modul anschauen, dieses hat nämlich schon das gewünschte Verhalten ;-) Interessant ist für dich wahrscheinlich dieser Abschnitt.
Das Leben ist wie ein Tennisball.
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Oww... :?

Whoopsy...
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Sorry, dass ich nochmal nachhake, aber mal abgesehen davon, dass es wohl in unittest für alle TestCases mit "test_" implementiert ist,
kann ich vielleicht trotzdem ne Antwort haben für "allgemein". Muss ja nicht zwangsweise ne Testklasse sein, sondern darf auch komplett custom sein.

Code: Alles auswählen

class Horses:

    Staples = set()

    def __init__(self):
        for i in dir(self):
            if i.startswith('Staple_'):
                self.Staples.add(i)

    def Staples_north(self):
        pass

    def Staples_south(self):
        pass

    def clean_staple(self, staple):
        pass
Obiger Code produziert aber nicht das erwünschte Ergebnis:

Code: Alles auswählen

>>> h = Horses()
>>> h.Staples
set([])
Das set() ist leer, die Klasse initialisiert/befüllt das set() nicht, wie ich es in __init__() vorgegeben habe.

Damit der Thread nicht für die Katz ist, könnten wir bitte hier weitermachen?

EDIT: ach ja, Staples ist bewusst auf der Klassenebene definiert und nicht auf Objektebene (self.Staples...), falls sich das jemand fragt.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

probier mal:

Code: Alles auswählen

class Horses:
    def __init__(self):
        self.Staples = set()
        for i in dir(self):
            if i.startswith('Staple_'):
                self.Staples.add(i)
Wenn du in der __init__ Methode wirklich auf das Klassenattribut zugreifen willst, so musst du das auch explizit machen:

Code: Alles auswählen

class Horses:
    Staples = set()
    def __init__(self):
        for i in dir(self):
            if i.startswith('Staple_'):
                Horses.Staples.add(i)
Stichwort: Klassenattribut vs. Instanzattritbut

Ein Pferdestall ist übrigens ein stable und kein staple. Außerdem solltest du dich in der Namensgebung an PEP8 halten, z.B. sollte Staples klein geschrieben werden, also staples bzw. stables.

Grüße,
anogayales
Zuletzt geändert von anogayales am Freitag 2. August 2013, 17:23, insgesamt 1-mal geändert.
BlackJack

@akis.kapo:

Code: Alles auswählen

In [13]: 'Staples_north'.startswith('Staple_')
Out[13]: False
Man muss schon auf die Schreibweise achten. ;-)
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Thanks all,

folgender Code funktioniert tatsächlich:

Code: Alles auswählen

class Horses:

    Staples = set()

    def __init__(self):
        for i in dir(self):
            if i.startswith('Staple_'):
                self.__class__.Staples.add(i)

    def Staple_north(self):
        pass

    def Staple_south(self):
        pass

    def clean_staple(self, staple):
        pass
Danke @BlackJack für den tipo-tip. ;-)
Danke @anogayales für den Hinweis, dass ich das Klassenattribut als Instanzattribut benutzt habe, obwohl ich's eigentlich genau als Klassenattribute vorgesehen habe.

Jetzt kann ich wieder ruhig schlafen.
(Oder auch nicht, in Anbetracht meiner Dummheit & der Hitze draussen) :lol:
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

anogayales hat geschrieben:Wenn du in der __init__ Methode wirklich auf das Klassenattribut zugreifen willst, so musst du das auch explizit machen:
Alle Klassenattribute (solange sie nicht von Instanzattributen überlagert werden) sind auch per »self.bla« ansprechbar.
Ein »self.__class__.bla« ist nicht nur nicht schön anzusehen sondern auch unnötig.


@akis.kapo: das Füllen von Klassenattributen in »__init__« ist keine gute Idee. Zum einen ist es zu spät, weil erst ein Exemplar der Klasse erzeugt werden muß, bevor das Attribut gültig ist (eine unglaublich schwer zu findende Fehlerquelle) zum anderen wird dieser Code unnötigerweise bei jedem Erzeugen einer Instanz durchlaufen. Elegant würde man das Problem mit einer Metaklasse lösen oder so:

Code: Alles auswählen

class Horses:
    def __init__(self):
        pass

    def Staples_north(self):
        pass

    def Staples_south(self):
        pass

    def clean_staple(self, staple):
        pass

    Staples = set(name for name in locals() if name.startswith('Staples_'))

print Horses.Staples
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

@Sirius3
locals() und globals() hab ich ja komplett vergessen. Wenn man 'was nicht täglich nutzt in Python ... ist's weg! (*cough* GC?)

Danke für den Hinweis.

Trotzdem ein Einwand zum self.__class__.Staples.add(i)...
Also ich verstehe, dass der Zeitpunkt der Instanzierung einer Klasse hier wohl zu spät ist für die Anforderung und es Fälle gibt, wo Horses.Staples nicht das gewünschte Ergebnis liefert, wenn man es im __init__() setzt, ok - point taken.

Aber angenommen, es muss (for argument's sake) unbedingt ins __init__(), wieso wäre dann self.__class__.Staples.add(i) hässlicher als Horses.Staples.add(i)?
Letzteres ist doch nur fehleranfällig, falls ich aus irgendeinem Grund den Klassennamen ändern will, oder per Copy & Paste ne neue Klasse erstellen will und vergesse, Horses zu ändern.
self.__class__.* referenziert hier per Definition immer die "richtige" (eigene) Klasse, unabhängig vom Namen.

Und von der Schönheit mal abgesehen, wie sonst hätte es gehen können (weil du gemeint hast, es wäre auch unnötig...)?
BlackJack

@akis.kapo: Das wäre hässlicher weil man die magischen Namen vermeiden sollte wenn es eine Lösung ohne gibt, die nicht deutlich umständlicher ist. Ob `__class__` *immer* richtig ist, wenn man mal an Mehrfachvererbung denkt, ist auch nicht so eindeutig klar. Es gibt als „nicht-magischen” Weg `super()` — was mit seinem eigenen Päckchen von Problemen kommt, weswegen ich das nicht verwende.

Aber in diesem (hypothetischen) Falle ist das ja auch alles unnötig, weil mankein Attribut neu binden möchte, sondern nur ein vorhandenes Klassenattribut über seine Methoden verändern möchte. Und dafür kann man auch auf dem Exemplar einfach `self.attributname` verwenden ohne irgendwelche weiteren Verrenkungen oder Magie.
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Ja, kann jemand ein Beispiel nennen ohne __class__? Von mir aus mit super() (Probleme hin oder her...)?
Lassen wir mal Mehrfachvererbung aus dem Spiel... sonst wird's zu kompliziert.
BlackJack

@akis.kapo: Okay, die Wärme macht auch vor mir nicht halt. `super()` war keine Superidee. Man könnte eine Klassenmethode schreiben (`classmethod()`) — die bekommt die Klasse als erstes Argument übergeben. Aber man kann auch einfach den Klassennamen explizit hinschreiben. Wenn man die Klasse umbenenennt, muss man halt ein wenig suchen und ersetzen betreiben. Was man in vielen Fällen sowieso machen muss, denn was Du da veranstaltest (des Arguments wegen) kommt sowieso kaum vor — Zustand gehört nicht auf Klassenebene. Im Normalfall hat man dort Konstanten, und auf die wird oft auch von aussen zugegriffen, und *dann* muss man den Klassennamen explizit wissen.
Antworten