Пытаюсь протестировать функцию StartP,
Ожидайте, что Start() должен быть вызван 1 раз, Done() должен быть вызван 1 раз
но у меня проблема с блокировкой теста при выполнении этого шага <-ps.Done()
Я ожидаю, что <-ps.Done() вернет ноль
Как я могу протестировать функцию, которая возвращает тип chan?
// 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)
}
Я использую gomock, чтобы издеваться над PServiceInterface.
// interface
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
})
@mkopriva я также модифицирую другой метод, я пишу последнюю часть статьи, но все равно не работает. Надеюсь, вы запишите код, позвольте мне правильно понять вашу идею.
Под «возвратом закрытого канала» я имел в виду что-то вроде этого: ch := make(chan error)
; close(ch)
; return ch
. Если это не разблокирует err = <-ps.Done()
, то я не уверен, что будет, я недостаточно знаком с gomock, чтобы точно знать, как заставить его работать.
я также добавляю close(ch); затем верните ch, все равно не работает
Я нашел первопричину. Спасибо!
Ниже показан, как мне кажется, минимальный код для реализации целей тестирования.
Он не использует никаких фиктивных фреймворков, потому что, по моему опыту, они, как правило, запутывают цель теста, требуют, чтобы все в команде научились их использовать, и в них нет необходимости, по крайней мере, в 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"}
, если используется тестовая библиотека, такая как отличный assert
пакет https://github.com/gotestyourself/gotest.tools
Ваша концепция идеальна. Я нашел первопричину. Спасибо!
@ Y.Ryan Если ответ помог вам, рассмотрите возможность его принятия. Это помогает будущим пользователям и дает мне стимул продолжать отвечать на вопросы.
Я нашел ответ, основная причина в том, что 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
})
DoAndReturn
вместо возвратаnil
в качестве канала изDone()
вернуть правильно инициализированный, но закрытый канал; тоerr = <-ps.Done()
больше не должен блокироваться и вместо этого немедленно возвращаться.