Search
Write a publication
Pull to refresh
1
0
Send message

Пример интересный, но тут есть некоторая фундаментальная неточность. Кафка -- не очередь. Кафка -- это журнал. Например у нас есть КПП и там отмечаются в журнале входящие и уходящие. Нам надо посчитать, сколько раз покидал территорию Иванов. Мы можем показать этот журнал двум солдатам и попросить посчитать их. Они могут считать независимо друг от друга. И тогда кто быстрее справился, то и молодец. А могут поделить работу: чётные строки читает один, нечётные -- другой (и тогда они будут группой консьюмеров). А потом просуммировать результат.

Статью лучше было назвать rabbitmq и картошка. И тогда можно расширить примеры. Описать такой процесс как пересменка солдат, когда один солдат меняет другого (деплой воркеров). И тогда недочищенная картошка возвращается в таз (очередь). Или если в тазу попадается батат и солдаты бросают его в отдельный таз, который уходит другим солдатам. Ну и много чего ещё.

Про различия kafka и rabbitmq можно почитать, например, в этой статье на хабре: https://habr.com/ru/companies/itsumma/articles/416629/.

Ну всегда можно сделать несколько моков, какие-то для ошибок, какие-то для успехов. Также мок можно сделать универсальнее:
type testRedis struct{
    p1 string
    p2 error
}

func (t *testRedis) Get(ctx context.Context, key string) (string, error) {
    return t.p1, t.p2
}
func (t *testRedis) Set(ctx context.Context, key string, v interface{}) error {
    return t.p2
}

И дальше в тестах использовать
&testRedis{"", fmt.Errorf("some error")}


Довольно таки универсальные моки создаются утилитами генерации моков.
Например если взять мок из второго пункта статьи, то там моку можно сказать, чтобы он сначала возвращал значения, а потом ошибку:
func TestMock(t *testing.T) {
	ctx := context.Background()
	storage := new(MockStorage)

	// Говорим что на первый вызов Get ожидаем ключ k1 и возвращаем v1
	storage.On("Get", mock.Anything, "k1").Return("v1", nil)

	// Говорим что на второй вызов Get ожидаем ключ k2 и возвращаем v2
	storage.On("Get", mock.Anything, "k2").Return("v2", nil)

	// Говорим что на второй вызов Get ожидаем ключ k3 и возвращаем ошибку
	storage.On("Get", mock.Anything, "k3").Return("", errors.New("error"))

	got, err := storage.Get(ctx, "k1")
	if got != "v1" || err != nil {
		t.Errorf("Get returns (%v, %v)", got, err)
	}
	got, err = storage.Get(ctx, "k2")
	if got != "v2" || err != nil {
		t.Errorf("Get returns (%v, %v)", got, err)
	}
	got, err = storage.Get(ctx, "k3")
	if err == nil {
		t.Errorf("Get returns (%v, %v)", got, err)
	}

	// Проверяем что было вызвано всё, что устанавливали в моке
	storage.AssertExpectations(t)
}
1. как быть если методов относительно много? в той же redis библиотеке их довольно много — выходит что каждый внешний метод нужно оборачивать в свой или идти в сторону кодогенерации?

2. как быть если внешний метод возвращает какой-то свой интерфейс? например pgx.Begin, возвращает транзакцию pgx.Tx (которая тоже интерфейс) — для транзакции тоже нужно писать свой интерфейс обертку и затем писать обертки для методов?


Мы в статье привели немного неудачные примеры, которые вводят в заблуждение.
Не надо оборачивать каждый метод redis. Если так приходится делать, то что-то пошло не так. Т.е. Вам не нужно создавать интерфейс «редис». Лучше создавать, например, интерфейс «хранилище книг» с минимальным набором методов для работы с книгами. С помощью интерфейсов вы отделяете «слой репозиториев» от бизнес логики вашего приложения. Можно рассмотреть другой популярный пример с просторов github.

Есть, например, приложение, которое работает со статьями и их авторами. Так как работа идёт с авторами, то нужно описать «модель» (или сущность, доменный класс, названия могут от статье к статье различаться) авторов: github.com/bxcodec/go-clean-arch/blob/d452858/domain/author.go#L6. Авторы будут где-то хранится, и поэтому описывается интерфейс, необходимый для работы с хранилищем авторов github.com/bxcodec/go-clean-arch/blob/d452858/domain/author.go#L14 (мы тут не поднимаем вопрос, где должен описываться интерфейс; кто-то может считать что описывать нужно там, где описаны модели, кто-то может считать что там, где интерфейс используется). Данный интерфейс реализуется, создавая слой «репозиториев» github.com/bxcodec/go-clean-arch/blob/d452858/author/repository/mysql/mysql_repository.go#L38.

Ещё раз подчеркну, тут не оборачиваются методы конкретной библиотеки работы с базой данных, тут реализуется интерфейс для работы с хранилищем авторов.

И дальше этот «репозиторий» участвует через интерфейс в реализации бизнес логики («сценариев использования») вашего приложения github.com/bxcodec/go-clean-arch/blob/master/article/usecase/article_ucase.go#L47.

И такое использование интерфейсов помогает в тестировании приложения: github.com/bxcodec/go-clean-arch/blob/d452858/article/usecase/article_ucase_test.go#L34

Да, при этом не тестируется код, непосредственно работающий с БД (или http сервисами), но тесты репозиториев можно вынести отдельно, усложнив для них CI (или не писать их на go, оставив для интеграционных тестов).

3. в обертках всегда возвращается «успех», а как быть если нужно также тестировать и случаи когда возвращаются ошибки? Ну то есть как сделать так чтобы метод-обертка мог возвращать не только успех, но и error.


Ну в примере в обёртке возвращается и ошибка:
Get(ctx context.Context, key string) (string, error)


И если нужно проверить как поведёт себя код, если при запросе некой сущности вернётся ошибка, то можно написать соответствующий mock:

type testRedis struct{}

func (t *testRedis) Get(ctx context.Context, key string) (string, error) {
    return "", fmt.Errorf("some error")
}
func (t *testRedis) Set(ctx context.Context, key string, v interface{}) error {
    return nil
}
Обратил внимание если вот так регистрировать хандлеры
github.com/gebv/go-bb-tests-metrics/blob/main/main.go#L43

На 43 строчке Вы регистрируете handler'ы, которые описаны на строках github.com/gebv/go-bb-tests-metrics/blob/f90db0e/main.go#L86-L109.

То такое ветвление он уже не считает
github.com/gebv/go-bb-tests-metrics/blob/main/main.go#L17-L26

На этих строчках у Вас напина функция, которая нигде не используется, кроме как в TestCase2: github.com/gebv/go-bb-tests-metrics/blob/f90db0e/main_test.go#L13.

Да. Если Вы запускаете только TestCase1 (который тестирует функцию main, которая никак не использует функцию handler), то функция handler остаётся непокрытой. Но это не имеет никакого отношения к регистрации хэндлеров. Хендлеры, зарегистрированные на строчке github.com/gebv/go-bb-tests-metrics/blob/f90db0e/main.go#L43 как раз отображаются как покрытые тестами:
image
В случае если хандлеры динамически регистрируются

В целом в примере они динамически регистрируются во время выполнения внутри функции main. Только вместо цикла две строчки. Но на всякий случай попробовал переписать на цикл и map: github.com/Grey-Fox/gomaintest.
Во-первых, общая рекомендация такая, что интерфейсы нужно объявлять там, где они используются, а не там, где для них создаётся имплементация,

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

Во-вторых, если вы пишете интерфейс только для тестирования

Основная цель интерфейсов остаётся сделать независимыми работу одних пакетов от других. Но интерфейсы также сильно помогают нам в тестировании, позволяют тестировать маленькие «кучочки» независимо.

Для экспортируемых интерфейсов должна быть первостепенной именно цель однообразия и они должны быть маленькими

Пример с redis и memcahed был приведён как раз из-за минимального набора методов и однообразия работы работы с ними как с key-value хранилищами.
Тоже интересно, что значит «забанили»?

Сам часто смотрю ivi на ubuntu в firefox и просто обожаю функционал «картинка в картинке».

Правда нужен drm плагин: support.mozilla.org/ru/kb/smotrite-drm.

Но требование к drm — это, к сожалению, требование правообладателей. И с этим просто приходится мириться.
Статья не ставила целью сделать антирекламу микросервисам, как и их рекламу. Мы думаем, что наша компания не единственная, которая столкнулась с трудностями при переходе к микросервисам. И подсветили те, с которыми столкнулись лично мы. Часть из проблем мы решили, часть приняли как риск. Но для себя мы решили что микросервисы лучше монолита. Но этот выбор каждый должен сделать сам, нет единственно правильного ответа что лучше.

Information

Rating
Does not participate
Works in
Registered
Activity