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

28.08.2018, 13:13

[Java] Problem mit statischen Methoden und Unit-Tests

Hallo zusammen!
Ich bin gerade dabei, für mein Spiel Unit-Tests zu schreiben. Dabei bin ich auf ein Problem mit statischen Methoden/Variablen in der folgenden Konstruktion gestoßen:

Grober Ablauf: Die Klasse ProfessionTypes enthält alle im Spiel verfügbaren Berufstypen. Dazu enthält die Klasse die Pfade von JSON-Dateien, in denen die Berufe definiert sind. Nachdem die Dateien von einem externen Loading-Screen geladen werden, wird die statische ProfessionTypes#initialize(AssetManager)-Methode aufgerufen und die ProfessionType-Objekte werden geladen und gesetzt.
Anschließend ist beispielsweise in der statischen Variable ProfessionTypes.SMITH der Berufstyp des Schmiedes enthalten.

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Enthält alle einzelnen ProfessionTypes als statische Variablen
public class ProfessionTypes {

    // Der Berufstyp des Schmiedes
    public static ProfessionType SMITH;

    // Der Pfad zur Datei, die Informationen über den Schmied enthält
    @Asset(JSON.class)
    public static final String SMITH_JSON_PATH = "data/professions/smith.json";

    private ProfessionTypes() {
        // shouldn't get instantiated
    }

    // Wird vom externen Loading-Screen aufgerufen; setzt die statischen Variablen 
    public static void initialize(AssetManager assetManager) {
        SMITH = assetManager.get(SMITH_JSON_PATH).getData(ProfessionType.class);
    }

    // Ein einzelner Berufstyp
    public static class ProfessionType {

        private String name;

        ProfessionType() {
        }

        public String getName() {
            return name;
        }

    }

}


Grund für das Vorgehen: Grund für die statischen Variablen und damit auch die statische #initialize(AssetManager)-Methode ist, dass die Variablen (wie beispielsweise SMITH) quasi ein „dynamisches Enum“ darstellen sollen.
Heißt: Einfach aufrufbar (ProfessionTypes.SMITH), mit „==“ vergleichbar, aber trotzdem durch externe Dateien im Inhalt (bspw. Name, etc.) bestimmt.

Problem: Beim Schreiben der Tests ist mir aufgefallen, dass sich statische Methoden mit Mockito leider nicht mocken lassen und PowerMockito mit JUnit 5 noch nicht funktioniert.
Gerade bei statischen Utility-Methoden (die beispielsweise einen zufälligen Beruf zurückgeben, s.u.) zeigt sich das Problem: Ruft irgendeine der zu testenden Klassen diese Utility-Methode auf, müssen vorher die entsprechenden Types geladen werden. Das ist für abgegrenzte Unit-Tests eher unpraktisch.

Quellcode

1
2
3
4
5
6
7
public class PlayerUtils {

    public Player getRandomPlayer() {
        return new Player("Vorname", "Nachname", RandomUtils.getRandomElement(ProfessionTypes.ALL_TYPES));
    }

}


Jetzt stellt sich mir die Frage, wie ich das Problem am besten umgehe. Mir ist klar, dass die Konstruktion mit den statischen Variablen (ProfessionTypes.SMITH) nicht die eleganteste ist, eine wirklich bessere Lösung fällt mir aber ehrlich gesagt auch nicht ein.
Was denkt ihr dazu: Liegt das Problem eher in den statischen Utility-Methoden? Sollte ich auf JUnit 4 downgraden, um über PowerMockito die Utility-Methoden (PlayerUtils#getRandomPlayer()) mocken zu können? Oder übersehe ich vielleicht auch einfach nur, wie sich die Types recht simpel mocken ließen?
Pewn - eine Plattform für alle, die an Indie-Spielen interessiert sind

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

2

29.08.2018, 06:30

Vermutlich war damios nicht klar, dass man enums auch mit zusätzlichen Properties bauen kann in Java. Statisch muss und sollte hier echt nichts sein, sehe ich auch so.

Aus meiner Sicht ist es übrigens eine Verletzung des Single-Responsibility-Principles, dass die Professions sich selbst aus irgendeiner Datei laden können.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

3

29.08.2018, 07:48

Danke für eure beiden Antworten! Mir ist durchaus klar, dass die statische Konstruktion hier unschön ist, aber deswegen frage ich ja hier nach Vorschlägen, wie ich es besser machen könnte.

@LetsGo Zur normalen Klasse: Das ist für die Art und Weise in der die Types verwendet werden (meiner Meinung nach) zu umständlich: Dann müssten plötzlich zahlreiche Klassen eine ProfessionTypes-Instanz übergeben bekommen - das summiert sich bei rund 15 weiteren Typen schnell auf. Außerdem fiele dann die knappe Schreibweise (PositionTypes.SMITH) weg und müsste durch (ein doch deutlich längeres) „this.getTypes().PROFESSION_TYPES.SMITH“ ersetzt werden.

@BlueCobold

Zitat von »BlueCobold«

Vermutlich war damios nicht klar, dass man enums auch mit zusätzlichen Properties bauen kann in Java.

Meinst du folgendes?

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
public class XYZ {
     A (321), B(123);

    private final int test;   // in kilograms

    Planet(int test) {
        this.test = test;
    }

    private double getTest() { return test; }

}


Bei enums (die von der Art her eigentlich ideal passen würden) hatte ich das Problem, wie ich deren Inhalt zur Laufzeit einfach festlegen kann. Klar ginge das manuell über Setter, aber bei 15 verschiedenen Types mit zig Klassenvariablen ist das schon wesentlich mehr Schreibarbeit als einfach normale Objekte automatisch über Gson befüllen zu lassen (gson.fromJson(assetString, ProfessionType.class)).
Und dann war mein Gedanken, dass ich mich lieber mit der unschönen Konstruktion, die im obigen Post zu sehen ist, herumschlage, als für lauter Types eigene AssetLoader zu implementieren.

Noch zur statischen Utility Methode (PlayerUtils#getRandomPlayer()): ist so etwas auch ungern gesehen? Mein Gedankengang war, dass sich ja der Server (der die Methode als einziger aufruft) nicht unbedingt selbst mit dem Erhalt eines Spielers herumschlagen muss und die Methode selbst ja auf keinerlei State angewiesen ist, deswegen habe ich das ausgelagert.

Zitat von »BlueCobold«

Aus meiner Sicht ist es übrigens eine Verletzung des Single-Responsibility-Principles, dass die Professions sich selbst aus irgendeiner Datei laden können.

Könntest du das nochmal näher ausführen? ProfessionTypes enthält ja letztlich nur Pfade und Variablen, das eigentliche Laden übernimmt ja ein externer Loading-Screen. Oder meinst du alles, was innerhalb der initialize(AssetManager)-Methode steht, was dann den geladenen Inhalt den Variablen zuweist (das eigentliche Laden erfolgt trotzdem außerhalb)?
Pewn - eine Plattform für alle, die an Indie-Spielen interessiert sind

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

4

29.08.2018, 12:03

Also zunächst mal ist mir unklar, wieso du unbedingt einen Enum brauchst für den Profession-Type. Und wenn es einen Enum geben muss, der ja für statischen Code steht, wieso sollte es dann dynamisch geladen werden? Das sind zwei widersprüchliche Konzepte.

Und ja, ich meine die initialize-Methode, die irgendwoher Daten lädt. Da werden aus meiner Sicht zwei Dinge in einer Klasse gemischt, die da nicht hingehören. Generell sollte bei "initialize"-Methoden eine Alarmglocke läuten, denn das deutet darauf hin, dass hier irgendwas ganz falsch läuft. Konstruktoren sind für Initialisierung zuständig. Dass das bei dir wegen den statischen Inhalten nicht geht, zeigt umso mehr, dass du da ein sehr unschönes Konstrukt zusammenbaust, was dir später mehr Ärger als Nutzen bringt.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

5

29.08.2018, 12:40

Zitat von »BlueCobold«

Also zunächst mal ist mir unklar, wieso du unbedingt einen Enum brauchst für den Profession-Type. Und wenn es einen Enum geben muss, der ja für statischen Code steht, wieso sollte es dann dynamisch geladen werden? Das sind zwei widersprüchliche Konzepte.

Die Types sind so etwas wie die folgenden: Religion (CATHOLIC, ORTHODOX), BuildingType (TOWN_HALL, FORGE_LVL_1) und eben auch ProfessionType (SMITH, TEACHER, etc.). Heißt ihre wesentlichen Eigenschaften sollen sein (1.) es gibt jeweils nur die vorher fest bestimmten Typen (=Enum), (2.) deren Inhalt wird aber über externe Dateien festgelegt.

Die erste Eigenschaft wird zum Beispiel im Bildschirm, der das Haus-Inneren darstellt, folgendermaßen verwendet:

Quellcode

1
2
3
4
5
6
7
8
9
private Building currentlyDisplayedBuidling;

protected void initUI() {
    //...

    if (currentlyDisplayedBuidling.getType() == BuildingTypes.TOWN_HALL) {
        // Button 'Einsehen der Stadtgesetze' zum UI hinzufügen
    }
}


Oder, insoweit fast noch deutlicher, in der CharacterFactory, die einen Spieler-Charakter erstellt:

Quellcode

1
2
3
4
5
6
7
8
9
public static Character createCharacterWithStatus(SocialStatus status) {
    Character c = new Character();

    if (status != SocialStatusS.NON_CITIZEN) {
        c.setGold(200);
    }

    // ...
}


Zur zweiten Eigenschaft, dem "dynamischen Inhalt": Die einzelnen Eigenschaften der Typen sollen nicht im Java-Code festgelegt werden, sondern in externen Dateien, damit auch die am Projekt beteiligten Nicht-Programmierer am Balancing arbeiten können. Eine solche externe Datei sieht zum Beispiel für die Import-Steuer (ein LawType) so aus:

Quellcode

1
2
3
4
5
6
{
    "name": "Importzoll",
    "defaultValue": 5,
    "lowerBound": 0,
    "upperBound": 15
}
Pewn - eine Plattform für alle, die an Indie-Spielen interessiert sind

Schorsch

Supermoderator

Beiträge: 5 145

Wohnort: Wickede

Beruf: Softwareentwickler

  • Private Nachricht senden

6

29.08.2018, 13:23

Heißt ihre wesentlichen Eigenschaften sollen sein (1.) es gibt jeweils nur die vorher fest bestimmten Typen (=Enum), (2.) deren Inhalt wird aber über externe Dateien festgelegt.

Du sagst es doch schon selbst. Es handelt sich um Eigenschaften welche unter anderem ein Typ und weitere Daten sind. Die Daten kommen aus einer externen Datei und der Typ soll durch eine Aufzählung dargestellt werden. Mach dir also eine Klasse die das ganze abbilden kann. Die Daten für eine solche Instanz lädst du dann aus einer Datei und erzeugst dadurch die Instanz. Um es einfach zu machen kannst du das Laden der Daten in den Konstruktor der Klasse schieben, schöner wäre es aber wie BlueCobold schon sagt den Initialisierungscode hier heraus zu ziehen.
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

7

29.08.2018, 14:59

Danke für deine Antwort, Schorsch!

Zitat von »Schorsch«

Du sagst es doch schon selbst. Es handelt sich um Eigenschaften welche unter anderem ein Typ und weitere Daten sind. Die Daten kommen aus einer externen Datei und der Typ soll durch eine Aufzählung dargestellt werden. Mach dir also eine Klasse die das ganze abbilden kann.

Wenn ich das richtig verstanden habe, hieße das umgesetzt in etwa folgendes, oder?

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public enum ProfessionType {
    SMITH, TEACHER;

    public String getJsonDataPath() {
        return String.format("data/professions/%s.json", name().toLowerCase());
    }

}

public class ProfessionTypeData {
    private String name;

    ProfessionTypeData() {
    }

    public String getName() {
        return name;
    }
}

public class TypeHolder {
    public final EnumMap<ProfessionType, ProfessionTypeData> PROFESSION_TYPE_DATA = new EnumMap<>(
            ProfessionType.class);
    // Weitere Types 
    // ...

    // Map wird hier irgendwo initialisiert
}


Zum weiteren Umgang mit dieser Konstruktion hätte ich dann noch eine (/zwei) Fragen: Wäre es unschön, TypeHolder hier als Singleton zu implementieren? Das hätte den Vorteil, dass ich nicht überallhin eine Instanz des TypeHolders mit übergeben muss und außerdem im Enum folgende Konstruktion möglich wäre (die das Aufrufen der Daten dann wieder schön bequem machen würde):

Quellcode

1
2
3
4
5
6
7
8
9
10
public enum ProfessionType {
    SMITH, TEACHER;

    // ...
    
    public ProfessionTypeData getData() {
        return TypeHolder.getInstance().PROFESSION_TYPE_DATA.get(this);
    }

}


Um eine initialize(AssetManager)-Methode in TypeHolder käme ich dann aber auch nicht herum, da ich dem Konstruktor beim Singleton schlecht den verwendeten AssetManager übergeben kann :S

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TypeHolder {
    public final EnumMap<ProfessionType, ProfessionTypeData> PROFESSION_TYPE_DATA = new EnumMap<>(
            ProfessionType.class);

    // Singleton stuff
    // ...

    public void initialize(AssetManager assetManager) {
        for (ProfessionType t : ProfessionType.values()) {
            PROFESSION_TYPE_DATA.put(t, assetManager.get(t.getJsonDataPath())
                    .getData(ProfessionType.class));
        }
    }
}
Pewn - eine Plattform für alle, die an Indie-Spielen interessiert sind

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

8

29.08.2018, 15:20

Was stört dich an

Java-Quelltext

1
2
3
4
public class Profession {
    private ProfessionType type;
    private String name;
}

Wenn der Name aber alles ist, was du als Daten für eine Profession hast und nur dafür, dass du sie in irgendeiner UI anzeigen kannst, dann solltest du eher vernünftige Lokalisierung nutzen und von ProfessionTypes auf Strings übersetzen, statt sie als Pseudo-Eigenschaften zu halten.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

9

29.08.2018, 16:17

Zitat von »BlueCobold«

Was stört dich an [...]

Eine Profession-Klasse gibt es schon auch noch:

Quellcode

1
2
3
4
5
6
public class Profession {
    private ProfessionType profession;
    private int level;
    private int experience;
    //...
}

Das ist dann der konkrete Beruf, den ein Spieler ausübt (und um einem solchen Vorschlag gleich vorzubeugen: Nein, das ließe sich auch nicht in die Spieler-Klasse auslagern, da jeder Spieler mehrere Berufe erlernen kann).

Zitat von »BlueCobold«

Wenn der Name aber alles ist, was du als Daten für eine Profession hast [...]

Der Name war in den bisherigen Beispielen nur exemplarisch, eine Amtsposition hat beispielsweise die folgenden Attribute:

Quellcode

1
2
3
4
5
6
7
8
public class PositionType {
    private int level;
    private int statusRequirementIndex;
    private int salary;
    private int cabinet;
    private List<Integer> lawsToVoteForIndices;
    private boolean popularVote = false;
}


Eine verkappte Lokalisierung ist also nicht der Grund für das ganze Trara – es geht darum, tatsächliche Eigenschaften der Types aus externen Dateien zu laden.

(Off-topic: Wie bekomme ich eigentlich das Syntax-Highlighting für Java-Code hin?)
Pewn - eine Plattform für alle, die an Indie-Spielen interessiert sind

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

10

29.08.2018, 16:56

Ich formuliere dann wohl mal um:
- Bäcker ist ein Beruf.
- Brezelbäcker ist ein spezieller Bäcker.
- Gärtner ist ein Beruf.
- Fleischer ist ein Beruf.

Gäbe es doch nur eine Möglichkeit so etwas als Code zu formulieren... :hmm:
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Werbeanzeige