如何有效地测试管道和过滤器模式

时间:2017-12-03 11:48:11

标签: go pipeline goroutine

我正在使用此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")
}

由于此过滤器是我的管道的末尾,我没有可以读取的输出通道来确定此goroutine /过滤器是否已经完成了某些操作。

到目前为止,我在网上找到的唯一一件事是,在我的测试中写入inStream之后等待几秒钟然后检查日志。然而,这似乎是一个非常糟糕的选择,因为它简单地引入了竞争条件并且减缓了测试。

测试这样的东西的最佳方法是什么,或者使用我的滤镜设计测试它是不是很好,我总是需要一个outStream?

2 个答案:

答案 0 :(得分: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
}

答案 1 :(得分:1)

工作人员goroutine并不总是有结果。但是,如果您想确切知道何时完成,则需要使用其中一个并发原语将其与主goroutine同步。它可以是信令通道或等待组。

以下是一个例子:

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