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 Interfaces

(Updates: 12.11.2014, 24.11.2014)

Dieser Aufsatz macht auf einen hilfreichen Aspekt von Interfaces aufmerksam, und hoffentlich das Testen leichter.

Testen ist wichtig ... gehört aber zu den weniger beliebten Tätigkeiten eines Programmierers. Umso interessierter war ich als ich vor kurzem auf John Graham-Cummings Aufsatz " Go interfaces make test stubbing easy" stieß.

Und in der Tat, Interfaces machen das Testen leichter, indem nämlich das zu testende Paket während des Tests von Funktionen importierter Pakete isoliert wird. Los geht's!

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

package pak

import "pfad/zu/imp"

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

package pak

import "testing"

func TestFunk(t *testing.T) {
    // ...
    funk(v)
    // ...
}
// imptyp.go enthält den Typ ImpTyp mit seiner Methode Mach.

package imp

type ImpTyp struct{ ... }

func (ImpTyp) Mach() { ... }

Ein Funktion funk im Paket pak arbeitet mit einer Variablen v vom Typ ImpTyp und ruft dessen Methode Mach. ImpTyp und Mach sind im Paket imp deklariert. So weit, so gut!

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 Methode Mach zu testen. Erstens weil das an anderer Stelle passiert, nämlich bei den Tests im Paket imp. Und zweitens weil Mach eine Vielzahl von Dingen voraussetzen kann, z.B. eine geöffnete Datenbank, die nun überhaupt nichts mit dem zu testenden Kode in funk zu tun haben, und das Testen unnötig kompliziert machen.

So geht's:

  1. Ich deklariere im Paket pak ein Interface Macher, das vom Typ ImpTyp implementiert wird, und sorge dann dafür, dass die übergebene Variable v vom Typ Macher ist:
    // funktion.go enthält die Funktion funk.
    
    package pak
    
    import "pfad/zu/imp"
    
    type Macher interface {
        Mach()
    }
    
    func funk(v Macher) {
        // ...
        v.Mach()
        // ...
    }
    
    Damit funktioniert das Paket pak genausogut wie vorher. Aufrufe von funk bleiben unverändert; dort wird weiterhin eine Variable mit dem konkreten Typ imp.ImpTyp übergeben.
  2. Im Testkode deklariere ich einen leeren Typ DumTyp mit einer Methode Mach, die nichts macht. Damit genügt auch dieser Typ dem Interface Macher. Ich kann jetzt für den Test eine Variable vom Typ DumTyp erzeugen und an die zu testende Funktion Mach übergeben:
    // funktion_test.go enthält einen Test für die Funktion funk.
    
    package pak
    
    import "testing"
    
    type DumTyp struct{}
    
    func (DumTyp) Mach() {}
    
    func TestFunk(t *testing.T) {
        // ...
        v := DumTyp{}
        funk(v)
        // ...
    }
    
    Damit testet TestFunk die Funktion funk ohne dass von dort Kode aus dem Paket imp aufgerufen werden müsste ... Applaus!

Eine unerwartete Schwierigkeit

Was tun, wenn Mach im Paket imp keine Methode, sondern eine einfache Funktion ist?

Nun, wenn imp ein externes Paket ist, dann habe ich verloren. (Update: 12.11.2014)

Ein eigenes Paket imp könnte ich z.B. ändern, indem ich dort einen leeren Typ Imp deklariere und aus jeder exportierten Funktion, in unserem Beispiel also aus Mach, eine Methode mit einem Empfänger vom Typ Imp machte.

(Update: 12.11.2014)
Eine bessere Methode, die auch mit Funktionen aus fremden Paketen funktioniert, beschreibe ich in "Testen mit Funktionszeigern".

Grundlegendes und Weiterführendes

Grundsätzliches zum Testen in Go findet man in " Wie man mit Go arbeitet" und natürlich in der Doku zum Paket testing. Zu tabellengesteuerten Tests wird man fündig beim go-wiki.

(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.

Wie Interfaces in Go intern arbeiten beschreibt Russ Cox in " Go Data Structures: Interfaces". Aus der Sicht eines Python-Programmierers schreibt Jordan Orelli über Go-Interfaces in " How to use interfaces in Go".

Oktober 2014