Junit Test - Testwert ändert sich

yeahr

Mitglied
Hallo an alle, ich hab ein kleines Problem beim Testen mit Junit. Ich habe eine Klasse R2D2 und erzeuge 3 verschiedene davon. bei jedem erstellen soll di ID-Nummer von anfangend von 0 um 1 erhöht werden.

Wenn ich in einer Main über syso getId() die ID von jedem Roboter abfrage kommt beim ersten ganz normal 0 beim zweiten 1 beim dritten 2 und wenn ich dann die Namen mit getName() abfrage ändert sich bei den ID's auch nichts. So soll es auch sein. Jetzt wollte ich das ganze über Junit testen und wenn ich allein die Testmethode für ID ausführe ist auch alles richtig also auch wieder 0, 1, 2. Aber sobald ich die Namen teste, hat der erste Roboter auf einmal anstatt die ID 0, die ID 3. Dann gibt es noch das Problem wenn ich die ID's einzeln abfrage kommt beim Junit Test beim ersten R2D2 noch die 0 aber beim zweiten die 4 und beim dritten die 8.Kann es sein das durch den Junit Test irgendwie noch ein Objekt im Hintergrund erzeugt wird und somit die statische Variable buildCounter irgendwie nochmal erhöht wird?


Java:
public class R2D2 extends RobotModel {

	private static final int MIN_ID_R2D2 = 0;
	private static final int MAX_ID_R2D2 = 9999;
	private static int buildCounter = MIN_ID_R2D2;

	public R2D2(String name) {
		this.name = name;
		checkRobotId();
		this.id = R2D2.buildCounter;
		R2D2.buildCounter++;
	}

	protected void checkRobotId() {
		if (R2D2.buildCounter > MAX_ID_R2D2) {
			R2D2.buildCounter = MIN_ID_R2D2;
		}

	}
}

Hier ist die Junit-Testklasse ohne Testabfrage von Namen und einzelne Abfrage der ID's(test1 hat 0 aber test2 hat 4 anstatt 1 und test3 hat 8 anstatt 2):

Java:
public class TestR2D2 {
    
    // R2D2 Roboter erstellen
    Robot test1 = RobotFactory.createRobot(RobotType.R2D2, "Bernd");
    Robot test2 = RobotFactory.createRobot(RobotType.R2D2, "Achim");
    Robot test3 = RobotFactory.createRobot(RobotType.R2D2, "Klaus");

    
    @Test
    public void test1() {
        assertEquals(0, test1.getId()); 
          

    }
    @Test
    public void test2() {
    
        assertEquals(1, test2.getId());    
      

    }
    @Test
    public void test3() {
       
       
        assertEquals(2, test3.getId());    

    }
}




Hier ist die Junit-Testklasse ohne Testabfrage von Namen(alles normal die ID's haben 0,1,2):

Java:
public class TestR2D2 {
    
    // R2D2 Roboter erstellen
    Robot test1 = RobotFactory.createRobot(RobotType.R2D2, "Bernd");
    Robot test2 = RobotFactory.createRobot(RobotType.R2D2, "Achim");
    Robot test3 = RobotFactory.createRobot(RobotType.R2D2, "Klaus");

    
    @Test
    public void testId() {
        assertEquals(0, test1.getId()); 
        assertEquals(1, test2.getId());    
        assertEquals(2, test3.getId());    

    }
   
}


Und hier ist die Junit-Testklasse mit Testabfrage von Namen(ID von Test1 hat anstatt 0 die 3):

Java:
public class TestR2D2 {
    
    // R2D2 Roboter erstellen
    Robot test1 = RobotFactory.createRobot(RobotType.R2D2, "Bernd");
    Robot test2 = RobotFactory.createRobot(RobotType.R2D2, "Achim");
    Robot test3 = RobotFactory.createRobot(RobotType.R2D2, "Klaus");

    
    @Test
    public void testId() {
        assertEquals(0, test1.getId()); 
        assertEquals(1, test2.getId());    
        assertEquals(2, test3.getId());    

    }

    @Test
    public void testName() {
        assertEquals(0, test1.getName()); 
        assertEquals(1, test2.getName());    
        assertEquals(2, test3.getName());   
        }
   
}


Ich hoffe ich hab mich nicht zu unklar ausgedrückt und man versteht was ich meine, falls jemand denkt da fehlt doch noch was an Klassen. Ja stimmt allerdings denke ich es betrifft speziell die R2D2-Klasse wegen der statischen Variablen buildCounter.
 
Zuletzt bearbeitet:
Also generell sehe ich da jetzt keinen Fehler, aber dafür fehlt auch wohl schlicht die getId()-Methode bzw die getName()-Methode.

Zur Vergabe der ID noch ein kurzes Wort, ich würde eine schlichtes
Code:
this.id = nextId();
machen und dort dann die Überprüfung machen statt zuerst einen Check zu machen und danach die Zuweisung. Fasst die ganze Geschichte ein bisschen zusammen, auch wenn es keinen wesentlichen Unterschied macht.
 
Hallo Gonzo, vielen Dank für deine Antwort. Ich habe deinen Tipp angenommen und meinen Code geändert, hoffe ich hab dich richtig verstanden. Weiterhin möchte ich eine weitere Klasse zeigen "RobotModel", denn von die erbt "R2D2", dort sind auch die Methoden getId und getName könnte vielleicht dort was falsch implementiert sein?

Klasse R2D2, die nach deinem Tipp geändert wurde:

Java:
public class R2D2 extends RobotModel {

	private static final int MIN_ID_R2D2 = 0;
	private static final int MAX_ID_R2D2 = 9999;
	private static int buildCounter = MIN_ID_R2D2;

	public R2D2(String name) {
		this.name = name;
		//checkRobotId();
		this.id = nextId();//R2D2.buildCounter;
		R2D2.buildCounter++;
	}

	private int nextId() {
	    if (R2D2.buildCounter > MAX_ID_R2D2)
	           R2D2.buildCounter = MIN_ID_R2D2;
	    
	    
	    return buildCounter;
    }

    /*protected void checkRobotId() {
		if (R2D2.buildCounter > MAX_ID_R2D2) {
			R2D2.buildCounter = MIN_ID_R2D2;
		}

		
	}*/
}


Und hier die Klasse RobotModel wovon R2D2 erbt:

Java:
public abstract class RobotModel implements Robot {

	protected String name;
	protected int id;
	protected boolean powerSwitch = false;
	protected final int MAGIC_VALUE = 42;
	protected RobotException lastException = null;

	//////////////////////////////////////
	// Methoden aus Interface RobotControl
	//////////////////////////////////////

	public int getId() {
		return this.id;
	}

	public String getName() {
		return this.name;
	}

	public void triggerPowerSwitch() {
		if (!this.powerSwitch) {
			this.powerSwitch = true;
		} else
			this.powerSwitch = false;
	}

	public boolean isPowerOn() {
		return this.powerSwitch;
	}

	public void setLastException(RobotException lastException) {
		this.lastException = lastException;
	}
	
	public RobotException getLastException() {
		return this.lastException;
	}

	protected void checkRobotId() {}

	///////////////////////////////////////////
	// Methoden aus Interface RobotInstructions
	///////////////////////////////////////////
	
	public String speak(int[] zahlen) throws RobotException {
		String outputString = "";
		try {

			if (!this.isPowerOn()) {
				throw new RobotIllegalStateException("Roboter ist ausgeschaltet!",
						this.name);
			}

			if (!(Arrays.binarySearch(zahlen, this.MAGIC_VALUE) < 0)) {
				throw new RobotMagicValueException("Zahl 42 im Zahlenarray!", this.name);
			}

			for (int i = 0; i < zahlen.length - 1; i++) {
				if (this instanceof C3PO) {
					outputString += String.valueOf(zahlen[i]) + ";";
				} else if (this instanceof R2D2)
					outputString += String.valueOf(zahlen[i]) + ",";
			}
			outputString += String.valueOf(zahlen[zahlen.length - 1]);

		} catch (RobotException e) {
			System.out.println(e.getRobotName() + ": " + e.getMessage());
			this.setLastException(e);
		}
		return outputString;

	}

	public int[] think(int[] zahlen) throws RobotException {

		try {
			if (!this.isPowerOn()) {
				throw new RobotIllegalStateException("Roboter ist ausgeschaltet!",
						this.name);
			}

			if (!(Arrays.binarySearch(zahlen, this.MAGIC_VALUE) < 0)) {
				throw new RobotMagicValueException("Zahl 42 im Zahlenarray!", this.name);
			}

			Arrays.sort(zahlen);
			//falls Roboter C3PO wird das sortierte Array in umgekehrter Reihenfolge erstellt
			if (this instanceof C3PO) {
			    int[] arrayReverse = new int[zahlen.length];
					
			        for(int i = 0; i < zahlen.length; i++){
			            arrayReverse[zahlen.length-i-1] = zahlen[i];
			        }
			        zahlen = arrayReverse;
			}

		} catch (RobotException e) {
			System.out.println(e.getRobotName() + ": " + e.getMessage());
			this.setLastException(e);
		}
		return zahlen;
	}

}
 
Also generell erstmal noch eine Anmerkung zu deiner Umsetzung meines Vorschlags. Ich hatte das eher so gemeint (ich erkläre auch gleich warum):

Java:
    public R2D2(String name) {
        this.name = name;
        //checkRobotId();
        this.id = nextId();//R2D2.buildCounter;
        // R2D2.buildCounter++;  Diese Zeile brauchst du nicht, wenn du weiter unten folgendes schreibst
    }
 
    private int nextId() {
        if (R2D2.buildCounter > MAX_ID_R2D2)
               R2D2.buildCounter = MIN_ID_R2D2;
        
        
        return buildCounter++; // Hier ein ++!
    }

Der Operator ++ funktioniert nämlich so, dass er dir den Wert der Variable gibt und ihn nach der Verwendung um 1 erhöht. In diesem Fall wird also der Wert von buildCounter als return-value genommen und danach um 1 erhöht. Das ist genau das Verhalten, das du oben auch schon hast, allerdings ist das eben eine Zeile mehr und relativ unnötig. 🙂

Zu deinem ursprünglichen Problem weiß ich leider keinen Ansatz, vielleicht liegt das Problem in der Factory. Denn so wie ich das sehe wird die ID ja nur beim Erstellen einer neuen Instanz verändert (in dem Fall um 1 erhöht). Könnte also sein, dass du - aus irgendeinem Grund - nicht nur ein Objekt erzeugst in deiner Factory. Ansonsten würde mir da auch nix mehr einfallen. Oder wird die ID noch irgendwo verwendet?
 
Ok jetzt habe ich verstanden was du genau gemeint hast mit der einen Zeile mehr, Danke ;-) !

Zur Factory ich zeig sie gerade mal:

Java:
public class RobotFactory {

	private RobotFactory() {
	}

	public static Robot createRobot(RobotType robotType, String robotName) {

		if (robotType == RobotType.R2D2) {
			return new R2D2(robotName);
		} else if (robotType == RobotType.C3PO) {
			return new C3PO(robotName);
		}
		return null;
	}

}

Und durch die Enumaration wird dient dazu, dass ich zwischen zwei verschiedenen Typen von Robotern auswählen kann:

Java:
public enum RobotType {
	R2D2 , C3PO;
}


Aber eigentlich wird durch die Factory nur der Typ erstellt den ich auch angebe und die ID ist halt nur in der abstrakten Klasse RobotModel allerdings erbt ja R2D2 oder CP3O von dieser Klasse je nachdem was ich für einen Typen erstelle. Hmm, es ist wirklich komisch
 
Zuletzt bearbeitet:
Also hab deinen Code genommen und den Test ausgeführt, ich hatte soweit keine Probleme. Allerdings sollte dir bewusst sein, dass folgender Test wohl falsch ist:

Java:
    @Test
    public void testName() {
        assertEquals(0, test1.getName()); 
        assertEquals(1, test2.getName());    
        assertEquals(2, test3.getName());   
    }

Da du hier die Namen mit den (vermutlichen) IDs vergleichst, aber bei getName() natürlich die Variable name zurückgegeben wird, also "Bernd", "Achim" und "Klaus". Dieser Test schlägt an der Stelle fehl, weil der Test etwas falsches testet.

Bei den Tests mit der ID hatte ich allerdings kein Problem. Da kommt bei mir kein Fehler. Und ich sehe wie gesagt auch keine Stelle, die da noch groß relevant sein könnte... :bahnhof:
 
Sry Gonzo, hatte das mit den Namen absichtlich drin also 0,1,2 um zu sehen ob dann bei den Fehlern auch die richtigen Namen kommen. Hätte ich erwähnen sollen 😱ops .

Kannst du bitte das hier bei dir testen, also da kommt dann bei mir bei test2Id anstatt 1 die 4 und bei bei test3Id die 8, irgendwie erhöht sich der Counter um 4 aber wie, wo und warum er das macht keine Ahnung ;( ? Wenn die Tests bei dir richtig laufen dann stimmt irgendwas bei Eclipse von mir nicht. Also hier die Tests:

Java:
public class TestR2D2 {
    
    // R2D2 Roboter erstellen
    Robot test1 = RobotFactory.createRobot(RobotType.R2D2, "Bernd");
    Robot test2 = RobotFactory.createRobot(RobotType.R2D2, "Achim");
    Robot test3 = RobotFactory.createRobot(RobotType.R2D2, "Klaus");
    
    @Test
    public void test1Id() {
        assertEquals(0, test1.getId()); 
          

    }
    
    @Test
    public void test2Id() {
    
        assertEquals(1, test2.getId());    
      
    }
    
    @Test
    public void test3Id() {
       
       
        assertEquals(2, test3.getId());    

    }
    
    @Test
    public void test1Name() {
        assertEquals("Bernd", test1.getName());    
    }
    
    @Test
    public void test2Name() {
        assertEquals("Achim", test2.getName());    
    }
    
    @Test
    public void test3Name() {
        assertEquals("Klaus", test3.getName());    
    }
   
    @Test
    public void test1isPowerOn() {
        assertEquals(false, test1.isPowerOn());    
    }
    @Test
    public void test2isPowerOn() {
        assertEquals(false, test2.isPowerOn());    
    }
    @Test
    public void test3isPowerOn() {
        assertEquals(false, test3.isPowerOn());    
    }
    
    @Test
    public void test1TriggerPowerSwitch() {
        test1.triggerPowerSwitch();
        assertEquals(true, test1.isPowerOn());    
    }
    @Test
    public void test2TriggerPowerSwitch() {
        test1.triggerPowerSwitch(); 
        test1.triggerPowerSwitch();
        assertEquals(false, test1.isPowerOn()); 
    }
}
 
Kann es vielleicht damit zusammenhängen, dass ich einer nicht static variablen also der id in RobotModel einen static Wert über die Klasse R2D2 zuweise?

Java:
public abstract class RobotModel implements Robot {

	protected String name;
	protected int id;
	protected boolean powerSwitch = false;
	protected final int MAGIC_VALUE = 42;
	protected RobotException lastException = null;

	//////////////////////////////////////
	// Methoden aus Interface RobotControl
	//////////////////////////////////////

	public int getId() {
		return this.id;
	}

Java:
public class R2D2 extends RobotModel {

	private static final int MIN_ID_R2D2 = 0;
	private static final int MAX_ID_R2D2 = 9999;
	private static int buildCounter = MIN_ID_R2D2;

	public R2D2(String name) {

		this.name = name;
 		//checkRobotId();
		this.id = nextId();//R2D2.buildCounter;
		//R2D2.buildCounter++;

	}

	private int nextId() {
	    
	    if (R2D2.buildCounter > MAX_ID_R2D2)
	           R2D2.buildCounter = MIN_ID_R2D2;
	        

	    return R2D2.buildCounter++;
    }
	}
 
Also, erst einmal, selbe Funktionen wie getId() würde ich immer in einem Test überprüfen und nicht mehrere Tests. Aber das ist sicherlich auch so eine Geschmackssache, ich weiß gerade nicht was hier die "korrekten" Conventions wären, aber ich finde es übersichtlicher.

Und nun der Grund, weshalb deine Tests nicht funktionieren. Vor JEDEM Test, also pro Methode in der Klasse TestR2D2, wird der Test neu vorbereitet. Das heißt, dass auch die Variablen neu initialisiert werden. Wenn du darauf achtest, dann wird dir auch auffallen, dass die Tests nicht immer in der gleichen Reihenfolge ausgeführt werden. Das hat gute Gründe, Tests sollen wiederholbar, unabhängig und schnell sein. Im Klartext heißt das, dass bei dir natürlich nicht nur 3, sondern eben mehr Roboter instanziiert werden und dementsprechend ist die ID höher. Das kannst du auch nicht beheben, indem du ein eine Methode mit @Before-Annotion deklarierst, die wird auch jedes Mal ausgeführt. Da musst du dir nochmal genau überlegen, wie du das sinnvoll testen kannst.
 
Ok super das macht alles Sinn. Ich werde die Tests umändern und überlegen wie ich das mit der Id doch noch hinbekommen. Dann wären eigentlich meine Fragen zu diesem Thema geklärt. Wie ich es umändere darauf will ich selbst kommen ;-).

Vielen Dank Gonzo für die sehr hilfreichen Informationen und Tipps!
 

Neue Themen


Zurück
Oben