Shanraq.org Shanraq.org
Первый тест в Go: go test и файл _test.go
IT

Go: с нуля до своего блога Урок 10 из 50

Первый тест в Go: go test и файл _test.go

Десятый урок курса по Go. Проверка, которая остаётся: файл _test.go рядом с кодом, функция TestXxx и команда go test. Как читать провал — имя теста, файл, строка, «получили» и «ждали», — чем t.Errorf отличается от t.Fatalf и почему проверять надо края списка, а не его середину.

Зачем это нужно

Вы уже написали count и longer. Откуда вы знаете, что они верны? Запустили, посмотрели на вывод, кивнули. Такая проверка живёт одну минуту: поправите функцию через неделю — и проверять придётся заново, руками, и вы этого не сделаете.

Тест — та же самая проверка, только записанная. Её выполняет машина, и выполняет каждый раз.

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

Сразу целиком

Новая папка, go mod init sabaq09. На этот раз файлов будет два. Первый — main.go, знакомая функция из прошлого урока:

package main

import "fmt"

func count(tags []string) map[string]int {
	m := make(map[string]int)
	for _, t := range tags {
		m[t]++
	}
	return m
}

func main() {
	fmt.Println(count([]string{"go", "веб", "go"}))
}

Второй — main_test.go, рядом, в той же папке:

package main

import "testing"

func TestCount(t *testing.T) {
	got := count([]string{"go", "веб", "go"})

	if got["go"] != 2 {
		t.Errorf("go: получили %d, ждали 2", got["go"])
	}
	if len(got) != 2 {
		t.Errorf("тегов: получили %d, ждали 2", len(got))
	}
}

Прежде чем запускать — не заглядывая ниже, скажите вслух, что напечатает go test. Потом запустите и сверьте.

PASS
ok  	sabaq09

Время прогона в выдержках убрано: оно у всех своё.

Последнее число — сколько это заняло времени; у вас будет своё.

Разбор

Тест — это файл рядом с кодом

Имя обязано заканчиваться на _test.go. Это не соглашение об аккуратности, а правило языка: такие файлы Go собирает только при go test и не включает в готовую программу. Тестов можно писать сколько угодно и сколь угодно подробно — на размер программы, которую вы отдадите людям, они не влияют.

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

Пакет тот же самый — package main, — поэтому тест видит count напрямую, безо всякого подключения.

Как выглядит тест

func TestCount(t *testing.T) {

Три требования, и все три обязательны. Имя начинается с Test. Дальше идёт заглавная буква — TestCount, а не Testcount. И единственный параметр — t *testing.T.

t — это то, чем тест сообщает о провале. Звёздочку пока читайте как часть записи; в уроке про указатели разберём, что она значит.

Функция ничего не возвращает. Тест, который ничего не сказал, считается пройденным: молчание здесь означает «всё сошлось».

go test и go test -v

go test печатает итог: PASS или FAIL. Когда тестов станет много и захочется видеть каждый по имени, добавьте -v:

=== RUN   TestCount
--- PASS: TestCount 
PASS
ok  	sabaq09

Провал читается как отчёт

Сломайте нарочно: замените в первом сравнении 2 на 3 и поправьте текст сообщения. Запустите:

--- FAIL: TestCount 
    main_test.go:9: go: получили 2, ждали 3
    main_test.go:12: тегов: получили 2, ждали 3
FAIL

Здесь есть всё, что нужно: какой тест упал, в каком файле и на какой строке, что получили и чего ждали. Поэтому в сообщении и пишут обе величины. Сообщение вида «не сработало» не говорит ничего и заставляет лезть в код руками.

Красный FAIL — не наказание, а отчёт. За время работы вы увидите его в сотни раз чаще, чем PASS.

t.Errorf и t.Fatalf

t.Errorf записывает провал и продолжает тест — потому вы и увидели сразу две строки. t.Fatalf записывает и останавливает его: до второй проверки дело не дойдёт.

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

Что проверять в первую очередь

Не середину, а края. Середина обычно работает у всех.

Для count края такие: пустой список, один элемент, все элементы одинаковые. Для longer из урока про срез — список, где не подходит ни один заголовок, и список, где подходят все.

Образ. Дорога. Машины съезжают не на середине полосы, а на обочине. Проверять надо обочину.

Краёв обычно три-четыре, и переписывать ради каждого целый if вы быстро устанете. Так и не делают: случаи складывают в один список и проходят по нему циклом, а каждый получает своё имя в отчёте. Называется это табличным тестом, и мы напишем его в уроке про тесты во второй раз — когда у вас появятся структуры, из которых такой список и собирают. Пока держите в голове, что один тест на функцию — начало, а не образец.

Почему это не двойная работа

Кажется, будто вы пишете одно и то же дважды. На самом деле — по-разному. В count вы описываете, как считать. В тесте — что должно получиться. Ошибка в способе не повторится в описании результата, и потому одно ловит другое.

А ещё тест — единственная проверка, которая не устаёт. Вы поправите функцию через месяц, забудете половину и запустите go test — он вспомнит за вас.

Карта урока

Карта урока: тест рядом с кодом, вызывает его и даёт один из двух ответов

Скажите своими словами

Не подглядывая, ответьте вслух или на бумаге. Ответы — в конце урока.

  1. Почему файл теста обязан оканчиваться на _test.go? Что от этого зависит?
  2. Тест ничего не напечатал и ничего не вернул. Он прошёл или нет?
  3. В одном тесте четыре проверки, и вторая упала. Сколько сообщений вы увидите с t.Errorf и сколько с t.Fatalf?

Задание

Обязательное. Возьмите функцию longer из урока про срез и напишите к ней тест. Проверьте два случая: список "Дала", "Шаңырақ", "Go", "Көш" при n = 3 должен дать два заголовка, а пустой список при любом n — ни одного. Запустите go test и добейтесь PASS.

По желанию.

  • Сломайте longer нарочно — например, поставьте >= вместо > — и убедитесь, что тест краснеет. Проверка, которая не умеет упасть, ничего не проверяет.
  • Запустите go test -v и посмотрите на разницу.
  • Замените в своём тесте t.Errorf на t.Fatalf и сломайте обе проверки сразу. Сколько строк вы теперь видите?

Куда это встанет в блоге

Помните readingTime из урока про функции — ту, что считает время чтения? Ошибка в ней была бы тихой: на ровных 400 словах легко получить три минуты вместо двух, и никто не заметит, пока не сядет считать. Именно такие ошибки и ловит тест: он проверит 199, 200 и 201 слово за одну секунду, и так каждый раз.

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

Соберите свой блог. В step-4 к тем же файлам добавился main_test.go: проверки на края — ноль слов, ровно двести, двести один. Именно на них ломаются формулы вроде времени чтения.

Ответы

Показать ответы
  1. Потому что это правило языка, а не привычка. Файлы с таким окончанием собираются только командой go test и не попадают в готовую программу, поэтому тесты не увеличивают то, что вы отдаёте людям.
  2. Прошёл. Тест сообщает только о провале — через t.Errorf или t.Fatalf. Молчание означает, что всё сошлось.
  3. С t.Errorf вы увидите сообщения обо всех упавших проверках, потому что тест продолжается после каждой. С t.Fatalf — только одно, про вторую: на ней тест остановится, и третья с четвёртой не выполнятся.

Источники

Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом

Проверить задание

Сначала решите и запустите в VS Code — редактор покажет ошибку на месте. Готовое решение вставьте сюда. Проверяет модель: она укажет на ошибку, но не даст готовый ответ.

Чтобы проверить, нужно войти. Войти

Комментарии (0)

Пока нет комментариев. Будьте первым.