"Mock-Objekte" in C++



  • In den Programmen, die ich für die Maschinen geschrieben habe, waren derlei Objekte die ersten, die ich implementiert habe, und sie sind auch bis zum Ende drin geblieben. (Bin schon wieder ohne Worte, dass die Dinger schon wieder einen Namen bekommen haben...)
    Die Umschaltung lief einfach über Selbsterkennung, ein globales Flag, einen Switch in der GUI oder Skripting.



  • Bashar schrieb:

    [ Für alle die nichts mit dem Begriff Mock Objekt anfangen können: ...]

    Das ganze ist beispielsweise in Java kein Problem, da Polymorphie default ist. Was aber mach ich am dümmsten in C++? Da hat man ja des öfteren konkrete Objekte mit nicht-virtuellen Funktionen ... ist es nicht ein bisschen blöd, nur zum Testen alles virtuell und indirekt zu machen, obwohl ich den Polymorphismus eigentlich gar nicht brauche?

    ... besteht da kein großes Problembewußtsein, dann wird eben eine abstrakte Basisklasse dazuerfunden, was solls ... das kanns doch nicht sein, oder?

    HumeSikkins schrieb:

    Hallo,
    ...
    Da macht es imo schon mehr sinn, beide Klassen parallel, in abhängigkeit meiner aktuellen User-Story, zu entwickeln und zu testen.
    ...
    So wie ich das sehe ergibt sich das Problem aus einer frühzeitigen Optimierung auf Geschwindigkeit bzw. Größe. Man könnte ja auch mit einem Interface beginnen (-> kein Problem mit Mock-Object) und dann, bei Bedarf, dieses durch eine konkrete Klasse ersetzen.

    Letztlich besteht das Problem aber bereits im Design. Wenn ich vor der Implementierung schon weiß, dass A eine konkrete Klasse ist und kein Interface, dann erscheint es mir logisch, dass ein Test, der etwas anderes voraussetzt schwierig wird.

    Jo, man kann das Mock Obj wohl als ein abstract factory pattern verstehen, das im Regelfall vom der Testsuite gestellt wird.
    Bei der Entwicklung von Unit-Teatsystemen sollte man schon darauf achten, dass Zielcode und Testsystem orthogonal aufeinander entwickelt werden. Schon das Setzen eines simplen Flags zu Testzwecken im Zielcode ist ein Zeichen dafür, das was schief läuft. Die "dazu erfundene" abstrakte Basisklasse ist ein wesentlicher Bestandteil des Testframeworks, die Komponenten über simple Vererbung zusammenführen - einen konkreten Testvektor und eine konkrete Instanz - ob die nun vom Mock Objekt oder von einer vituellen Zielcodefunktion stammt, ist dabei unerheblich. Deshalb das "fehlende Problembewußtsein " ...

    So wie ich auch TDD verstanden habe und einsetze, wird zuerst die Testsuite mit Testvektor entwickelt und dann erst der eigentlichen Zielcode vorangetrieben.

    Es ist korrekt, dass Performanceuntersuchungen und Laufzeitbetrachtungen gerade in Java bei virtuellen und abstrakten Funktionen im Testframework stark verfälscht werden (Heisenbergische Unschärferelation - Ort und Implus eines Teilchens sind nicht beliebig genau bestimmbar 😃 ) So dass, wie von Hume vorgeschlagen, eine parallele Entwicklung im Zielcode sinnvoll sein mag. Mir pesönlich gefallen mehr die Ideen vom Einsatz Meta-Code-Generatoren, um wieder eine Trennung von Testsystem und Zielcode zu erhalten.



  • Prof84 schrieb:

    Die "dazu erfundene" abstrakte Basisklasse ist ein wesentlicher Bestandteil des Testframeworks, die Komponenten über simple Vererbung zusammenführen - einen konkreten Testvektor und eine konkrete Instanz - ob die nun vom Mock Objekt oder von einer vituellen Zielcodefunktion stammt, ist dabei unerheblich. Deshalb das "fehlende Problembewußtsein " ...

    So wie ich auch TDD verstanden habe und einsetze, wird zuerst die Testsuite mit Testvektor entwickelt und dann erst der eigentlichen Zielcode vorangetrieben.

    Das heißt? Dass die abstrakte Basisklasse schon vom Test her da ist und der Zielcode sich da nur reinhängt? Bin mir nicht sicher, ob ich dich richtig verstehe, weil das meine Frage ja nur streift.



  • Bashar schrieb:

    Das heißt? Dass die abstrakte Basisklasse schon vom Test her da ist und der Zielcode sich da nur reinhängt? Bin mir nicht sicher, ob ich dich richtig verstehe, weil das meine Frage ja nur streift.

    Wie jetzt?? 😕 - Die Seite kennst Du?!
    http://www.xprogramming.com/software.htm

    Die auch?!
    http://cppunit.sourceforge.net/cgi-bin/moin.cgi/FrontPage?action=show&redirect=StartSeite

    Und die?!
    http://www.mockobjects.com/wiki/

    Oder die?!
    http://mockpp.sourceforge.net/



  • Prof84 schrieb:

    Wie jetzt?? 😕

    Ich habe nicht nach Erklärungen, was Mock Objekte sind oder was man damit machen kann, gesucht, sondern nach einer Diskussion, wie sich diese Idee mit einem der Grundgedanken von C++ verträgt, nämlich dass Polymorphie optional ist, und dass ich nur für das bezahle was ich nutze. Vielleicht ist das auch kein Problem, kann sein, weiß ich nicht. Deshalb frag ich ja.



  • @Bashar:
    Ja, irgendwie haben wir Frequenzprobleme....

    1. Für jeden Testcase den Du implementierst wird bezahlt. Das ist auch bei Mock Objekten nicht anders.

    2. Es ist Intention eines Unit Testsystems jede entwickelte Methode durchzutesten. Dabei ist die Gesamt-Performance egal. Deswegen heißt das auch UNIT-Test. Wir haben hier Systeme, die bollern nach Feierabend durch das Testfield 3-4 Stunden durch. Ob daraus später 6,7 oder 8 Stunden werden ist uns ebenfalls schnuppe. Hauptsache - wir haben vor Schichtbeginn unsere Ergebnisse. Ob dies nun in astrophilosphischen ethischen theologischen Sinne einer optionalen C++ Polymorphie ist, tangiert mich und wahrscheinlich die meisten anderen Entwickler perifer ...



  • Prof84 schrieb:

    1. Für jeden Testcase den Du implementierst wird bezahlt. Das ist auch bei Mock Objekten nicht anders.

    Ich meinte nicht monetär, sondern im übertragenen Sinne.

    1. Es ist Intention eines Unit Testsystems jede entwickelte Methode durchzutesten. Dabei ist die Gesamt-Performance egal. Deswegen heißt das auch UNIT-Test. Wir haben hier Systeme, die bollern nach Feierabend durch das Testfield 3-4 Stunden durch.

    Die Performance beim Testen ist mir auch schnuppe, es geht um die Performance der fertigen Applikation. Aber auch um andere Schwierigkeiten, die sich durch Polymorphie ergeben.



  • Bashar schrieb:

    Die Performance beim Testen ist mir auch schnuppe, es geht um die Performance der fertigen Applikation. Aber auch um andere Schwierigkeiten, die sich durch Polymorphie ergeben.

    Mock Objekte sind 100%ige Bestandteile des Testsystems. Welche Schwierigkeiten? Du setzt den Testcase an das Mock Objekt und ratterst die
    Polymorphie durch. Wo siehst Du die Probleme?

    Ups... Nacht! 😮

    P84



  • Prof84 schrieb:

    Bashar schrieb:

    Die Performance beim Testen ist mir auch schnuppe, es geht um die Performance der fertigen Applikation. Aber auch um andere Schwierigkeiten, die sich durch Polymorphie ergeben.

    Mock Objekte sind 100%ige Bestandteile des Testsystems. Welche Schwierigkeiten? Du setzt den Testcase an das Mock Objekt und ratterst die
    Polymorphie durch. Wo siehst Du die Probleme?

    Ich weiß, dass die MockObjekte zum Test gehören. Aber die ihnen übergeordnete Basisklasse, die ohne sie nicht notwendig gewesen wäre, nicht. Beispiel:

    // ohne Testen:
    class A { public: bool bar(); };
    class Testkandidat { public:   bool foo(A&); };
    
    // mit Testen:
    class ABase { public: virtual bool bar() = 0; };
    class A : public ABase { public: bool bar(); };
    class Testkandidat { public bool foo(ABase&); };
    
    class MockA : ABase { public: bool bar(); };
    TEST(Testkandidat, foo)
    {
      Testkandidat k;
      MockA a;
      CHECK(k.foo(a));
    }
    

    Wenn ich das Programm irgendwann als fertig deklariere und die Tests entferne/deaktiviere, hab ich immer noch zusätzlich die ABase-Klasse, ausserdem den Umstand, dass viele ihrer Memberfunktionen virtual sind, dann kann ich die Klasse nicht mehr sorglos kopieren, muss sie evtl. in eine Handle-Klasse einpacken, etc.

    Ups... Nacht! 😮

    Ups, genau. Nacht 🙂



  • Bashar schrieb:

    [

    // ohne Testen:
    class A { public: bool bar(); };
    class Testkandidat { public:   bool foo(A&); };
    
    // mit Testen:
    class ABase { public: virtual bool bar() = 0; };
    class A : public ABase { public: bool bar(); };
    class Testkandidat { public bool foo(ABase&); };
    
    class MockA : ABase { public: bool bar(); };
    TEST(Testkandidat, foo)
    {
      Testkandidat k;
      MockA a;
      CHECK(k.foo(a));
    }
    

    Wenn ich das Programm irgendwann als fertig deklariere und die Tests entferne/deaktiviere, hab ich immer noch zusätzlich die ABase-Klasse, ausserdem den Umstand, dass viele ihrer Memberfunktionen virtual sind, dann kann ich die Klasse nicht mehr sorglos kopieren, muss sie evtl. in eine Handle-Klasse einpacken, etc.

    Ach so, jetzt dämmert´s ...
    a) Inneres isoliertes Testen
    Zielcode + Mock Objekt incl. Testvektor => Testsuite (AOP)
    b) Recycle Testsuite
    Testsuite - Mock Objekt => Zielcode (wie auch immer ...)

    Hmmm ... pupupupuh ... heiß, heiß ....Ok!

    0. Lohnt es sich darüber nachzudenken?
    Während bei Java systembedingt eine Typensicherheit garantiert ist, ist das "Rosinenpoolen" bei C++ nicht ganz ungefährlich. Da sind virtuelle Funktionen und abstrakte Klassen das geringste Problem.Ich setze Mock Objekte auch oft in Bereichen ein, indem ich eine penible Pointer-Sicherheit gewährleisten muss. Dort wäre ein "switch off" der Mock Objekte ohne Revision wie Rauchen im Benzinlager. Thema: Vertauenswürdige Komponenten - Der generierte Zielcode ist nicht getestet!

    Apropos Einsatz:
    0b) Sinn und Unsinn von Mock Objekten
    ... oder vielleicht legen wir erst eine Etage höher an...
    0a) Isoliertes Testen - Einsatz von Stubs, Shunts und Mocks

    " ...
    -Stubs bieten die minimalste Fake Implementierung einer Klasse oder Schnittstelle.Ich verwende sie immer, wenn der Aufruf an sich, noch sein Aktualparameter (RR: Was für ein Wort) für den Test interessant sind.
    -Self-Shunt ist sehr elegant solange die implementierte Schnittstelle schmall bleibt.Bei mir kommen sie zum Einsatz, wenn der Aufruf, d.h. die Interaktion zwischen den Objekten geprüft werden soll.
    -Mocks sind die unbestreitbaren Könige unter den Hochstablern. Ich benötige sie, wenn sie dublizierenten Testcode vermeiden helfen oder etwas mehr als nur ins triviale Verhalten vorzugaukeln ist.
    ...
    Wir wissen, dass wir es mit den Mocks übertrieben haben,
    - wenn der Test der Klasse mehr als drei Mocks verlangt.
    - wenn Mocks noch wieder Gebrauch von Mocks machen.
    - wenn für die Mehrzahl der Enitäten im System eine Schnittstelle, ein Mock und die tatsächliche Anwendungsklasse existiert.
    - wenn Mocks ihrerseits so kompliziert sind, dass sie eigentlich schon wieder eigene Test benötigen.
    ..."
    Quelle: http://frankwestphal.de/TestgetriebeneEntwicklungmitJUnitundFIT.html
    9.23, 9.18

    Also ich setze Mock Objekte oft in der Systementwicklung ein, wenn es mir nicht möglich ist, die Systemserver "unter Feuer zu nehmen". Oder irgend ein Systemmüll generiert wird, der Mitarbeiter und Abteilungen nervt oder irgendeine Auswertung verfälscht, die noch gebraucht wird etc.
    Aber mein Haupteinsatzfeld ist immer noch die "Stecknadelfunktion", weil ich mir als Freiberufler, im Gegensatz zu vielen Angestellten, nicht erlauben kann "für ein Monatsgehalt über ein einziges Problem zu hängen". Ich generiere zb mein Mock und mache erst einmal weiter, ohne dass das Testsystem spinnt, ich die Team-Members aufhalte oder verlangsame oder ich auf irgendeine "Schnachtasse" warten müsste, bis die aus dem Quark kommt (so fuck Strouppy - " ... versuchen Sie niemals soziologische Probleme mit technischen Mitteln zu lösen!" 🤡 ). Wenn ich mich dann länger brässig anstelle, kann ich auch so leichter mein Problem dem "Halbgott" (Systemanalytiker || Senior Developer) servieren. Mit dem Framework wird er direkt an den neuralgischen Punkt - zb Mock - gelotst, ohne das er sich erst per Analyse durch die äußere Kapselung bohren muss.

    1. Wenn nich´ - wat machen wa damit?
    Die Testsuites werden mit Mockobjekten idR ehe refakturiert oder gehen durch die Revision für den Test der nächsten Ebene. D.h. aus der generischen Programmierung werden später auch konkrete Klassen und aus der Testsuite wird Zielcode abgebildet und dieser unabhängig getestet, obwohl Beide sich lediglich durch das Mock Objekt unterscheiden mögen. Was ich persönlich oft mache, ist das Mock zum "System-Ping" zu modifizieren, als "is still alive"-Test und so Testsuite als Zielcode übernehme. Dabei ist C++ ein Vorteil, weil der Preis nicht so hoch ist, wie bei Java (s.o.). Wenn die Leute nur die Rosinen poolen, würde kein Bäcker welche verwenden ... 😋

    2. Wenn doch - wie machen wa dat?
    Mein Fazit: Wir haben bei der Testsuite eben eine AOP! D.h. die Mocks müssen wir unterdücken durch libaries, frameworks, headers, makros etc..
    Dafür benötigen wir eben eine Makierung im Zielcode und das hat zur Folge, dass wir unsere Orthogonalität verlieren. IMHO wird dann der Teufel mit dem Beezebub ausgetrieben und ist nicht Intention eines Unittests.


Anmelden zum Antworten