Get Go Unit Testing (2) — Mock Framework (gomock)

Go

Прочитав предыдущую статью, я считаю, что у вас есть предварительное представление о том, как проводить модульное тестирование, и вы можете приступать к трансформации и тестированию существующих проектов. Научившись ходить, мы пытаемся бегать.В этой статье в основном представлена ​​среда тестирования gomock, чтобы сделать наше модульное тестирование более эффективным.

Табличные тесты

При модульном тестировании метода мы обычно пишем более одного теста, нам нужно проверить, может ли метод нормально работать при различных входных параметрах, особенно для граничных значений. Обычно именно здесь и приходит на помощь табличное тестирование.Когда вы обнаружите, что используете копирование и вставку при написании своих тестовых методов, пришло время подумать об использовании табличного тестирования для структурирования ваших тестовых методов.. Давайте все же возьмем пример:

func TestTime(t *testing.T) {
    testCases := []struct {  // 设计我们的测试用例
        gmt  string
        loc  string
        want string
    }{
        {"12:31", "Europe/Zuri", "13:31"},     // incorrect location name
        {"12:31", "America/New_York", "7:31"}, // should be 07:31
        {"08:08", "Australia/Sydney", "18:08"},
    }
    for _, tc := range testCases {  // 循环执行测试用例
        loc, err := time.LoadLocation(tc.loc)
        if err != nil {
            t.Fatalf("could not load location %q", tc.loc)
        }
        gmt, _ := time.Parse("15:04", tc.gmt)
        if got := gmt.In(loc).Format("15:04"); got != tc.want {
            t.Errorf("In(%s, %s) = %s; want %s", tc.gmt, tc.loc, got, tc.want)
        }
    }
}

Подход к тестированию на основе таблиц делает наши методы тестирования более четкими и лаконичными, сокращает количество операций копирования и вставки и значительно улучшает читабельность тестового кода.

Помните выше, что модульные тесты также необходимо поддерживать? Модульные тесты также являются частью кода, и к ним следует относиться серьезно. Не забудьте организовать свои тестовые случаи в виде тестов на основе таблиц и не забудьте написать соответствующие комментарии, такие как официальный код. Больше ссылок:GitHub.com/gowaves/go/me… blog.golang.org/subtests

Используйте фреймворк для тестирования — gomock

What is gomock?

gomock — это среда тестирования Golang с открытым исходным кодом от Google. Или, цитируя официальную строку: «GoMock — этоmocking frameworkдля языка программирования Go».

github.com/golang/mock

Почему гомок?

В конце прошлой статьи был представлен тестовый метод объединения mock и stub.Чувствуется, что комбинация mock и stub является мощной - определение последовательности вызова, определение номера вызова, динамический контроль возвращаемого значения функций, и т. д., но в то же время он имеет стоимость обслуживания и сложность, которые нельзя игнорировать, и было бы катастрофой поддерживать такой набор тестового кода вручную. Мы рассчитываем использовать набор фреймворков или инструментов, которые помогут нам поддерживать сложный фиктивный код, предоставляя при этом мощные функции тестирования.

How does it work?

гомок черезmockgenкоманда для создания фиктивного объекта, содержащего.goфайл, сгенерированный фиктивный объект имеет мощную функцию mock+stub и освобождает нас от написания фиктивных объектов:

mockgen -destination foo_mock.go -source foo.go -package foo //mock foo.go里面所有的接口,将mock结果保存到foo_mock.go

Gomock позволяет нам использовать мощные функции комбинации моков и заглушек без ручного обслуживания этих фиктивных объектов Разве это не красиво?

взять каштан

Здесь мы делаем простую демонстрацию основных функций gomock: Предположим, что наш интерфейс определен вuser.go:

// user.go
package user

// User 表示一个用户
type User struct {
   Name string
}
// UserRepository 用户仓库
type UserRepository interface {
   // 根据用户id查询得到一个用户或是错误信息
   FindOne(id int) (*User,error)
}

Создайте фиктивный файл user_mock.go в том же каталоге через mockgen

mockgen -source user.go -destination user_mock.go -package user

Затем создайте новый в этом каталогеuser_test.goЧтобы написать нашу тестовую функцию, после выполнения вышеуказанных шагов наша структура каталогов выглядит следующим образом:

└── user
    ├── user.go
    ├── user_mock.go
    └── user_test.go 

Установить возвращаемое значение функции

// 静态设置返回值
func TestReturn(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	repo := NewMockUserRepository(ctrl)
	// 期望FindOne(1)返回张三用户
	repo.EXPECT().FindOne(1).Return(&User{Name: "张三"}, nil)
	// 期望FindOne(2)返回李四用户
	repo.EXPECT().FindOne(2).Return(&User{Name: "李四"}, nil)
	// 期望给FindOne(3)返回找不到用户的错误
	repo.EXPECT().FindOne(3).Return(nil, errors.New("user not found"))
	// 验证一下结果
	log.Println(repo.FindOne(1)) // 这是张三
	log.Println(repo.FindOne(2)) // 这是李四
	log.Println(repo.FindOne(3)) // user not found
	log.Println(repo.FindOne(4)) //没有设置4的返回值,却执行了调用,测试不通过
}
// 动态设置返回值
func TestReturnDynamic(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	repo := NewMockUserRepository(ctrl)
	// 常用方法之一:DoAndReturn(),动态设置返回值 
	repo.EXPECT().FindOne(gomock.Any()).DoAndReturn(func(i int) (*User,error) {
		if i == 0 {
			return nil, errors.New("user not found")
		}
		if i < 100 {
			return &User{
				Name:"小于100",
			}, nil
		} else {
			return &User{
				Name:"大于等于100",
			}, nil
		}
	})
	log.Println(repo.FindOne(120))
	//log.Println(repo.FindOne(66))
	//log.Println(repo.FindOne(0))
}

Обнаружение количества вызовов

func TestTimes(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	repo := NewMockUserRepository(ctrl)
	// 默认期望调用一次
	repo.EXPECT().FindOne(1).Return(&User{Name: "张三"}, nil)
	// 期望调用2次
	repo.EXPECT().FindOne(2).Return(&User{Name: "李四"}, nil).Times(2)
	// 调用多少次可以,包括0次
	repo.EXPECT().FindOne(3).Return(nil, errors.New("user not found")).AnyTimes()

	// 验证一下结果
	log.Println(repo.FindOne(1)) // 这是张三
	log.Println(repo.FindOne(2)) // 这是李四
	log.Println(repo.FindOne(2)) // FindOne(2) 需调用两次,注释本行代码将导致测试不通过
	log.Println(repo.FindOne(3)) // user not found, 不限调用次数,注释掉本行也能通过测试
}

Обнаружение заказа звонка

func TestOrder(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	repo := NewMockUserRepository(ctrl)
	o1 := repo.EXPECT().FindOne(1).Return(&User{Name: "张三"}, nil)
	o2 := repo.EXPECT().FindOne(2).Return(&User{Name: "李四"}, nil)
	o3 := repo.EXPECT().FindOne(3).Return(nil, errors.New("user not found"))
	gomock.InOrder(o1, o2, o3) //设置调用顺序
	// 按顺序调用,验证一下结果
	log.Println(repo.FindOne(1)) // 这是张三
	log.Println(repo.FindOne(2)) // 这是李四
	log.Println(repo.FindOne(3)) // user not found
	
	// 如果我们调整了调用顺序,将导致测试不通过:
	// log.Println(repo.FindOne(2)) // 这是李四
	// log.Println(repo.FindOne(1)) // 这是张三
	// log.Println(repo.FindOne(3)) // user not found
}

Приведенный выше пример показывает толькоgomockВерхушка айсберга функций не будет подробно обсуждаться в этой статье, для более подробного использования обратитесь к документации.

Еще официальные примеры:GitHub.com/gowaves/mock…

Если вы выполнили небольшие упражнения из предыдущей главы, попробуйте использовать гомок, чтобы преобразовать его!

в заключении

В этой статье рассказывается о тестировании на основе таблиц и среде тестирования gomock. Использование табличного метода тестирования не только делает тестовый код более кратким и легко читаемым, но также улучшает нашу способность писать тестовые примеры и фактически повышает качество модульного тестирования. Gomock очень богат функциями, если вы хотите освоить различные операции, вам все равно придется внимательно читать официальные примеры, но обычно 20% штатных функций достаточно, чтобы покрыть 80% тестовых сценариев.
Table Driven Unit Testing и gomock поднимают эффективность и качество модульного тестирования на новый уровень. В следующей статье мы познакомимtestifyБиблиотека утверждений, продолжайте оптимизировать наши модульные тесты.