2013-04-09 3 views
8

Wenn ich eine Schnittstelle schreibe, ist es oft bequem, meine Tests im selben Paket wie die Schnittstelle zu definieren und dann mehrere Pakete zu definieren, die die Schnittstellengruppe implementieren, z.Wie kann ich eine gemeinsame Testsuite für mehrere Pakete in go haben?

package/ 
package/impl/x <-- Implementation X 
package/impl/y <-- Implementation Y 

Gibt es einen einfachen Weg, um die gleiche Testsuite (in diesem Fall befindet sich im Paket/* _ test.go) in den Unterpakete laufen?

Die beste Lösung, die ich mit so weit kommen habe, ist ein Testpaket hinzuzufügen:

package/tests/ 

, die die Test-Suite implementiert, und einen Test in jedem der Implementierungen die Tests laufen, aber dies zwei Nachteile hat:

1) die Tests in Paket/Tests sind nicht in _test.go Dateien, und einen Teil der tatsächlichen Bibliothek am Ende werden durch godoc dokumentiert usw.

2) die Tests in Paket/tests werden von einem benutzerdefinierten Test-Runner ausgeführt, der im Grunde alle duplizieren muss Funktionalität von 'go test', um nach Go-Tests zu suchen und sie auszuführen.

Scheint wie eine ziemlich klebrige Lösung.

Gibt es einen besseren Weg, dies zu tun?

Antwort

7

Ich mag nicht nicht wirklich die Idee, eine separate Test Bibliothek zu verwenden. Wenn Sie eine Schnittstelle haben, und Sie haben generische Tests für jede Schnittstelle, andere Menschen, die diese Schnittstelle implementieren könnte wie diese Tests auch verwenden.

Sie könnten ein Paket "package/test" erstellen, die eine Funktion

// functions needed for each implementation to test it 
type Tester struct { 
    func New() package.Interface 
    func (*package.Interface) Done() 
    // whatever you need. Leave nil if function does not apply 
} 

func TestInterface(t *testing.T, tester Tester) 

Hinweis enthält, dass die Unterschrift von TestInterface nicht zu dem, was go test erwartet entspricht. Nun, für jedes Paket package/impl/x Sie fügen eine Datei generic_test.go:

package x 

import "testing" 
import "package/test" 

// run generic tests on this particular implementation 
func TestInterface(t *testing.T) { 
    test.TestInterface(t,test.Tester{New:New}) 
} 

Wo New() der Konstruktor-Funktion Ihrer Implementierung ist. Der Vorteil dieser Regelung ist, dass

  1. Ihre Tests für wiederverwendbar sind, wer setzt Ihre Schnittstelle, auch aus anderen Paketen
  2. Es ist sofort klar, dass Sie die generische Testsuite
  3. Die Testfälle laufen, wo die Implementierung und nicht an einem anderen, obskuren Ort
  4. Der Code kann leicht angepasst werden, wenn eine Implementierung spezielle Initialisierung oder ähnliche Sachen braucht
  5. Es ist go test kompatibel (großes Plus!)

Natürlich in einigen Fällen müssen Sie eine kompliziertere TestInterface Funktion, aber das ist die Grundidee.

+0

Ja, das ist ziemlich genau das, was ich tue (mit var args, um die Impl liefern eine Setup-und Teardown-Funktion, wenn es will); Wie ich schon sagte, es ist ein bisschen nervig, dass die Funktionen in generierten go doc Ausgaben auftauchen, und einen 'Megatest' zu haben, der alle Untertests ausführt, lässt 'Test gehen' ewig auf diesem einen Gegenstand hängen (stimme zu all deine anderen Punkte tho). – Doug

+0

@Doug Ich bevorzuge es, eine Struktur für Hilfsfunktionen und Daten zu verwenden; es ist viel sauberer und Sie können Null (der Standardwert für Zeiger) liefern, wenn es keine solche Funktion gibt. Möglicherweise möchten Sie auch einige Tests streichen, wenn [test.Short()] (http://golang.org/pkg/testing/#Short) festgelegt ist, und einige Testfälle entfernen, wenn dies der Fall ist. – fuz

+0

@Doug BTW, was ist der Punkt, dass die Testfunktionen in der Dokumentation angezeigt werden? Sie können sie in ein anderes Paket verschieben (wie ich es vorgeschlagen habe), wenn sie die Dokumentation überladen. – fuz

1

Wenn Sie ein Stück Code für die Wiederverwendung durch verschiedene Pakete freigeben, dann ist es ja eine Bibliothek per definitionem. Auch wenn es nur zum Testen von * _test.go-Dateien verwendet wird. Es unterscheidet sich nicht vom Importieren von "Testen" von "fmt" in der Datei _test.go. Und die API von godoc dokumentiert zu haben ist ein Plus, nicht minus IMHO.

1

Vielleicht etwas verwirrt hier ein wenig: Wenn Paket a definiert nur eine Schnittstelle als es gibt keinen Code zu Test als Schnittstellen in Go sind Implementierung frei.

Also ich nehme an, die Methoden in Ihrer Schnittstelle in Paket haben Einschränkungen. Z.B. in

interface Walker { 
    Walk(step int) 
    Tired() bool 
} 

Sie Vertrag geht davon aus, dass Tired gibt true zurück, wenn mehr als 500 Stufen haben Walk'ed (andernfalls false) gewesen und Ihren Testcode prüft diese Abhängigkeiten (oder Annahme, Verträge, Invarianten, was Sie Benenne es).

Ist dies der Fall, dass ich (im Paket a) eine exportierte Funktion

func TestWalkerContract(w Walker) error { 
    w.Walk(100) 
    if w.Tired() { return errors.New("Tired after 100 steps") } 
    w.Walk(450) 
    if !w.Tired() { return errors.New("Not tired after 100+450 steps") } 
} 

, die den Vertrag ordnungsgemäß dokumentiert bieten würde und kann mit Arten der Umsetzung Walker von Paketen b und c verwendet werden, um ihre zu testen Implementierungen in b_test.go und c_test.go. IMHO ist es völlig in Ordnung, dass diese Funktion wie TestWalkerContract von Godoc angezeigt werden.

P.S. Häufiger als Walk and Tired könnte ein Fehlerzustand sein, der bis zum Löschen/Zurücksetzen gespeichert und gemeldet wird.

Verwandte Themen