Randoop mit mehreren Projekten


Quelle : http://geekandpoke.typepad.com

Die Analyse bzw. automatische Testgenerierung für ein einzelnes Projekt mag ja noch ganz easy aussehen und das Randoop-Plugin für Eclipse hat auch den Charme, damit weiter rumzuspielen. Den ein oder anderen Fehler im Produktiv-Code konnte ich damit auch schon finden, aber selten sind die Projekte einfach und überschaubar. Viel mehr bietet es sich ja an, durch den Library/Dependeny-Charakter einer Java-Anwendung, auch die Abhängigkeiten mit zu prüfen. Nach den ersten Versuchen, auch mehrere Projekte mit Randoop auf einen Schlag mit 100000 Regressionstests abzudecken, hab ich dann auch die Möglichkeit dafür gefunden. Fraglich bleibt aber, ob man für ein einzelnes Projekt auch die ganzen Abhängigkeiten mittesten sollte. Folgendes Beispiel dafür : Es gibt wahrscheinlich in so ziemliche jedem Java-Projekt, einen Teil, den alle Entwickler als Backend nutzen: framework, core oder in unserem Fall einfach „utils“, welches die Klasse „Picture“ enthält, ein einfaches Pojo mit zwei Membern und den entsprechenden Gettern und Settern. Und dann gibt es noch das eigentliche Projekt : „camera“, welches eine Klasse „Photo“ besitzt. Photo ist ebenfalls ein einfaches Pojo. Allerdings mit zwei primitiven Membern und einem Member vom utils-Typ „Picture“. Um das zu testen, bzw. dafür Random-Tests generieren zu lassen, muss man sich nochmal daran erinnern, was Randoop eigentlich macht : Es erstellt ein Modell der Public-API, also in diesem Fall lediglich die Getter und Setter, zu einer Execution-Trace, also einer Ausführungsspur, welcher dann bei der Test-Erstellung gefolgt wird. Begonnen wird dabei bei den Primitiven (die man ja auch seinen Forderungen entsprechend anpassen kann) und die Trace führt letztlich dazu, dass auch Nicht-Primitive, wie eben unsere Picture-Klasse von Randoop instanziiert wird, um ein anderes Nicht-Primitives zu erzeugen. Das eigentlich entscheidene bei mehreren Projekten ist eigentlich, dem Classpath-Dropins zu folgen und alle benötigten Klassen mit in die TestGenerierung einzukippen.

Die beiden Projekte im Eclipse-Workspace :

In diesem einfachen Fall, habe ich per Eclipse-Export das Util-Projekt als jar exportiert und in den Classpath des Camera-Projektes eingebunden. Realistisch gesehen, macht man das natürlich per Buildsystem (Maven, Gradle, TFS etc.) und deployd in ein (oder zwei für die Sicher-Geher) entfernten Company-Repositories, damit auch die Kollegen Zugriff haben. Wie gesagt, hierfür reicht’s.

Die utils-Klasse Picture :

package eu.christopburmeister.utils;

public class Picture {
    /** width of picture. */
    private int width;
    /** height of picture. */
    private int height;
    
    /**
     * Ctor.
     * @param width
     * @param height
     */
    public Picture(int width, int height){
        this.width = width;
        this.height = height;
    }
    /**
     * getter.
     * @return witdh
     */
    public int getWidth() {
        return width;
    }
    /**
     * setter.
     * @param width
     */
    public void setWidth(int width) {
        this.width = width;
    }
    /**
     * getter.
     * @return height
     */
    public int getHeight() {
        return height;
    }
    /**
     * setter.
     * @param height
     */
    public void setHeight(int height) {
        this.height = height;
    }    
}

und die Photo-Klasse :

package eu.christopburmeister.camera;

import eu.christopburmeister.utils.Picture;

public class Photo {    
    /** the vendor of the camera. */
    private String cameraVendor;
    /** the type of the camera. */
    private String cameraType; 
    /** the main attributes of the photo. */
    private Picture picture;

    /**  Ctor. */
    public Photo() {
        // nop
    }
    
    /**
     * Ctor.
     * 
     * @param vendor
     * @param type
     * @param picture
     */
    public Photo(String vendor, String type, Picture picture) {
        this.cameraVendor = vendor;
        this.cameraType = type;
        this.picture = picture;
    }
    
    /**
     * setter.
     * @param cameraVendor
     */
    public void setCameraVendor(String cameraVendor) {
        this.cameraVendor = cameraVendor;
    }
    /**
     * getter.
     * @return cameraVendor
     */
    public String getCameraVendor() {
        return cameraVendor;
    }
    /**
     * setter.
     * @param cameraType
     */
    public void setCameraType(String cameraType) {
        this.cameraType = cameraType;
    }
    /**
     * getter .
     * @return cameratype
     */
    public String getCameraType() {
        return cameraType;
    }
    /**
     * setter.
     * @param picture
     */
    public void setPicture(Picture picture) {
        this.picture = picture;
    }
    /**
     * getter.
     * @return picture
     */
    public Picture getPicture() {
        return picture;
    }
}

Die Konfiguration des Randoop-Plugins sieht dann dementsprechend so aus :

Die Klassen aus utils werden via „referenced classpath“ hinzugefügt.

Anschließend das obligatorische „RUN“ und die Randoop-Generierung startet. Als Beispiel habe ich folgenden Test ausgewählt, an dem man sehr schön sieht, dass Randoop zuerst ein Objekt aus utils instanziiert, um dieses dann für einen Test gegen eine camera-Klasse zu nutzen :

public void test18() throws Throwable {

    if (debug) System.out.printf("%nRandoopTest0.test18");

    eu.christopburmeister.utils.Picture var6 = new eu.christopburmeister.utils.Picture(10, 1);
    eu.christopburmeister.camera.Photo var7 = new eu.christopburmeister.camera.Photo("hi!", "hi!", var6);
    eu.christopburmeister.utils.Picture var8 = var7.getPicture();
    var8.setHeight((-1));
    eu.christopburmeister.camera.Photo var11 = new eu.christopburmeister.camera.Photo("", "", var8);
    var8.setHeight(100);
    var8.setWidth((-1));
    
    // Regression assertion (captures the current behavior of the code)
    assertNotNull(var8);
}

Fazit : gewusst wie und dann funktioniert’s 🙂