2017-12-03 18 views
1

このblog postで説明されているパイプとフィルターのパターンを使用しています。パイプとフィルターのパターンを効果的にテストする方法

これを効果的にテストする方法が不思議です。私の考えは、各フィルタを個別にテストすることでした。たとえば、私は今の私のテストでこの

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

のように見えるフィルタを持っている私は、ログが印刷されているかどうかを確認したいです。 私のテストは次のように見えます。

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

このフィルタは私のパイプラインの終わりであるとして、私は、このゴルーチン/フィルタは、すでに何かを行っているかどうかを判断するから読み取ることができますアウトチャネルを持っていません。

これまで私がオンラインで見つけたのは、テストでinStreamに書き込んだ後に数秒待ってからログを確認することでした。しかし、これは競争条件を導入してテストを遅くするので、これは本当に貧しい選択のようです。

このようなことをテストする最善の方法は何ですか?またはこのフィルタのデザインでテストするための良い方法はありません。いつもoutStreamが必要ですか?

答えて

1

必ずしもワーカーgoroutineが配信するとは限りません。しかし、完了したタイミングを正確に知りたい場合は、同時実行プリミティブの1つを使用してメインのゴルーチンと同期させる必要があります。これは、シグナリングチャネルまたは待機グループとすることができる。ここで

は例です:

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

これは私のアプローチを再考するように多くの助けになります。しかし、パイプラインパターンに関しては、結果を報告した後、ゴルーチンは停止/終了しません。したがって、結果チャンネルの権利と実際に違うわけではないステップが完了したことを報告しない限り、その方法で同期するのは難しいでしょうか? – FChris

+1

チャンネルを使用して進捗状況/結果/完了を送信できます。あるいは、同じタイプ/ステップのすべてのワーカーが終了したときに、 'sync.WaitGroup'を使って通知を受け取ることができます。 –

+0

goroutineを開始する関数のパラメータとしてオプションのチャネルを使用するのは一般的ですが、存在する場合は進捗状況の更新が行われ、存在しない場合は使用されません。 – FChris

1

あなたの構造を少し変更する必要があると思います。まず、関数が何かをプリントするかどうかをテストすることは、まったく良いことではないようです。ログはビジネスロジックの一部であってはなりません。デバッグとトレースを容易にするアドオンです。次に、ログを除いて出力を提供しないgoroutineを開始しているため、ジョブの終了時を制御できませんでした。

代替ソリューション:

はあなたの関数からの出力を取得し、好ましくは、あなたの関数に渡すチャネルを宣言します。私は可能な限り単純な文字列チャンネルを使用しました。代わりに、通常のログ施設の

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

、このチャンネルにログインして、各入力のためのあなたは、出力トークンを作成する必要がありトークン:それぞれがあなたが出力を待って送信した後

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

そして、あなたのテストでは:

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

私は、フィルタを持っている、しかしそれを好む私はこのことを考慮すると思いますが、結果がエラーなどであるかどうかも情報が含まれています全体の結果の型を宣言しますoutStreamを渡す代わりにoutStreamを返します。このように、複数の他のルーチンはその結果を利用できます。 – FChris

関連する問題