Golang如何测试返回通道类型的函数?

huangapple go评论71阅读模式
英文:

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 &lt;-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 = &lt;-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 := &amp;vService {
                      factory: &amp;servicefactory.FakeServiceFactory{
                                 MockPService: mockPService
                               }
                    }

	mockPService.EXPECT().Start(&quot;reason&quot;).Times(1).Return()
	mockPService.EXPECT().Done().Times(1).DoAndReturn(func() chan error {
		return nil
	})

 	err := vService.StartP(context.Background(), &quot;reason&quot;)
	assert.Equal(t, nil, err)
}

I use gomock to mock the PServiceInterface

// interface

type PServiceInterface interface {
	Start(reason string)
	Done() &lt;-chan error
}

gomock gen this function

func (m *MockProvisionServiceInterface) Done() &lt;-chan error {
        m.ctrl.T.Helper()
        ret := m.ctrl.Call(m, &quot;Done&quot;)
        ret0, _ := ret[0].(&lt;-chan error)
        fmt.Println(ret0,&quot;.....mock Done()&quot;)
        return ret0
}

// I also try this

	mockProvisionService.EXPECT().Done().Times(1).DoAndReturn( func() chan error {
		fmt.Println(&quot;DoAndReturn...err nil&quot;)

		ch := make(chan error, 1)
		ch &lt;- 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, &quot;start&quot;)
}

func (sm *ServiceMock) Done() &lt;-chan error {
	sm.records = append(sm.records, &quot;done&quot;)
	ch := make(chan error)
	close(ch)
	return ch
}

And finally the test:

func TestWithMock(t *testing.T) {
	mock := ServiceMock{}
	sut := &amp;vService{factory: &amp;testFactory{&amp;mock}}

	err := sut.StartP(context.Background(), &quot;banana&quot;)
	if err != nil {
		t.Fatalf(&quot;StartP: have: %s; want: no error&quot;, err)
	}

	if have, want := len(mock.records), 2; have != want {
		t.Fatalf(&quot;number of mock calls: have: %v; want: %v&quot;, have, want)
	}

	if have, want := mock.records[0], &quot;start&quot;; have != want {
		t.Fatalf(&quot;mock call 1: have: %v; want: %v&quot;, have, want)
	}

	if have, want := mock.records[1], &quot;done&quot;; have != want {
		t.Fatalf(&quot;mock call 2: have: %v; want: %v&quot;, have, want)
	}
}

The three assertions on the mock call sequence can be collapsed into one, comparing directly the slice []string{&quot;start&quot;, &quot;done&quot;}, 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() &lt;-chan error{
            fmt.Println(&quot;DoAndReturn...err nil&quot;)
            ch := make(chan error, 1)
            ch &lt;- nil
            return ch
        })

huangapple
  • 本文由 发表于 2023年2月8日 13:55:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/75381842.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定