> For the complete documentation index, see [llms.txt](https://eda-1.gitbook.io/lgwt/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://eda-1.gitbook.io/lgwt/osnovy-go/mocking.md).

# Мокирование

[**Вы можете найти весь код для этой главы здесь**](https://github.com/quii/learn-go-with-tests/tree/main/mocking)

Вас попросили написать программу, которая отсчитывает время от 3, выводя каждое число на новой строке (с 1-секундной паузой), а когда оно достигает нуля, выводит "Go!" и завершает работу.

```
3
2
1
Go!
```

Мы решим эту задачу, написав функцию `Countdown`, которую затем поместим в программу `main`, чтобы она выглядела примерно так:

```go
package main

func main() {
	Countdown()
}
```

Хотя это довольно тривиальная программа, чтобы полностью ее протестировать, нам, как всегда, потребуется *итеративный*, *тестоориентированный* подход.

Что я подразумеваю под итеративным? Мы следим за тем, чтобы делать самые маленькие шаги, которые мы можем, чтобы получить *полезное программное обеспечение*.

Мы не хотим тратить много времени на код, который теоретически заработает после некоторых доработок, потому что именно так разработчики часто заходят в тупик. **Умение разбивать требования на максимально мелкие части, чтобы получить&#x20;*****рабочее программное обеспечение*****, является важным навыком.**

Вот как мы можем разделить нашу работу и итерировать ее:

* Вывести 3
* Вывести 3, 2, 1 и Go!
* Ждать секунду между каждой строкой

## Сначала напишите тест

Наше программное обеспечение должно выводить данные в стандартный вывод (stdout), и мы видели, как можно использовать внедрение зависимостей (DI) для облегчения тестирования этого в разделе DI.

```go
func TestCountdown(t *testing.T) {
	buffer := &bytes.Buffer{}

	Countdown(buffer)

	got := buffer.String()
	want := "3"

	if got != want {
		t.Errorf("got %q want %q", got, want)
	}
}
```

Если что-то вроде `buffer` вам незнакомо, перечитайте [предыдущий раздел](/lgwt/osnovy-go/dependency-injection.md).

Мы знаем, что наша функция `Countdown` должна записывать данные куда-то, и `io.Writer` — это де-факто способ захвата этого в виде интерфейса в Go.

* В `main` мы будем отправлять данные в `os.Stdout`, чтобы наши пользователи видели обратный отсчет, выводимый в терминал.
* В тесте мы будем отправлять данные в `bytes.Buffer`, чтобы наши тесты могли захватывать генерируемые данные.

## Попробуйте запустить тест

`./countdown_test.go:11:2: undefined: Countdown`

## Напишите минимальный объем кода для запуска теста и проверьте вывод неудачного теста

Определите `Countdown`

```go
func Countdown() {}
```

Попробуйте снова

```
./countdown_test.go:11:11: too many arguments in call to Countdown
    have (*bytes.Buffer)
    want ()
```

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

```go
func Countdown(out *bytes.Buffer) {}
```

`countdown_test.go:17: got '' want '3'`

Идеально!

## Напишите достаточно кода, чтобы тест прошел

```go
func Countdown(out *bytes.Buffer) {
	fmt.Fprint(out, "3")
}
```

Мы используем `fmt.Fprint`, которая принимает `io.Writer` (например, `*bytes.Buffer`) и отправляет в него `string`. Тест должен пройти.

## Рефакторинг

Мы знаем, что хотя `*bytes.Buffer` работает, лучше использовать более общий интерфейс.

```go
func Countdown(out io.Writer) {
	fmt.Fprint(out, "3")
}
```

Перезапустите тесты, и они должны пройти.

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

```go
package main

import (
	"fmt"
	"io"
	"os"
)

func Countdown(out io.Writer) {
	fmt.Fprint(out, "3")
}

func main() {
	Countdown(os.Stdout)
}
```

Попробуйте запустить программу и восхититесь своей работой.

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

Далее мы можем заставить его выводить 2,1, а затем "Go!".

## Сначала напишите тест

Инвестируя в правильную работу всей системы, мы можем безопасно и легко итерировать наше решение. Нам больше не нужно будет останавливать и перезапускать программу, чтобы быть уверенными в ее работе, так как вся логика протестирована.

```go
func TestCountdown(t *testing.T) {
	buffer := &bytes.Buffer{}

	Countdown(buffer)

	got := buffer.String()
	want := `3
2
1
Go!`

	if got != want {
		t.Errorf("got %q want %q", got, want)
	}
}
```

Синтаксис с обратными кавычками — это еще один способ создания `string`, но он позволяет включать такие вещи, как новые строки, что идеально подходит для нашего теста.

## Попробуйте запустить тест

```
countdown_test.go:21: got '3' want '3
        2
        1
        Go!'
```

## Напишите достаточно кода, чтобы тест прошел

```go
func Countdown(out io.Writer) {
	for i := 3; i > 0; i-- {
		fmt.Fprintln(out, i)
	}
	fmt.Fprint(out, "Go!")
}
```

Используйте цикл `for`, отсчитывающий в обратном порядке с `i--`, и используйте `fmt.Fprintln` для вывода в `out` нашего числа, за которым следует символ новой строки. Наконец, используйте `fmt.Fprint` для отправки "Go!".

## Рефакторинг

Особо нечего рефакторить, кроме как превратить некоторые "магические" значения в именованные константы.

```go
const finalWord = "Go!"
const countdownStart = 3

func Countdown(out io.Writer) {
	for i := countdownStart; i > 0; i-- {
		fmt.Fprintln(out, i)
	}
	fmt.Fprint(out, finalWord)
}
```

Если вы запустите программу сейчас, вы должны получить желаемый вывод, но у нас нет драматического обратного отсчета с 1-секундными паузами.

Go позволяет добиться этого с помощью `time.Sleep`. Попробуйте добавить его в наш код.

```go
func Countdown(out io.Writer) {
	for i := countdownStart; i > 0; i-- {
		fmt.Fprintln(out, i)
		time.Sleep(1 * time.Second)
	}

	fmt.Fprint(out, finalWord)
}
```

Если вы запустите программу, она будет работать так, как мы хотим.

## Мокирование

Тесты по-прежнему проходят, и программное обеспечение работает, как задумано, но у нас есть некоторые проблемы:

* Наши тесты занимают 3 секунды.
  * Каждая дальновидная публикация о разработке программного обеспечения подчеркивает важность быстрых циклов обратной связи.
  * **Медленные тесты снижают продуктивность разработчика**.
  * Представьте, если требования станут более сложными, требуя больше тестов. Довольны ли мы добавлением 3 секунд к выполнению теста для каждого нового теста `Countdown`?
* Мы не протестировали важное свойство нашей функции.

У нас есть зависимость от `Sleep`, которую нам нужно извлечь, чтобы мы могли затем контролировать её в наших тестах.

Если мы можем *замокировать* `time.Sleep`, мы можем использовать *внедрение зависимостей*, чтобы использовать его вместо "настоящего" `time.Sleep`, а затем мы можем **шпионить за вызовами**, чтобы делать утверждения о них.

## Сначала напишите тест

Давайте определим нашу зависимость как интерфейс. Это позволит нам использовать *настоящий* Sleeper в `main` и *шпионский sleeper* в наших тестах. Используя интерфейс, наша функция `Countdown` остается невосприимчивой к этому и добавляет некоторую гибкость для вызывающей стороны.

```go
type Sleeper interface {
	Sleep()
}
```

Я принял решение о том, что наша функция `Countdown` не будет отвечать за продолжительность сна. Это немного упрощает наш код, по крайней мере, на данный момент, и означает, что пользователь нашей функции может настроить эту "сонливость" по своему усмотрению.

Теперь нам нужно создать его *мок* для использования в наших тестах.

```go
type SpySleeper struct {
	Calls int
}

func (s *SpySleeper) Sleep() {
	s.Calls++
}
```

*Шпионы* — это разновидность *мока*, которая может записывать, как используется зависимость. Они могут записывать переданные аргументы, сколько раз она была вызвана и т.д. В нашем случае мы отслеживаем, сколько раз вызывается `Sleep()`, чтобы мы могли проверить это в нашем тесте.

Обновите тесты, чтобы внедрить зависимость от нашего Spy и утверждать, что `sleep` был вызван 3 раза.

```go
func TestCountdown(t *testing.T) {
	buffer := &bytes.Buffer{}
	spySleeper := &SpySleeper{}

	Countdown(buffer, spySleeper)

	got := buffer.String()
	want := `3
2
1
Go!`

	if got != want {
		t.Errorf("got %q want %q", got, want)
	}

	if spySleeper.Calls != 3 {
		t.Errorf("not enough calls to sleeper, want 3 got %d", spySleeper.Calls)
	}
}
```

## Попробуйте запустить тест

```
too many arguments in call to Countdown
    have (*bytes.Buffer, *SpySleeper)
    want (io.Writer)
```

## Напишите минимальный объем кода для запуска теста и проверьте вывод неудачного теста

Нам нужно обновить `Countdown`, чтобы он принимал наш `Sleeper`

```go
func Countdown(out io.Writer, sleeper Sleeper) {
	for i := countdownStart; i > 0; i-- {
		fmt.Fprintln(out, i)
		time.Sleep(1 * time.Second)
	}

	fmt.Fprint(out, finalWord)
}
```

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

```
./main.go:26:11: not enough arguments in call to Countdown
    have (*os.File)
    want (io.Writer, Sleeper)
```

Давайте создадим *настоящий* sleeper, который реализует нужный нам интерфейс.

```go
type DefaultSleeper struct{}

func (d *DefaultSleeper) Sleep() {
	time.Sleep(1 * time.Second)
}
```

Затем мы можем использовать его в нашем реальном приложении следующим образом:

```go
func main() {
	sleeper := &DefaultSleeper{}
	Countdown(os.Stdout, sleeper)
}
```

## Напишите достаточно кода, чтобы тест прошел

Тест теперь компилируется, но не проходит, потому что мы по-прежнему вызываем `time.Sleep` вместо внедренной зависимости. Давайте это исправим.

```go
func Countdown(out io.Writer, sleeper Sleeper) {
	for i := countdownStart; i > 0; i-- {
		fmt.Fprintln(out, i)
		sleeper.Sleep()
	}

	fmt.Fprint(out, finalWord)
}
```

Тест должен пройти и больше не занимать 3 секунды.

### всё ещё есть проблемы

Есть еще одно важное свойство, которое мы не протестировали.

`Countdown` должен "спать" перед каждым следующим выводом, например:

* `Print N`
* `Sleep`
* `Print N-1`
* `Sleep`
* `Print Go!`
* и т.д.

Наше последнее изменение только утверждает, что он "спал" 3 раза, но эти "сны" могли произойти не по порядку.

При написании тестов, если вы не уверены, что ваши тесты дают вам достаточно уверенности, просто сломайте его! (убедитесь, что вы предварительно зафиксировали свои изменения в системе контроля версий). Измените код на следующий:

```go
func Countdown(out io.Writer, sleeper Sleeper) {
	for i := countdownStart; i > 0; i-- {
		sleeper.Sleep()
	}

	for i := countdownStart; i > 0; i-- {
		fmt.Fprintln(out, i)
	}

	fmt.Fprint(out, finalWord)
}
```

Если вы запустите свои тесты, они по-прежнему должны проходить, хотя реализация неверна.

Давайте снова используем "шпионирование" с новым тестом, чтобы проверить правильность порядка операций.

У нас есть две разные зависимости, и мы хотим записать все их операции в один список. Поэтому мы создадим *один шпион для них обоих*.

```go
type SpyCountdownOperations struct {
	Calls []string
}

func (s *SpyCountdownOperations) Sleep() {
	s.Calls = append(s.Calls, sleep)
}

func (s *SpyCountdownOperations) Write(p []byte) (n int, err error) {
	s.Calls = append(s.Calls, write)
	return
}

const write = "write"
const sleep = "sleep"
```

Наша структура `SpyCountdownOperations` реализует как `io.Writer`, так и `Sleeper`, записывая каждый вызов в один срез. В этом тесте нас беспокоит только порядок операций, поэтому простого их записи в виде списка именованных операций достаточно.

Теперь мы можем добавить подтест в наш набор тестов, который проверяет, что наши "сны" и выводы работают в том порядке, на который мы надеемся.

```go
t.Run("sleep before every print", func(t *testing.T) {
	spySleepPrinter := &SpyCountdownOperations{}
	Countdown(spySleepPrinter, spySleepPrinter)

	want := []string{
		write,
		sleep,
		write,
		sleep,
		write,
		sleep,
		write,
	}

	if !reflect.DeepEqual(want, spySleepPrinter.Calls) {
		t.Errorf("wanted calls %v got %v", want, spySleepPrinter.Calls)
	}
})
```

Этот тест теперь должен завершиться неудачно. Верните `Countdown` к прежнему состоянию, чтобы исправить тест.

Теперь у нас есть два теста, шпионящие за `Sleeper`, поэтому мы можем рефакторить наш тест так, чтобы один тестировал, что выводится, а другой гарантировал, что мы "спим" между выводами. Наконец, мы можем удалить нашего первого шпиона, так как он больше не используется.

```go
func TestCountdown(t *testing.T) {

	t.Run("prints 3 to Go!", func(t *testing.T) {
		buffer := &bytes.Buffer{}
		Countdown(buffer, &SpyCountdownOperations{})

		got := buffer.String()
		want := `3
2
1
Go!`

		if got != want {
			t.Errorf("got %q want %q", got, want)
		}
	})

	t.Run("sleep before every print", func(t *testing.T) {
		spySleepPrinter := &SpyCountdownOperations{}
		Countdown(spySleepPrinter, spySleepPrinter)

		want := []string{
			write,
			sleep,
			write,
			sleep,
			write,
			sleep,
			write,
		}

		if !reflect.DeepEqual(want, spySleepPrinter.Calls) {
			t.Errorf("wanted calls %v got %v", want, spySleepPrinter.Calls)
		}
	})
}
```

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

## Расширение Sleeper для возможности настройки

Хорошей функцией было бы сделать `Sleeper` настраиваемым. Это означает, что мы можем регулировать время "сна" в нашей основной программе.

### Сначала напишите тест

Сначала давайте создадим новый тип для `ConfigurableSleeper`, который будет принимать то, что нам нужно для настройки и тестирования.

```go
type ConfigurableSleeper struct {
	duration time.Duration
	sleep    func(time.Duration)
}
```

Мы используем `duration` для настройки времени "сна" и `sleep` как способ передачи функции "сна". Сигнатура `sleep` та же, что и у `time.Sleep`, что позволяет нам использовать `time.Sleep` в нашей реальной реализации и следующий шпион в наших тестах:

```go
type SpyTime struct {
	durationSlept time.Duration
}

func (s *SpyTime) SetDurationSlept(duration time.Duration) {
	s.durationSlept = duration
}
```

Имея нашего шпиона, мы можем создать новый тест для настраиваемого sleeper.

```go
func TestConfigurableSleeper(t *testing.T) {
	sleepTime := 5 * time.Second

	spyTime := &SpyTime{}
	sleeper := ConfigurableSleeper{sleepTime, spyTime.SetDurationSlept}
	sleeper.Sleep()

	if spyTime.durationSlept != sleepTime {
		t.Errorf("should have slept for %v but slept for %v", sleepTime, spyTime.durationSlept)
	}
}
```

В этом тесте не должно быть ничего нового, и он настроен очень похоже на предыдущие тесты моков.

### Попробуйте запустить тест

```
sleeper.Sleep undefined (type ConfigurableSleeper has no field or method Sleep, but does have sleep)

```

Вы должны увидеть очень четкое сообщение об ошибке, указывающее на то, что у нас нет метода `Sleep`, созданного в нашей `ConfigurableSleeper`.

### Напишите минимальный объем кода для запуска теста и проверьте вывод неудачного теста

```go
func (c *ConfigurableSleeper) Sleep() {
}
```

С нашим новым методом `Sleep` у нас есть неудачный тест.

```
countdown_test.go:56: should have slept for 5s but slept for 0s
```

### Напишите достаточно кода, чтобы тест прошел

Всё, что нам нужно сделать сейчас, это реализовать функцию `Sleep` для `ConfigurableSleeper`.

```go
func (c *ConfigurableSleeper) Sleep() {
	c.sleep(c.duration)
}
```

С этим изменением все тесты снова должны проходить, и вы, возможно, задаетесь вопросом, зачем все эти хлопоты, если основная программа вообще не изменилась. Надеюсь, это станет ясно после следующего раздела.

### Очистка и рефакторинг

Последнее, что нам нужно сделать, это фактически использовать наш `ConfigurableSleeper` в основной функции.

```go
func main() {
	sleeper := &ConfigurableSleeper{1 * time.Second, time.Sleep}
	Countdown(os.Stdout, sleeper)
}
```

Если мы запустим тесты и программу вручную, мы увидим, что все поведение остается прежним.

Поскольку мы используем `ConfigurableSleeper`, теперь безопасно удалить реализацию `DefaultSleeper`. Завершая нашу программу и имея более [обобщенный](https://stackoverflow.com/questions/19291776/whats-the-difference-between-abstraction-and-generalization) Sleeper с произвольно длинными обратными отсчетами.

## Но разве мокирование не зло?

Вы, возможно, слышали, что мокирование — это зло. Как и все в разработке программного обеспечения, оно может быть использовано во зло, так же как и [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself).

Люди обычно попадают в плохую ситуацию, когда они не *слушают свои тесты* и *не уважают стадию рефакторинга*.

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

* Тестируемый вами объект делает слишком много вещей (потому что у него слишком много зависимостей для мокирования)
  * Разделите модуль, чтобы он делал меньше
* Его зависимости слишком детализированы
  * Подумайте, как можно объединить некоторые из этих зависимостей в один значимый модуль
* Ваш тест слишком озабочен деталями реализации
  * Отдавайте предпочтение тестированию ожидаемого поведения, а не реализации

Обычно большое количество мокирования указывает на *плохую абстракцию* в вашем коде.

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

### Но моки и тесты всё ещё усложняют мне жизнь!

Вы когда-нибудь сталкивались с такой ситуацией?

* Вы хотите выполнить рефакторинг
* Для этого вы в конечном итоге меняете множество тестов
* Вы ставите под сомнение TDD и публикуете статью на Medium под названием "Мокирование вредно"

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

Иногда трудно понять, *какой уровень* тестировать, но вот некоторые мыслительные процессы и правила, которым я стараюсь следовать:

* **Определение рефакторинга заключается в том, что код меняется, но поведение остается тем же**. Если вы решили выполнить рефакторинг, теоретически вы должны иметь возможность сделать коммит без каких-либо изменений в тестах. Поэтому, когда вы пишете тест, спросите себя:
  * Тестирую ли я желаемое поведение или детали реализации?
  * Если бы я рефакторизировал этот код, пришлось бы мне вносить много изменений в тесты?
* Хотя Go позволяет тестировать приватные функции, я бы избегал этого, так как приватные функции являются деталями реализации для поддержки публичного поведения. Тестируйте публичное поведение. Sandi Metz описывает приватные функции как "менее стабильные", и вы не хотите связывать свои тесты с ними.
* Мне кажется, что если тест работает с **более чем 3 моками, то это красный флаг** — пора переосмыслить дизайн.
* Используйте шпионов с осторожностью. Шпионы позволяют вам видеть внутренности алгоритма, который вы пишете, что может быть очень полезно, но это означает более тесную связь между вашим тестовым кодом и реализацией. **Убедитесь, что вам действительно важны эти детали, если вы собираетесь шпионить за ними.**

#### Разве я не могу просто использовать фреймворк для мокирования?

Мокирование не требует никакой магии и относительно просто; использование фреймворка может сделать мокирование более сложным, чем оно есть. В этой главе мы не используем автомакирование, чтобы получить:

* лучшее понимание того, как мокировать
* практику реализации интерфейсов

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

Вы должны использовать только генератор моков, который генерирует тестовые дубли для интерфейса. Любой инструмент, который слишком жёстко диктует, как писать тесты, или в котором полно "магии", может катиться куда подальше.

## Подводим итоги

### Подробнее о подходе TDD

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

> "Когда использовать итеративную разработку? Вы должны использовать итеративную разработку только в проектах, которые вы хотите, чтобы преуспели."

Мартин Фаулер.

### Мокирование

* **Без мокирования важные области вашего кода останутся непроверенными**. В нашем случае мы не смогли бы протестировать, что наш код приостанавливался между каждым выводом, но есть бесчисленное множество других примеров. Вызов сервиса, который *может* выйти из строя? Желание протестировать вашу систему в определенном состоянии? Очень трудно протестировать эти сценарии без мокирования.
* Без моков вам, возможно, придется настраивать базы данных и другие сторонние вещи только для проверки простых бизнес-правил. У вас, вероятно, будут медленные тесты, что приведет к **медленным циклам обратной связи**.
* Из-за необходимости запускать базу данных или веб-сервис для тестирования чего-либо, у вас, вероятно, будут **хрупкие тесты** из-за ненадежности таких сервисов.

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

В этой статье о мокировании мы рассмотрели только **шпионов** (Spies), которые являются разновидностью моков. Моки — это тип "тестового дубля".

> [Тестовый дубль — это общий термин для любого случая, когда вы заменяете производственный объект для целей тестирования.](https://martinfowler.com/bliki/TestDouble.html)

Среди тестовых дублей существуют различные типы, такие как заглушки (stubs), шпионы (spies) и, конечно, моки! Дополнительную информацию см. в [статье Мартина Фаулера](https://martinfowler.com/bliki/TestDouble.html).

## Бонус - Пример итераторов из Go 1.23

В Go 1.23 [были представлены итераторы](https://tip.golang.org/doc/go1.23). Мы можем использовать итераторы различными способами, в данном случае мы можем создать итератор `countdownFrom`, который будет возвращать числа для обратного отсчета в обратном порядке.

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

```go
func Countdown(out io.Writer, sleeper Sleeper) {
	for i := range countDownFrom(3) {
		fmt.Fprintln(out, i)
		sleeper.Sleep()
	}

	fmt.Fprint(out, finalWord)
}
```

Чтобы написать итератор, подобный `countDownFrom`, вам нужно написать функцию определенным образом. Из документации:

```
Предложение “range” в цикле “for-range” теперь принимает функции-итераторы следующих типов
    func(func() bool)
    func(func(K) bool)
    func(func(K, V) bool)
```

(Буквы `K` и `V` обозначают типы ключа и значения соответственно.)

В нашем случае у нас нет ключей, только значения. Go также предоставляет удобный тип `iter.Seq[T]`, который является псевдонимом типа для `func(func(T) bool)`.

```go
func countDownFrom(from int) iter.Seq[int] {
	return func(yield func(int) bool) {
		for i := from; i > 0; i-- {
			if !yield(i) {
				return
			}
		}
	}
}
```

Это простой итератор, который будет выдавать числа в обратном порядке, начиная с `from` — идеально для нашего случая использования.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://eda-1.gitbook.io/lgwt/osnovy-go/mocking.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
