Go Home Page
Die Programmiersprache Go
© 2014 Hans-Werner Heinzen @ Bitloeffel.de
Die Nutzung dieses Werks ist unter den Bedingungen der "Creative Commons Namensnennung - Keine Bearbeitungen 4.0 International"-Lizenz erlaubt.

Testen mit Funktionszeigern

(Update: 24.11.2014)

Dieser Aufsatz zeigt, dass auch Funktionszeiger das Testen leichter machen können.

Im letzten Aufsatz, das war "Testen mit Interfaces", habe ich beschrieben, wie man Methoden aus fremden Paketen durch Dummies ersetzen kann. Das ist hilfreich, weil man dadurch das Testen auf den Kode des zu testenden Pakets einschränkt. Der Beitrag von Matt Ho in diesem Gesprächsfaden hat mir gezeigt, dass man Gleiches auch für Funktionen aus fremden Paketen erreichen kann.

Starten wir also ähnlich wie beim letzten Mal:

// funktion.go enthält die Funktion funk.

package pak

import "pfad/zu/imp"

func funk() {
    //...
    imp.Mach()
    //...
}
// funktion_test.go enthält einen Test für die Funktion funk.

package pak

import "testing"

func TestFunk(t *testing.T) {
    //...
    funk()
    //...
}

Die Funktion funk im Paket pak ruft also die Funktion Mach aus dem (eventuell fremden) Paket imp:

package imp

func Mach() { ... }

Problem und Lösung

Wenn ich nun die Testroutine TestFunk starte, um die Funktion funk zu testen, so möchte ich das tun ohne mittelbar auch die fremde Funktion imp.Mach zu testen.

In Go sind Funktionen "Bürger erster Klasse"; man kann vieles mit ihnen tun. Man kann sie Variablen zuweisen, man kann sie Funktionen als Parameter mitgeben und natürlich können Funktionen auch Funktionen als Ergebniswerte zurückgeben. Das Erstere wollen wir nutzen:

  1. Im Paket pak deklariere ich eine Variable Mach vom Typ der Funktion imp.MachMach ist ein Funktionszeiger. Bevor der benutzt werden kann, muss er versorgt werden, am besten in einer init-Funktion. Und dann rufe ich in funk (oder wo auch immer) nicht mehr imp.Mach, sondern Mach auf:
    // funktion.go enthält die Funktion funk.
    
    package pak
    
    import "pfad/zu/imp"
    
    var Mach func()
    
    func init() { Mach = imp.Mach }
    
    func funk() {
        // ...
        Mach()
        // ...
    }
    
    Damit funktioniert das Paket pak genausogut wie vorher.
  2. Im Testkode deklariere ich eine Dummy-Funktion dumMach mit derselben Signatur, die auch imp.Mach hat. Zu Beginn der Testfunktion weise ich der Variablen Mach die Dummy-Funktion zu; dadurch wird von funk jetzt diese anstelle der originalen Funktion imp.Mach benutzt:
    // funktion_test.go enthält einen Test für die Funktion funk.
    
    package pak
    
    import "testing"
    
    func dumMach() {}
    
    func TestFunk(t *testing.T) {
        Mach = dumMach
        // ...
        funk(v)
        // ...
    }
    
    Also testet TestFunk jetzt die Funktion funk ohne dass von dort Kode aus dem Paket imp aufgerufen werden müsste ... Applaus!

Kompliziertere Fremdfunktionen

Mach hatte bisher eine Signatur, wie sie einfacher nicht geht, nämlich eine ohne Parameter und ohne Ergebnisparameter. Aber anders funktioniert's genauso. Nehmen wir an, Mach sähe so aus:

func Mach(in imp.Struk1) (out imp.Struk2, err error) { ... }

Dann müssen in funktion.go der Funktionszeiger so:

var Mach func(imp.Struk1) (imp.Struk2, error)

und in funktion_test.go die Dummy-Funktion so:

import "pfad/zu/imp"

func dumMach(in imp.Struk1) (out imp.Struk2, err error) { return }

aussehen. Wegen der extern deklarierten Strukturen muss für den Test jetzt auch imp importiert werden. Zur Laufzeit des Tests wird beim Aufruf von Mach, also dumMach, lediglich etwas nach in übergeben, und die Variablen out und err werden initialisiert; mehr passiert hier aber auch nicht.

Noch mehr Info

Allgemein zum Testen in Go möchte ich wieder verweisen auf " Wie man mit Go arbeitet" und auf die Doku zum Paket testing.

(Update: 24.11.2014)
Für Leser, die des Englischen mächtig sind, hier noch der Verweis auf einen Vortrag von Andrew Gerrand: Testing Techniques, und auf die dazugehörenden Dias.

Ein schönes Beispiel dafür, was man mit Funktionszeigern in Go noch machen kann, ist die Funktion makeHandler in diesem Aufsatz: "Netzdienste schreiben".

November 2014