dongshungou7699 2018-03-23 18:39
浏览 69
已采纳

使用go中的通道返回值测试Emitter函数

I am having a hard time getting my test for my emitter function which passes results through a channel for a data pipeline. This function will be triggered periodically and will pull records from the database. I compiled an stripped done version for this question the real code would more complex but would follow the same pattern. For testing I mocked the access to the database because I want to test the behavoir of the Emitter function.

I guess code is more than words:

This is the method I want to test:

//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
}

I have a couple of types with an interface:

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

This is my service Mock for testing:

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

And finally this is my test method which does not work. It does not hit the done case and das not hit the 1*time.Second timeout case either ... the test just times out. I guess I am missing something here.

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

        })
    }

}

It would be great, if someone could give me an advice what I missing here and maybe some input how to verify the count of the EmittRecord function right now it is only set to 1 Thanks in advance

//Edited: the expectedResult as per Comment by @Lansana

  • 写回答

1条回答 默认 最新

  • 普通网友 2018-03-23 19:08
    关注

    Are you sure you have your expected results in the tests set to the proper value?

    In the first slice in the test, you expect a fmt.Errorf("RRRR"), yet the mock returns a fmt.Errorf("YIKES").

    And then later in the actual test conditionals, you do this:

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

    You are checking "Hello". Shouldn't you be checking if it's an error with the message "YIKES"?

    I think your logic is good, but your test is just not properly written. Check my Go Playground example here and run the code. You will see there is no output or panics when you run it. This is because the code passes my test conditions in main.

    You are adding more complexity to your test by more channels, and if those extra channels are invalid then you may have some false positives that make you think your business logic is bad. In this case, it actually seems to be working as it should.

    Here is the highlight of the code from my playground example . (the part that tests your logic):

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

    The output from the above code is nothing. No panics. Thus, it succeeded.

    Try simplifying your test to a working state, and then add more complexity from there. :)

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 如何在scanpy上做差异基因和通路富集?
  • ¥20 关于#硬件工程#的问题,请各位专家解答!
  • ¥15 关于#matlab#的问题:期望的系统闭环传递函数为G(s)=wn^2/s^2+2¢wn+wn^2阻尼系数¢=0.707,使系统具有较小的超调量
  • ¥15 FLUENT如何实现在堆积颗粒的上表面加载高斯热源
  • ¥30 截图中的mathematics程序转换成matlab
  • ¥15 动力学代码报错,维度不匹配
  • ¥15 Power query添加列问题
  • ¥50 Kubernetes&Fission&Eleasticsearch
  • ¥15 報錯:Person is not mapped,如何解決?
  • ¥15 c++头文件不能识别CDialog