2013-04-09 5 views
8

Kiedy piszę interfejs, często wygodnie jest definiować moje testy w tym samym pakiecie co interfejs, a następnie definiować wiele pakietów, które implementują zestaw interfejsów, np.W jaki sposób mogę mieć wspólny zestaw testów dla wielu pakietów w biegu?

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

Czy istnieje prosty sposób uruchomić ten sam zestaw testów (w tym przypadku, znajdujący się w opakowaniu/* _ test.go) w pakietach sub?

Najlepszym rozwiązaniem mam wymyślić tak daleko, aby dodać pakiet testowy:

package/tests/ 

który implementuje zestaw testów oraz test w każdym z wdrożeń, aby uruchomić testy, ale to ma dwie wady:

1) próby w opakowaniu/testy nie są w formacie _test.go i w końcu na część rzeczywistą biblioteki udokumentowana godoc itp

2) próby w opakowaniu/testy są uruchamiane przez niestandardowego biegacza testowego, który musi zasadniczo powielać wszystkie funkcjonalność "go test", aby skanować w poszukiwaniu testów i uruchamiać je.

Wydaje się być dość tandetnym rozwiązaniem.

Czy jest lepszy sposób na zrobienie tego?

Odpowiedz

7

Nie podoba mi się pomysł wykorzystania osobnej biblioteki testowej. Jeśli masz interfejs i masz ogólne testy dla każdego interfejsu, inne osoby, które implementują ten interfejs, mogą również chcieć skorzystać z tych testów.

Można utworzyć pakiet "package/test" że zawiera funkcję

// 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) 

Zauważ, że podpis TestInterface nie odpowiada temu, co go test spodziewa. Teraz dla każdego pakietu package/impl/x dodać jeden plik 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}) 
} 

Gdzie New() jest funkcja konstruktora implementacji. Zaletą tego systemu jest to, że

  1. Twoje testy są wielokrotnego użytku dla tego, kto realizuje swój interfejs, nawet z innych pakietów
  2. To jest oczywiste, że po uruchomieniu ogólny zestaw testowy
  3. Przypadki testowe gdzie realizacja jest, a nie w innym, niejasne miejsce
  4. Kod może być łatwo dostosowane jeśli realizacja wymaga specjalnej inicjalizacji lub podobny materiał
  5. Nadszedł go test kompatybilny (duży plus!)

Oczywiście w niektórych przypadkach potrzebna jest bardziej skomplikowana funkcja TestInterface, ale jest to podstawowa idea.

+0

Tak, to prawie to, co robię (używając argumentów var, aby umożliwić implowi dostarczenie funkcji konfiguracji i rozpadu, jeśli chce); jak już mówiłem, nieco denerwujące jest to, że funkcje pojawiają się w generowanym wynikowym dokumencie doc, a posiadanie jednego "mega testu", który uruchamia wszystkie testy podrzędne, powoduje, że test "go test" zawiesza się na wieki na tym jednym przedmiocie (zgadzam się z wszystkie twoje pozostałe punkty). – Doug

+0

@Doug Wolę używać struct dla funkcji pomocniczych i danych; jest znacznie czystszy i możesz dostarczyć zero (domyślna wartość dla wskaźników), jeśli nie ma takiej funkcji. Możesz również usunąć niektóre testy, jeśli ustawisz [testing.Short()] (http://golang.org/pkg/testing/#Short) i usuniesz kilka przypadków testowych, jeśli tak jest. – fuz

+0

@Doug BTW, jaki jest sens funkcji testowych w dokumentacji? Możesz przenieść je do innej paczki (zgodnie z sugestią), jeśli zaśmiecają dokumentację. – fuz

1

Jeśli udostępnisz fragment kodu do ponownego wykorzystania przez różne pakiety, to tak, z definicji jest to biblioteka. Nawet jeśli jest używany tylko do testowania z plików * _test.go. Nie różni się od importowania "testowania" "fmt" w pliku _test.go. A posiadanie API udokumentowanego przez godoc to plus, a nie minus IMHO.

1

Może coś się tu nieco pomieszało: Jeśli pakiet a definiuje interfejs tylko wtedy, gdy nie ma kodu do testu , interfejsy w Go to implementacja bezpłatna.

Zakładam więc, że metody w twoim interfejsie w pakiecie mają ograniczenia w postaci . Na przykład. w

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

ty umowa zakłada, że ​​Zmęczone Zwraca true, jeśli więcej niż 500 kroki zostały Walk'ed (i false) i kod testu sprawdza te zależności (lub założenie, umów, niezmienniki cokolwiek Nazwij to).

Jeśli jest to przypadek chciałbym przedstawić (w opakowaniu a) wyeksportowanego funkcję

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") } 
} 

Jakie dokumenty umowy prawidłowo i może być używany przez pakiety B i C Typy realizacji Walker, aby przetestować ich implementacje w b_test.go i c_test.go. IMHO jest całkowicie w porządku, że te działają jak TestWalkerContract są wyświetlane przez godoc.

P.S. Częściej niż Walk and Tired może być stan błędu , który jest przechowywany i zgłaszany do momentu skasowania/zresetowania.

Powiązane problemy