2017-12-03 6 views
1

Ich verwende die Rohre und Filter Muster wie in diesem blog post beschrieben.Wie effektiv die Rohre und Filter Muster

Ich frage mich, wie man das effektiv testen kann. Meine Idee war, jeden Filter unabhängig voneinander zu testen. Zum Beispiel habe ich einen Filter, wie diese mag ich nur für jetzt

func watchTemperature(ctx context.Context, inStream <-chan int) { 
    maxTemp = 90 

    go func() { 
     for { 
      select { 
      case <-ctx.Done(): 
       return 
      case temp := <-inStream: 
       if temp > maxTemp{ 
        log.Print("Temperature too high!") 
       } 
      } 
     } 
    }() 
} 

In meinem Test überprüft, ob das Protokoll gedruckt wurde. Mein Test sieht wie folgt aus.

func TestWatchTemperature(t *testing.T) { 
    maxTemp = 90 

    ctx := context.Background() 
    inStream := make(chan int) 
    defer close(inStream) 
    watchTemperature(ctx, inStream) 

    var buf bytes.Buffer 
    log.SetOutput(&buf) 

    inStream<-maxTemp+1 

    logMsg := buf.String() 
    assert.True(t, strings.Contains(logMsg, "Temperature too high!"), 
     "Expected log message not found") 
} 

Da diese Filter das Ende meiner Pipeline ist, habe ich nicht einen aus Kanal habe ich aus lesen kann, um zu bestimmen, ob diese goroutine/Filter bereits etwas getan hat.

Das Einzige, was ich bisher online gefunden habe, war, nach dem Schreiben in den inStream in meinem Test nur ein paar Sekunden zu warten und dann das Protokoll zu überprüfen. Jedoch scheint dies eine wirklich schlechte Wahl zu sein, da es einfach eine Rennbedingung einführt und den Test verlangsamt.

Was ist der beste Weg, um so etwas zu testen oder gibt es einfach keine gute Möglichkeit, es mit diesem Design meines Filters zu testen und ich brauche immer einen outStream?

Antwort

1

Nicht immer eine Worker Goroutine hat ein Ergebnis zu liefern. Aber wenn Sie genau wissen wollen, wann es fertig ist, müssen Sie es mit Ihrer Haupt-Routine mit einem der Parallelen-Primitiven synchronisieren. Es könnte ein Signalisierungskanal oder eine Wartegruppe sein.

Hier ist ein Beispiel:

package main 

import (
    "bytes" 
    "context" 
    "fmt" 
    "log" 
    "strings" 
) 

const (
    maxTemp = 90 
    errMsg = "Temperature too high!" 
) 

func watchTemperature(ctx context.Context, inStream <-chan int, finished chan<- bool) { 
    go func() { 
     defer func() { 
      finished <- true 
     }() 
     for { 
      select { 
      case <-ctx.Done(): 
       return 
      case temp := <-inStream: 
       if temp > maxTemp { 
        log.Print(errMsg) 
       } 
      } 
     } 
    }() 
} 

func main() { 
    // quit signals to stop the work 
    ctx, quit := context.WithCancel(context.Background()) 
    var buf bytes.Buffer 
    // Make sure, this is called before launching the goroutine! 
    log.SetOutput(&buf) 
    inStream := make(chan int) 
    finished := make(chan bool) 
    // pass the callback channel to the goroutine 
    watchTemperature(ctx, inStream, finished) 

    // asynchronously to prevent a deadlock 
    go func() { 
     inStream <- maxTemp + 1 
     quit() 
    }() 
    // Block until the goroutine returns. 
    <-finished 

    if !strings.Contains(buf.String(), errMsg) { 
     panic("Expected log message not found") 
    } 

    fmt.Println("Pass!") 
} 
+0

Dies hilft viel, da es mich dazu bringt, meinen Ansatz zu überdenken. In Bezug auf das Pipeline-Muster stoppt/beendet die Goroutine jedoch nicht, nachdem ein Ergebnis gemeldet wurde. Es wäre also schwierig, auf diese Weise zu synchronisieren, wenn ich nicht melde, dass ein Schritt abgeschlossen wurde, der sich nicht wirklich von einem Ergebniskanal unterscheidet, oder? – FChris

+1

Sie können Kanäle verwenden, um Fortschritt/Ergebnis/Vervollständigung zu senden. Oder Sie können 'sync.WaitGroup' verwenden, um benachrichtigt zu werden, wenn alle Worker des gleichen Typs/Schritts fertig sind. –

+0

Ist es üblich, einen optionalen Kanal als Parameter für Funktionen zu haben, die goroutines initiieren, auf die Fortschrittsaktualisierungen geschrieben werden, wenn sie vorhanden sind und die nicht verwendet werden, wenn sie nicht bereitgestellt werden? – FChris

1

Ich denke, dass Sie Ihre Struktur ein wenig ändern sollten. Erstens scheint es mir gar nicht gut zu sein zu testen, ob eine Funktion etwas druckt oder nicht. Protokolle sollten nicht Teil Ihrer Geschäftslogik sein. Sie sind nur Add-Ons, um das Debuggen und Tracen einfacher zu machen. Zweitens starten Sie eine Goroutine, die keine Ausgabe liefert (außer Logs), so dass Sie nicht kontrollieren können, wann sie ihren Job beendet hat.

ALTERNATIVE:

einen Kanal Deklarieren Ausgänge von Ihrer Funktion zu erhalten und es vorzugsweise auf Ihre Funktion übergeben. Ich benutzte einen String-Kanal, um so einfach wie möglich zu sein.

var outStream = make(chan string) 
watchTemperature(ctx, inStream, outStream) 

Statt normalen Protokoll Einrichtungen, melden Sie sich auf diesen Kanal und für jeden Eingang Token Sie eine Ausgabe-Token erzeugen soll:

if temp > maxTemp { 
    outStream <- "Temperature too high!" 
} else { 
    outStream <- "Normal" 
} 

Und in Ihrem Test nach jedem Sie eine Ausgabe warten senden:

inStream <- maxTemp + 1 
reply <- outStream 
if reply != "Temperature too high!" { 
    // test failed 
} 
+0

Ich glaube, ich dies prüfen, aber eine ganze Ergebnistyp deklarieren, die auch Informationen enthält, ob das Ergebnis ein Fehler etc. ich es aber bevorzugen, um den Filter zu haben Geben Sie outStream zurück, anstatt es zu übergeben. Auf diese Weise können mehrere andere Routinen die Ergebnisse verwenden. – FChris