Das treibt mich OCD-verrückt. Angenommen, ich habe die folgende Funktion:Wie kann ich diese Select-Anweisung für eine garantierte 100% -Testabdeckung neu schreiben?
func Map(quit <-chan struct{}, dst chan<- interface{}, src <-chan interface{}, f func(interface{}) interface{} {
for {
select {
case v, ok := <- src:
if !ok {
return
}
select {
case dst <- f(v):
case <-quit:
return
}
case <-quit:
return
}
}
}
Es f sendet (v) auf dst für jeden Wert v von src erhalten, bis entweder src oder beenden geschlossen und leer oder ein Wert empfangen wird von beenden.
Nun, ich nehme an, einen Test schreiben möchten, die, dass es zeigt, abgebrochen werden kann:
func TestMapCancel(t *testing.T) {
var wg sync.WaitGroup
quit := make(chan struct{})
success := make(chan struct{})
wg.Add(3)
src := // channel providing arbitrary values until quit is closed
dst := make(chan interface{})
// mapper
go func() {
defer wg.Done()
defer close(dst)
Map(quit, dst, src, double)
}()
// provide a sink to consume values from dst until quit is closed
timeout(quit, 10*time.Millisecond)
wait(success, &wg)
select {
case <-success:
case <-time.After(100 * time.Millisecond):
t.Error("cancellation timed out")
}
}
Die undefinierte Funktionen sind hier nicht sehr wichtig. Bitte gehen Sie davon aus, dass sie funktionieren. timeout
schließt seinen Kanalparameter nach der angegebenen Zeit und wait
schließt seinen Kanalparameter nach wg.Wait()
.
Das Problem ist, dass dies keine 100% ige Abdeckung bietet, da ein select case einheitlich unter (pseudo-) random gewählt wird, wenn beide zum Senden/Empfangen bereit sind. Die folgende Version von Map
hat dieses Problem nicht, leidet aber unter Potenzial unbestimmter Blockierung, wenn der Upstream-Kanal (src) nicht geschlossen ist: kann
func Map(quit <-chan struct{}, dst chan<- interface{}, src <-chan interface{}, f func(interface{}) interface{}) {
for v := range src {
select {
case dst <- f(v):
case <-quit:
return
}
}
}
I sort-of dieses Problem lösen, indem Sie den Test zu wiederholen Umschreiben mehrmals in einer Schleife, so dass jeder Zweig zufällig ausgewählt werden kann. Ich habe so wenig wie 10 Iterationen probiert und 100% Deckung mit allen bestandenen Tests erreicht (es gibt andere Tests außer diesem). Aber es bringt mich um den Verstand, dass ich scheinbar keine Best-of-Worlds-Version schreiben kann, die nicht blockiert, wenn der Upstream-Kanal nicht geschlossen ist und garantiert 100% Testabdeckung bietet (nicht nur wahrscheinlich).
Irgendwelche Inspiration für mich?
P.S., wenn Sie neugierig sind, warum "nicht blockiert, wenn Upstream-Kanal nicht geschlossen ist" ist wichtig, es ist nur ein weiteres Stück von OCD. Diese Funktion wird exportiert, was bedeutet, dass sich mein Code falsch verhält, wenn sich der Clientcode nicht richtig verhält. Ich möchte, dass es widerstandsfähiger ist als das, was die erste Version ist.
Ich würde es in mehrere Testfunktionen aufteilen. Außerdem gibt es zwei weitere Fälle, die Sie nicht testen: Empfangen von 'src', dann Senden von' dst' _then_ Schließen 'quit', und Beenden, weil' src' geschlossen wurde. https://play.golang.org/p/KI9OJLsHdc – Kaedys
Diese werden bereits in anderen Testfunktionen behandelt. Gerade dieser bestimmte gab mir Anfälle, weil ich nicht bemerkt hatte, dass ich die Auswahlanweisung deterministisch durchqueren könnte, indem ich Kanäle auf Null setze, so dass sie niemals empfangen würden. – burfl
Sie müssen sie nicht einmal auf Null setzen, um ehrlich zu sein. Einfach ungepufferte Kanäle zu machen, die man nicht als Empfang bezeichnet, ist mechanisch identisch mit dem Kanal, der Null ist. Ihr Test würde sich überhaupt nicht ändern, wenn Sie einen ungepufferten 'dst'-Kanal erstellen würden und ihn an' Map' übergeben würden und sich einfach nie darum kümmern würden, ein '<-dst' zu machen. – Kaedys