英文:
Golang how to test function that return channel type?
问题
我尝试测试StartP函数,期望Start()被调用1次,Done()被调用1次。但是当运行这一步骤时,测试会被阻塞在**<-ps.Done()**这一行。我期望<-ps.Done()返回nil。我该如何测试返回chan类型的函数?
// 生产代码
func (s *vService) StartP(ctx context.Context, reason string) error {
ps, err := s.factory.CreateVService(ctx)
if err != nil {
return err
}
ps.Start(reason)
err = <-ps.Done() // 代码在这里停下来等待?我该如何测试?
if err != nil {
return err
}
return nil
}
// 测试代码
func Test_StartP(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockPService := mockpservice.NewMockPInterface(mockCtrl)
vService := &vService {
factory: &servicefactory.FakeServiceFactory{
MockPService: mockPService
}
}
mockPService.EXPECT().Start("reason").Times(1).Return()
mockPService.EXPECT().Done().Times(1).DoAndReturn(func() chan error {
return nil
})
err := vService.StartP(context.Background(), "reason")
assert.Equal(t, nil, err)
}
我使用gomock来模拟PServiceInterface接口
// 接口
type PServiceInterface interface {
Start(reason string)
Done() <-chan error
}
gomock生成了这个函数
func (m *MockProvisionServiceInterface) Done() <-chan error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Done")
ret0, _ := ret[0].(<-chan error)
fmt.Println(ret0,".....mock Done()")
return ret0
}
// 我还尝试了这个
mockProvisionService.EXPECT().Done().Times(1).DoAndReturn( func() chan error {
fmt.Println("DoAndReturn...err nil")
ch := make(chan error, 1)
ch <- nil
return ch
})
英文:
I try to test function StartP,
Expect that Start() should be called 1 times, Done() should be called 1 times
but I have trouble that test will block when run this step <-ps.Done()
I expect <-ps.Done() return nil
How can I test function that return chan type?
// production code
func (s *vService) StartP(ctx context.Context, reason string) error {
ps, err := s.factory.CreateVService(ctx)
if err != nil {
return err
}
ps.Start(reason)
err = <-ps.Done() // code stop here to wait ? how can i test ?
if err != nil {
return err
}
return nil
}
// test code
func Test_StartP(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockPService := mockpservice.NewMockPInterface(mockCtrl)
vService := &vService {
factory: &servicefactory.FakeServiceFactory{
MockPService: mockPService
}
}
mockPService.EXPECT().Start("reason").Times(1).Return()
mockPService.EXPECT().Done().Times(1).DoAndReturn(func() chan error {
return nil
})
err := vService.StartP(context.Background(), "reason")
assert.Equal(t, nil, err)
}
I use gomock to mock the PServiceInterface
// interface
type PServiceInterface interface {
Start(reason string)
Done() <-chan error
}
gomock gen this function
func (m *MockProvisionServiceInterface) Done() <-chan error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Done")
ret0, _ := ret[0].(<-chan error)
fmt.Println(ret0,".....mock Done()")
return ret0
}
// I also try this
mockProvisionService.EXPECT().Done().Times(1).DoAndReturn( func() chan error {
fmt.Println("DoAndReturn...err nil")
ch := make(chan error, 1)
ch <- nil
return ch
})
答案1
得分: 0
以下是我翻译好的内容:
以下显示了我认为实现您的测试目标所需的最小代码。
它不使用任何模拟框架,因为根据我的经验,它们往往会混淆测试意图,要求团队中的每个人都学会如何使用它们,并且在Go中不需要使用它们。人们还可以思考一下测试实际上在测试什么...
首先,让我们添加一些缺失的生产代码:
type factoryInterface interface {
CreateVService(ctx context.Context) (PServiceInterface, error)
}
type vService struct {
factory factoryInterface
}
现在是测试代码,分为三个部分:工厂、模拟和测试。
测试工厂:
type testFactory struct {
mock PServiceInterface
}
func (f *testFactory) CreateVService(ctx context.Context) (PServiceInterface, error) {
return f.mock, nil
}
模拟:
type ServiceMock struct {
records []string
}
func (sm *ServiceMock) Start(reason string) {
sm.records = append(sm.records, "start")
}
func (sm *ServiceMock) Done() <-chan error {
sm.records = append(sm.records, "done")
ch := make(chan error)
close(ch)
return ch
}
最后是测试:
func TestWithMock(t *testing.T) {
mock := ServiceMock{}
sut := &vService{factory: &testFactory{&mock}}
err := sut.StartP(context.Background(), "banana")
if err != nil {
t.Fatalf("StartP: have: %s; want: no error", err)
}
if have, want := len(mock.records), 2; have != want {
t.Fatalf("number of mock calls: have: %v; want: %v", have, want)
}
if have, want := mock.records[0], "start"; have != want {
t.Fatalf("mock call 1: have: %v; want: %v", have, want)
}
if have, want := mock.records[1], "done"; have != want {
t.Fatalf("mock call 2: have: %v; want: %v", have, want)
}
}
对于模拟调用顺序的三个断言可以合并为一个,直接比较切片[]string{"start", "done"}
,如果使用像https://github.com/gotestyourself/gotest.tools这样的测试库,可以使用assert
包。
英文:
The following shows, I think, the minimum code to implement your test goals.
It does not use any mocking framework because in my experience they tend to obfuscate the test intent, require everybody in the team to learn how to use them and are not needed, at least in Go. One could also wonder what the test is actually testing...
First, let's add some missing production code:
type factoryInterface interface {
CreateVService(ctx context.Context) (PServiceInterface, error)
}
type vService struct {
factory factoryInterface
}
And now the test code, in three parts: the factory, the mock, and the test.
The test factory:
type testFactory struct {
mock PServiceInterface
}
func (f *testFactory) CreateVService(ctx context.Context) (PServiceInterface, error) {
return f.mock, nil
}
The mock:
type ServiceMock struct {
records []string
}
func (sm *ServiceMock) Start(reason string) {
sm.records = append(sm.records, "start")
}
func (sm *ServiceMock) Done() <-chan error {
sm.records = append(sm.records, "done")
ch := make(chan error)
close(ch)
return ch
}
And finally the test:
func TestWithMock(t *testing.T) {
mock := ServiceMock{}
sut := &vService{factory: &testFactory{&mock}}
err := sut.StartP(context.Background(), "banana")
if err != nil {
t.Fatalf("StartP: have: %s; want: no error", err)
}
if have, want := len(mock.records), 2; have != want {
t.Fatalf("number of mock calls: have: %v; want: %v", have, want)
}
if have, want := mock.records[0], "start"; have != want {
t.Fatalf("mock call 1: have: %v; want: %v", have, want)
}
if have, want := mock.records[1], "done"; have != want {
t.Fatalf("mock call 2: have: %v; want: %v", have, want)
}
}
The three assertions on the mock call sequence can be collapsed into one, comparing directly the slice []string{"start", "done"}
, if one is using a test library such as the excellent assert
package of https://github.com/gotestyourself/gotest.tools
答案2
得分: 0
我找到了答案,根本原因是DoAndReturn的部分有问题。
函数类型应该是**<-chan error**,而不是chan error。
mockProvisionService.EXPECT().Done().Times(1).DoAndReturn(func() <-chan error {
fmt.Println("DoAndReturn...err nil")
ch := make(chan error, 1)
ch <- nil
return ch
})
英文:
I found the answer, root cause is DoAndReturn something wrong.
Func type should be <-chan error, not chan error
mockProvisionService.EXPECT().Done().Times(1).DoAndReturn( func() <-chan error{
fmt.Println("DoAndReturn...err nil")
ch := make(chan error, 1)
ch <- nil
return ch
})
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论