Frage zur TDD

Ich übe mich gerade mal an einem kleinen Test-Projekt an der TDD. Ich habe zwar bisher viel Testcode geschrieben, aber nur teilweise vorher, meist doch eher nachher.

Das Vorgehen ist doch eigentlich so, dass man im Produktivcode nur immer genau das hinschreibt, was den Test bestehen lässt, und nicht mehr. Also schreibe ich erst das Erzeugen eines Objekt in den Testcode und lege dann die Klasse an, weil es im Testcode einen Kompilerfehler gibt. Ich benutze den Konstruktor im Testcode, bevor ich ihn in den Produktivcode schreibe etc.

Wenn ich jetzt einen boolean-Wert in einer Methode zufällig belegen will, wie kann ich mich durch Tests dazu zwingen, da wirklich einen zufälligen Wert zu erzeugen? Selbst wenn ich hundert Aufrufe durchführe, könnten die hundert erzeugten Werte ja zufällig immer falsch sein. Ein zuverlässiger Test ist das nicht.

Wie mache ich das?

Da wirst du keinen zuverlässigen Test für schreiben können. Das liegt in der Natur der Zufälligkeit - es ist eben nicht vorhersagbar :wink:
Du kannst deinen Test mehrfach ausführen und die statistischen Maße - in diesem Fall ist der Erwartungswert wohl recht interessant - errechnen. Das ist aber keine Garantie, dass der Test durchgeht, der Test ist „brittle“ (wie heißt das denn auf deutsch?).

Gut, dann war ich also nicht nur zu einfallslos, sondern es lässt sich systembedingt nicht testen, und in dem Fall sollte ich den Zufallswert also schon “richtig” erzeugen. schmunzelt

Stimmt, ein Test, der 100% zuverlässig beweist, dass eine Methode wirklich zufällige Werte erzeugt, ist nicht zu schreiben. Versuche den Code, der Zufallswerte erzeugt, vom Rest des Codes zu isolieren. So kannst Du zumindest den Rest zuverlässig testen. Beim Zufallsteil dann einen Test, der gut genug ist. Ob man da jetzt auf erwartete Wahrscheinlichkeiten testen muss? Finde ich schon fast übertrieben.

P.S. Wenn Du Random verwendest, kannst Du diesen übrigens mit einem Seed so initialisieren, dass Du vorhersagen kannst, welche Zufallswerte dieser liefern wird. Das kann bei Deinem Test hilfreich sein.

Man kann zumindest Invarianten festlegen und testen (Beispielsweise muss das Ergebnis der Methode immer identisch sein, wenn die Klasse einen Random-Generator mit gleichem Seed verwendet).

/Da war wieder jemand schneller :slight_smile:

Es kommt auf die Implementierung an, die deinen Zufallswert erzeugt. Wenn du da nur eine fremde API nutzt, dann brauchst du deine Wrapper Methode auch nicht testen. Und wenn du eigene Logik hast, dann ist es wahrscheinlich, dass es sich nur um Pseudo Zufälligkeit handelt und du eigentlich schon immer weißt, welcher Wert bei definierten Parametern erwartet wird.

Und wenn andere Tests von dieser Methode abhängig sind, kannst du die Methode natürlich mocken.

Letztlich ist das Testen von Zufallswerten mit Unit Tests schwierig, da Unit Tests ein erwartetes Verhalten testen sollen. Bei echten Zufallswerten kennt man den erwarteten Wert nicht. Man kennt meistens nur bestimmte Kriterien, wie Grenzwerte oder Wiederholbarkeit. Das kann man dann auch mit Unit Tests abdecken.

Ganz so schlimm ist das eigentlich gar nicht. Wenn du den Erwartungswert errechnest, dann ist der Test nur in Ausnahmefällen rot. Dem Gesetz der großen Zahlen zufolge muss die Anzahl Iterationen nur groß genug sein.
Dadurch könntest du dir sogar Tests schreiben, die die Qualität des Zufallszahlengenerators prüft.

Edit: da kam ja jetzt ganz schön viel auf einmal :wink:

*** Edit ***

Noch eine Verständnisfrage: ich habe glaube ich den Ausgangspost falsch gelesen. Ich hatte es verstanden, dass du einen zufällig generierten Wert auf Zufälligkeit hin testen möchtest.
Jetzt bei erneutem Lesen sieht es aber so aus, als ob du eine Methode mit zufälligen Werten füttern möchtest. Und das ist aus oben von @deetee genannten Gründen normalerweise nicht sinnvoll.

Gut, dann war ich also nicht nur zu einfallslos, sondern es lässt sich systembedingt nicht testen, und in dem Fall sollte ich den Zufallswert also schon “richtig” erzeugen. schmunzelt

Die Idee mit dem Seed ist gut. Ist zwar vielleicht etwas überkandidelt für das Testprojekt, aber wenn ich das schon übe, dann richtig.

Wie stelle ich mir das mit dem Trennen vor? Ich schreibe eine Extraklasse, die meine zufälligen Werte erzeugt, und übergebe meiner Klasse, die diese verwendet, diese Extraklasse und für Tests ersetze ich die Zufallsklasse dann durch eine, die immer bspw. auf true setzt?

*** Edit ***

genau. Keine zufälligen Parameter.

@cmrudolph
Du hast ihn schon richtig verstanden, ich denke aber, es ist nicht klar, wie die Implementierung seiner Methode aussieht, und daher hab ich einfach mal etwas geraten.

Also ich würde einen package private Getter für ein [JAPI]Random[/JAPI]-Objekt schreiben. In meinem Test erzeuge ich eine Ableitung des Testobjekts in dem ich diese Methode überschreibe. In der überschriebenen Methode prüfe ich, ob ich tatsächlich ein Random-Obket zurück bekommen und liefere ein gemocktes Random-Objekt zurück, das prüft, ob nextBoolean() aufgerufen wurde und wo nextBoolean() natürlich kein zufälliges Ergebnis zurückliefert.

bye
TT

Auch ein interessanter Ansatz. Ich hab es jetzt so gemacht:

Es gibt ein Interface BooleanInitializer:


    boolean getInitialValue();

}```

und dazu den `RandomBooleanInitialiser`:

```import java.util.Random;

public class RandomBooleanInitialiser implements BooleanInitializer {

    Random random;

    public RandomBooleanInitialiser() {
        random = new Random();
    }

    public RandomBooleanInitialiser(long seed) {
        random = new Random(seed);
    }

    @Override
    public boolean getInitialValue() {
        return random.nextBoolean();
    }

}```

mit dem dazugehörigen Test `RandomBooleanInitialiserTest`:

```import static org.junit.Assert.*;

import org.junit.Test;

public class RandomBooleanInitialiserTest {

    @Test
    public void create1() {
        BooleanInitializer initializer = new RandomBooleanInitialiser();
        assertNotNull(initializer);
    }

    @Test
    public void create2() {
        BooleanInitializer initializer = new RandomBooleanInitialiser(17L);
        assertNotNull(initializer);
    }

    @Test
    public void getInitialValue1() {
        BooleanInitializer initializer = new RandomBooleanInitialiser(0L);
        boolean actual = initializer.getInitialValue();
        assertEquals(true, actual);
    }

    @Test
    public void getInitialValue2() {
        BooleanInitializer initializer = new RandomBooleanInitialiser(0L);
        //for (int i=0; i<10; ++i) {
        //    System.out.println(initializer.getInitialValue());
        //}
        // true, true, false, true, true, false, true, false, true, true
        initializer.getInitialValue(); // ersten verbrauchen
        initializer.getInitialValue(); // zweiten verbrauchen
        boolean actual = initializer.getInitialValue();
        assertEquals(false, actual);
    }

}```

Zum Testen der eigentlichen Klasse verwende ich dann den `TrueBooleanInitialiser`:

```public class TrueBooleanInitialiser implements BooleanInitializer {

    @Override
    public boolean getInitialValue() {
        return true;
    }

}```

und der Methode, die den zufälligen Wert erzeugen soll, wird dann halt so ein Objekt übergeben.


Edit: Vielleicht ist unprakatisch, später von außen immer so einen Initializer mitgeben zu müssen. Dann würde ich vielleicht einen Paketsichtbaren Setter schreiben, mit dem der zufällige Initializer für Tests mit einem nicht zufälligen überschrieben werden kann.

Ganz einfach: Im Test schreibt man die Ergebnisse des Methodenaufrufs in eine Kontingenztafel und macht dann einen Chi-Quadrat-Unabhänigkeitstest um die statistische Signifikanz etwaiger Abweichungen vom Erwartungswert zu bestimmen.

:o)

Oder, der Klassiker:

Na, ich denke, dass man das nicht vernünftig testen kann, aber… weiteres Infragestellen überlasse ich mal anderen :slight_smile:

@Crian
Wie ich vermutet habe musst du deine Wrapper Klasse nicht testen, da du damit nur die Java API testest.

Und statt mit deiner Klasse TrueBooleanInitialiser zu arbeiten, wäre ein Mock Objekt besser geeignet.

Dann hab ich nicht verstanden, was ein Mock-Objekt ist. Ich dachte, der TrueBooleanInitialiser wäre eines.

Edit: Nach dem was auf http://de.wikipedia.org/wiki/Mock-Objekt so steht, ist es doch genau sowas. Der TrueBooleanInitialiser liegt auch nicht im src-, sondern im Test-Pfad.

Ja, es ist eine Mock Objekt. Aber es gibt Mocking Frameworks die dir für deine produktive Klasse ein Mock Objekt zurückliefern können und man kann eben das Verhalten des Mock Objekts per API definieren. Man erstellt Mock Klassen eigentlich nur in Ausnahmefällen selbst. Ich habe bisher nur einmal ein eigene Mock Klasse benötigt, da das simulierte Verhalten etwas komplexer war. Wäre aber wohl auch ohne eigene Mock Klasse irgendwie gegangen.

Ah, danke. Dann muss ich mir mal anschauen, wie man sowas macht.

Empfehle JMockit.

[QUOTE=maki]http://martinfowler.com/articles/mocksArentStubs.html

Empfehle JMockit.[/QUOTE]
Mockito…

:stuck_out_tongue:

Mockito ist leider nur ein Test Spy, witzigerweise kannten die Entwickler von Mockito die definition von Mock nicht als sie es erstellt hatten…