Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

1

12.06.2015, 16:47

Unittest von "Manager"-Klassen

Hi, ich überlege derzeit wie ich meine "Manager"-Klassen am besten teste.

Mein Design: Ich befinde mich Design-technisch bei einem Entity-Component-System das viele Aspekte des Data-oriented Designs verwendet. Beispielsweise habe ich keine Entity-Klasse an sich sondern pro Komponente je eine Struktur - nennen wir sie PhysicsData. Zu jeder Domäne (Physik, Animation, Rendering etc.) habe ich Manager- oder System-Klassen, z.B. PhysicsSystem. Diese fungieren als Container der jeweiligen Daten (hier PhysicsData, d.h. via Array) und führen auf diesen bestimmte Methoden aus, z.B. bool PhysicsSystem::objectCanAccess(PhysicsData const & object, int x, int y) const;. Das öffentliche Interface ist relativ einfach gestrickt: Im Grunde calle ich jeden Frame die Update-Methoden der Systeme und diese erledigen die zum jeweiligen System zugehörige Arbeit. D.h. die objectCanAccess()-Methode (zur Prüfung ob object die Position (x,y) betreten kann - aus Sicht der Kollision) ist private, da das Physik-System seine Logik autonom verwendet. Weitere Methoden sind z.B. void PhysicsSystem::interpolateMovement(PhysicsData& object, int elapsedTime) const; zu Interpolation der Bewegung.

Mein Problem: Ich möchte natürlich auch die privaten Methoden einem Unit-Test unterziehen. Nur sind die Methoden natürlich nicht public. Sie explizit zum Zweck des Unittestings öffentlich zu deklarieren, halte ich nicht für den richtigen Weg. Daher überlege ich, ob ich sie als protected umdeklariere und zum Unit-Test eine Ableitung der Klasse erstelle, z.B. PhysicsSystemTest, die von PhysicsSystem erbt. Dann könnte ich meine Testcases als Methoden meiner "Test-Klasse" schreiben, da ich innerhalb der Methoden dann auf protected-Member Zugriff habe.

Was haltet ihr von dieser Lösung?

Ich habe mal etwas gegoogelt: z.B. mit Boost's Unittesting Framework ist das prinzipiell möglich - nur muss ich die Methoden dann explizit zur Testsuite hinzufügen. Kennt da jemand eine elegante Alternative? Jede Methode manuell added ist ... mir irgendwie zu viel Aufwand :D

LG Glocke

PS: Ich weiß, dass "will private Methoden extern testen" nicht OO-like ist .. aber ehrlich gesagt halte ich den OO-Ansatz nicht für die absolute und letzte Wahrheit :D Vielmehr "missbrauche" ich das Konzept der Klasse um für die Physik etc. mir ein einfaches Interface zu bauen und die Implementierung vor den anderen Systemen zu verstecken.

/EDIT: Was ich mir noch vorstellen könnte, wäre "mittels using als public zu erben", d.h.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <cassert>

class FooSystem {
    protected:
        bool foo(int a) { return a > 0; }
        float bar(float x, float y) { return x * y; }
        int lol(int u, int v) { return u + v; }
};

class FooSystemTest: public FooSystem {
    public:
        using FooSystem::foo;
        using FooSystem::bar;
        using FooSystem::lol;
};

int main() {
    // assume this to be my unit test ^^
    FooSystemTest sys;
    assert(sys.foo(5) == true);
    assert(sys.bar(3.f, 0.5f) == 1.5f);
    assert(sys.lol(1, 2) == 3);
}

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Glocke« (12.06.2015, 16:57)


Tobiking

1x Rätselkönig

  • Private Nachricht senden

2

12.06.2015, 17:07

Mir ist nicht ganz klar wie die öffentliche Schnittstelle von deinem PhysicsSystem aussieht. Die kann ja nicht nur eine Update Funktion haben. Wie kommen die Daten rein und wie werden sie wieder ausgelesen? Wenn sich das PhysicsSystem die Daten selber holt, hast du eh keine Units gebaut sondern einen Klumpen und kannst vermutlich nur mit Integrations- und Systemstests ein paar Szenarien durchtesten.

Zum Thema private Funktionen gilt üblicherweise das sie nicht einzeln getestet werden. Ich bin allerdings aktuell auf der Arbeit dabei uralten Code mit Tests abzudecken und muss dafür auch gezielt an Funktionen kommen die private sind, da es kaum Abstraktionen gibt. Dort setzen wir einfach ein bedingtes friend ein. Sprich sowas wie

C-/C++-Quelltext

1
2
3
#ifdef Test
  friend class PhysicsSystemTest;
#endif

3

12.06.2015, 17:38

Wie wäre es mit:

C-/C++-Quelltext

1
2
3
4
5
6
#ifdef UNITTEST
public:
#esle
private:
#endif
//alle Klassenmember und Methoden
Wer aufhört besser werden zu wollen hört auf gut zu sein!

aktuelles Projekt:Rickety Racquet

4

12.06.2015, 18:13

Wie wäre es mit:

C-/C++-Quelltext

1
2
3
4
5
6
#ifdef UNITTEST
public:
#esle
private:
#endif
//alle Klassenmember und Methoden

Super Idee, Danke!!

Wie kommen die Daten rein und wie werden sie wieder ausgelesen?

Dem Physik-System wird gesagt "erzeuge eine Physik-Komponente für Objekt X" (im Stile add()). Die Komponente kann über die ObjektID abgefragt werden (im Stile query()). Soll sich z.B. der Spieler bewegen, bekommt das Physik-System (über pushEvent()) ein Input-Event. Beim Abarbeiten der Events (pro Frame) werden dann die entsprechenden Methoden (z.B. Kollisionstest + Interpolation) ausgelöst.

D.h. faktisch habe ich verschiedene Stufen dessen was ich testen möchte:
  • die blanke Funktionalität, d.h. dass die Bewegungs-Interpolation, Kollision etc. richtig arbeiten
  • die Integration mit dem Event-System (über das die System kommunizieren), d.h. dass z.B. ein "Bewege X nach oben"-Event die Figur (sofern die Bewegung erlaubt ist), die Figur tatsächlich bewegt.

Letztere Tests will ich realisieren, indem ich ein Objekt erzeuge, ein Event auslöse, das System aktualisiere (Update()) und dann das Objekt abfrage und z.B. seine Position prüfe. Soweit so gut. Nur für den Teil "davor" (dass die Kollision in verschiedenen Szenarien richtig erkannt wird etc.) möchte ich "normale" Unit-Tests machen, im Stile: Objekte erzeugen und die entsprechende Methode aufrufen um zu schauen, ob das Physik-System die Kollision mitbekommt.

Zum Thema private Funktionen gilt üblicherweise das sie nicht einzeln getestet werden.

Prinzipiell ja - lässt sich in meinem Fall nur nicht anwenden (außer ich verzichte auf diese Art Tests - die ich aber als besonders wichtig erchte).

xardias

Community-Fossil

Beiträge: 2 731

Wohnort: Santa Clara, CA

Beruf: Software Engineer

  • Private Nachricht senden

5

12.06.2015, 18:18

Dies ist denke ich eine der wenigen Situationen wo das Friend statement angebracht waere.

http://www.cplusplus.com/doc/tutorial/inheritance/

Jedoch ist es wie schon erwaehnt oft kein gutes Zeichen wenn man private Methoden testen moechte. Das fuehrt ganz schnell zu "evil" unittests die dich mehr einschraenken als dass sie dir helfen. Aber Ausnahmen bestaetigen ja ueblicherweise die Regel ;)

6

12.06.2015, 18:36

Super Idee, Danke!!

Gerne.

Jedoch ist es wie schon erwaehnt oft kein gutes Zeichen wenn man private Methoden testen moechte. Das fuehrt ganz schnell zu "evil" unittests die dich mehr einschraenken als dass sie dir helfen. Aber Ausnahmen bestaetigen ja ueblicherweise die Regel

Hab über den Satz von xardias nachgedacht, ist es den überhaupt nötig die Privaten Methoden separat zu testen? Die Private-Methoden werden doch bestimmt, so ziemlich alle, indirekt über die Public-Methoden aufgerufen und wären dann doch auch getested.
Wer aufhört besser werden zu wollen hört auf gut zu sein!

aktuelles Projekt:Rickety Racquet

Sacaldur

Community-Fossil

Beiträge: 2 301

Wohnort: Berlin

Beruf: FIAE

  • Private Nachricht senden

7

12.06.2015, 18:48

Methoden, die nur intern verfügbar sind (private), sollten auch nur für interne Prozesse relevant sein. Wie genau Klassen das nach außen hin definierte Verhalten implementieren ist Sache der Klassen und sollte für äußere Zugriffe nicht relevant sein.
Würde man Unit-tests für Listen (oder vergleichbare Container) schreiben wollen, dann würde man nach dem Hinzufügen prüfen, ob das Element über den zu erwartenden Index abrufbar ist, ob die Vorhandenseinsprüfung funktioniert etc. Nach dem Entfernen würden die entsprechenden Gegenprüfungen gemacht werden. Um diese Prüfungen durchführen zu können, muss man aber nicht von außen auf die internen Strukturen zugreifen können.

In deinem Fall würdest du wohl unterschiedliche Situationen aufbauen, in diesen für bestimmte Berechnungen sorgen (Objekt X bewegt sich mit Geschwindigkeit Y in Richtung Z, Objekte A und B bewegun sich mit Geschwindigkeiten C und D aufeinander zu, usw.).
Du könntest auch gucken, ob bestimmte Teilfunktionalitäten wirklich privat sein müssen. Die Prüfung, ob sich 2 Formen überlagern könnte auch für andere Stellen im Code interessant sein, um ein Beispiel zu nennen.
Spieleentwickler in Berlin? (Thema in diesem Forum)
---
Es ist ja keine Schande etwas falsch zu machen, als Programmierer tu ich das täglich, [...].

8

12.06.2015, 19:59

Du könntest auch gucken, ob bestimmte Teilfunktionalitäten wirklich privat sein müssen. Die Prüfung, ob sich 2 Formen überlagern könnte auch für andere Stellen im Code interessant sein, um ein Beispiel zu nennen.

Ja ok, das Kollisions-Ding war ein ungünstiges Beispiel .. hab gerade nochmal in den Code geguckt:

C-/C++-Quelltext

1
bool checkTileCollision(PhysicsData const & data, Cell const & cell) const;

ist in der Tat bereits public.

In deinem Fall würdest du wohl unterschiedliche Situationen aufbauen, in diesen für bestimmte Berechnungen sorgen (Objekt X bewegt sich mit Geschwindigkeit Y in Richtung Z, Objekte A und B bewegun sich mit Geschwindigkeiten C und D aufeinander zu, usw.).

Vermutlich ist die Variante die praktikabelste .. zumal ich dann direkt weiß, ob das System richtig auf verschiedene Events reagiert.

birdfreeyahoo

Alter Hase

Beiträge: 756

Wohnort: Schorndorf

Beruf: Junior Software Engineer

  • Private Nachricht senden

9

13.06.2015, 16:10

Die privaten Methoden werden doch aus der Klasse heraus aufgerufen und wenn du alle öffentlichen Methoden testest, dann werden doch alle privaten Methoden mit eingebunden.
Wenn du also keinen Fehler in der Schnittstelle findest sollte auch intern alles in Ordnung sein.

10

13.06.2015, 16:18

Die privaten Methoden werden doch aus der Klasse heraus aufgerufen und wenn du alle öffentlichen Methoden testest, dann werden doch alle privaten Methoden mit eingebunden.
Wenn du also keinen Fehler in der Schnittstelle findest sollte auch intern alles in Ordnung sein.

Richtig. Die Sache ist nur halt die, dass der Code "drumrum" (zum Testen einer einzelnen Funktionalität) durch die öffentliche Methode (z.B. generieren und übergeben der Events) das ganze aufbläht.

Werbeanzeige