【问题标题】:Testing Emitter func with channel return value in go在 go 中使用通道返回值测试 Emitter func
【发布时间】:2018-09-02 12:42:15
【问题描述】:

我很难对发射器函数进行测试,该发射器函数将结果通过数据管道的通道传递。此功能将定期触发,并从数据库中提取记录。我为这个问题编译了一个剥离完成的版本,真正的代码会更复杂,但会遵循相同的模式。为了测试,我模拟了对数据库的访问,因为我想测试 Emitter 函数的行为。

我猜代码不仅仅是文字:

这是我要测试的方法:

//EmittRecord pull record from database
func EmittRecord(svc Service, count int) <-chan *Result {
    out := make(chan *Result)

    go func() {
        defer close(out)
        for i := 0; i < count; i++ {
            r, err := svc.Next()
            if err != nil {
                out <- &Result{Error: err}
                continue
            }
            out <- &Result{Payload: &Payload{
                Field1: r.Field1,
                Field2: r.Field2,
            }, Error: nil}
        }

    }()

    return out
}

我有几种带接口的类型:

//Record is a Record from db
type Record struct {
    Field1 string
    Field2 string
}

//Payload is a record for the data pipeline
type Payload struct {
    Field1 string
    Field2 string
}

//Result is a type for the data pipeline
type Result struct {
    Payload *Payload
    Error   error
}

//Service is an abstraction to access the database
type Service interface {
    Next() (*Record, error)
}

这是我用于测试的服务模拟:

//MockService is a struct to support testing for mocking the database
type MockService struct {
    NextMock func() (*Record, error)
}

//Next is an Implementation of the Service interface for the mock
func (m *MockService) Next() (*Record, error) {
    if m.NextMock != nil {
        return m.NextMock()
    }
    panic("Please set NextMock!")
}

最后这是我的测试方法,它不起作用。它没有达到完成的情况,也没有达到1*time.Second 超时情况......测试只是超时。我想我在这里遗漏了一些东西。

 func TestEmitter(t *testing.T) {

    tt := []struct {
        name           string
        svc            runner.Service
        expectedResult runner.Result
    }{

        {name: "Database returns error",
            svc: &runner.MockService{
                NextMock: func() (*runner.Record, error) {
                    return nil, fmt.Errorf("YIKES")
                },
            },
            expectedResult: runner.Result{Payload: nil, Error: fmt.Errorf("RRRR")},
        },
        {name: "Database returns record",
            svc: &runner.MockService{
                NextMock: func() (*runner.Record, error) {
                    return &runner.Record{
                        Field1: "hello",
                        Field2: "world",
                    }, nil
                },
            },
        },
    }

    for _, tc := range tt {

        t.Run(tc.name, func(t *testing.T) {
            done := make(chan bool)
            defer close(done)

            var output <-chan *runner.Result
            go func() {
                output = runner.EmittRecord(tc.svc, 1)
                done <- true
            }()
            found := <-output
            <-done
            select {
            case <-done:
            case <-time.After(1 * time.Second):
                panic("timeout")
            }

            if found.Error.Error() != tc.expectedResult.Error.Error() {
                t.Errorf("FAIL: %s, expected: %s; but got %s", tc.name, tc.expectedResult.Error.Error(), found.Error.Error())

            } else if reflect.DeepEqual(found.Payload, tc.expectedResult.Payload) {
                t.Errorf("FAIL: %s, expected: %+v; got %+v", tc.name, tc.expectedResult.Payload, found.Payload)
            }

        })
    }

}

如果有人可以给我一个建议,我在这里缺少什么,也许可以提供一些输入来验证 EmittRecord 函数的计数,那就太好了,它现在只设置为 1 提前致谢

//已编辑:@Lansana 评论中的预期结果

【问题讨论】:

    标签: testing go channel


    【解决方案1】:

    您确定将测试中的预期结果设置为正确的值吗?

    在测试的第一个切片中,您期望 fmt.Errorf("RRRR"),但模拟返回 fmt.Errorf("YIKES")

    然后在实际的测试条件中,你这样做:

    if found.Error.Error() != "Hello" {
        t.Errorf("FAIL: %s, expected: %s; but got %s", tc.name, tc.expectedResult.Error.Error(), found.Error.Error())
    }
    

    您正在检查"Hello"。您不应该检查消息"YIKES" 是否有错误吗?

    我认为您的逻辑很好,但是您的测试没有正确编写。检查我的Go Playground example 并运行代码。当你运行它时,你会看到没有输出或恐慌。这是因为代码通过了我在main中的测试条件。

    您通过更多渠道增加了测试的复杂性,如果这些额外渠道无效,那么您可能会遇到一些误报,让您认为您的业务逻辑不好。在这种情况下,它实际上似乎可以正常工作。

    这是我的游乐场示例代码的亮点。 (测试你的逻辑的部分):

    func main() {
        svc1 := &MockService{
            NextMock: func() (*Record, error) {
                return nil, errors.New("foo")
            },
        }
        for item := range EmittRecord(svc1, 5) {
            if item.Payload != nil {
                panic("item.Payload should be nil")
            }
            if item.Error == nil {
                panic("item.Error should be an error")
            }
        }
    
        svc2 := &MockService{
            NextMock: func() (*Record, error) {
                return &Record{Field1: "Hello ", Field2: "World"}, nil
            },
        }
        for item := range EmittRecord(svc2, 5) {
            if item.Payload == nil {
                panic("item.Payload should have a value")
            }
            if item.Payload.Field1 + item.Payload.Field2 != "Hello World" {
                panic("item.Payload.Field1 and item.Payload.Field2 are invalid!")
            }
            if item.Error != nil {
                panic("item.Error should be nil")
            }
        }
    }
    

    上述代码的输出是什么。没有恐慌。这样,就成功了。

    尝试将您的测试简化为工作状态,然后从那里增加更多复杂性。 :)

    【讨论】:

    • 感谢您的回复。我更改了预期值,看看它是否达到了这一点。很抱歉,我应该把它转回去,或者至少提一下。将编辑它。我的猜测是您的操场示例中的代码只是失败了,因为您没有使用该频道。这基本上就是我要问的:如何等待频道并期望 EmitRecord 的值返回 &lt;- chan *Result 还是我出错了?
    • 我的代码中的for item := range EmittRecord() 就是这样做的。每次发出记录时,它都会调用块内的逻辑。如果这需要 5 分钟,那么将需要 5 分钟。您可以通过在 for 循环中添加 fmt.Println() 来测试这一点,您将看到它会打印一些内容,无论您的 EmittRecord() 频道发出多少次。
    • 谢谢先生,工作就像一个迷人的def。学到了一些东西,并在我的代码中发现了另一个错误!
    • 乐于助人! :)
    猜你喜欢
    • 1970-01-01
    • 2011-05-15
    • 2021-02-15
    • 2014-10-11
    • 2014-01-10
    • 2022-12-17
    • 2022-01-22
    • 2022-01-24
    • 1970-01-01
    相关资源
    最近更新 更多