День 14 · Первый тест: go test · страница 7 из 7

Ритуал перед коммитом

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

Образец · набирать не нужно
gofmt -l .     формат: код выглядит так же, как весь остальной Go
go vet .       подозрительные места, которые компилятор пропускает
go test .      поведение: функции считают то, что задумано
git add . и git commit -m "что сделали"

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


Три команды по очереди

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

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day14/cost$ gofmt -l .
stagiaire@lab:~/gocourse/day14/cost$ go vet .
stagiaire@lab:~/gocourse/day14/cost$ go test .
ok  	day14/cost	0.005s

Две первые команды промолчали, третья сказала ok — можно коммитить.

Как это читать. У каждой команды своя работа, и они не заменяют друг друга.

Команда Что находит Чего не находит
gofmt -l . файлы, которые отличаются от канонического формата ничего про смысл кода
go vet . подозрительное, но собирающееся: неверный формат в Printf, недостижимый код обычные ошибки в расчётах
go test . расхождение между тем, что функция считает, и тем, что вы ожидали случаи, которых нет в тестах

gofmt -l . печатает имена файлов, а не разницу; исправляет их gofmt -w . или Ctrl+S в VS Code. Файлы _test.go форматируются по тем же правилам, что обычные.


go vet и тесты

go vet смотрит и на тестовые файлы. Чаще всего он ловит несовпадение формата и аргументов в t.Errorf — та же проверка, что для fmt.Printf из дня 03. Пусть в тесте вместо числа в t.Errorf попала строка:

Код для чтения · разбираем, набирать не нужно
 1  package main
 2
 3  import "testing"
 4
 5  func TestPrice(t *testing.T) {
 6  	got := price(3)
 7  	want := 13500
 8  	if got != want {
 9  		t.Errorf("price(3) = %d", "мало")
10  	}
11  }
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day14/scratch$ go vet .
price_test.go:9:24: (*testing.common).Errorf format %d has arg "мало" of wrong type string

Строка читается как сообщение компилятора: файл, строка, колонка — и дальше находка. Колонка 24 указывает не на аргумент, а на сам %d: vet показывает место в строке формата, из-за которого он ругается. (*testing.common).Errorf — это полное имя того t.Errorf, который вы вызвали: Errorf у t достался от общей части testing, и vet называет его так, как он записан в стандартной библиотеке.

Часть проверок vet встроена в go test: он гоняет их сам перед запуском тестов, поэтому та же беда видна и так — и тесты при этом не запускаются вовсе:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day14/scratch$ go test .
# day14/scratch
# [day14/scratch]
./price_test.go:9:24: (*testing.common).Errorf format %d has arg "мало" of wrong type string
FAIL	day14/scratch [build failed]
FAIL

Текст находки тот же, а обрамление другое: go test печатает имя пакета и [build failed] — «до запуска тестов дело не дошло», как в дне 01 при ошибке сборки. И путь к файлу у него с ./, а у go vet — без.

Отдельно go vet всё равно нужен: в go test встроена не вся его проверка, а только часть, и работает она лишь по тестовой сборке.


Все каталоги разом

Каталогов у дня несколько, и обходить их по одному утомительно. Из корня модуля — каталога ~/gocourse/day14, где лежит go.mod, — работает многоточие: ./... значит «этот каталог и все вложенные».

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day14$ go test ./...
ok  	day14/cost	0.006s
ok  	day14/deliver	0.006s
ok  	day14/packs	0.006s
ok  	day14/scratch	0.007s

Каталог, в котором нет ни одного файла _test.go, отмечается знаком ? и в счёт не идёт. gofmt -l . и go vet ./... из корня работают так же.

Порядок пакетов в выводе — по путям, не по времени: сначала cost, потом deliver, packs и scratch. Поэтому строки можно читать как список каталогов и сразу видеть, какого в нём не хватает.


Когда ритуал покраснел

Красное по всему модулю выглядит так: три пакета отчитались ok, четвёртый — нет. Здесь в scratch/price.go снова стоит qty > 12 вместо qty >= 12 — тот самый дефект со страницы 6.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day14$ go test ./...
ok  	day14/cost	0.008s
ok  	day14/deliver	0.009s
ok  	day14/packs	0.010s
--- FAIL: TestPrice (0.00s)
    price_test.go:19: ровно порог: price(12) = 54000, хотели 48600
--- FAIL: TestPriceOnThreshold (0.00s)
    price_test.go:28: price(12) = 54000, хотели 48600
FAIL
FAIL	day14/scratch	0.006s
FAIL

Как это читать. Отчёт про упавший пакет стоит на его месте в списке, а не в конце: --- FAIL на каждый упавший тест, под ним строка с сообщением, потом FAIL по пакету. Последнее одинокое FAIL — итог команды целиком. Имя пакета в строке FAIL day14/scratch и говорит, в какой каталог идти.

Упавших теста два, а дефект один: его видят и случай «ровно порог» из таблицы (строка 19), и репродьюсер TestPriceOnThreshold (строка 28). Так и должно быть — это разные проверки одного и того же места.

Дальше — сузить, как в дне 09: перейти в этот каталог и работать только с ним, чтобы вывод не мешался.

Кто ругается Что делать сначала
gofmt -l . напечатал имена файлов gofmt -w ., потом повторить ритуал с начала: формат мог сдвинуть номера строк в сообщениях
go vet ./... нашёл что-то почитать находку: vet показывает файл, строку и колонку — чинить по ней, тесты пока не трогать
go test ./... дал FAIL cd в каталог из строки FAIL day14/… и там go test -v ., дальше -run по имени упавшего теста
[build failed] вместо результатов тестов это ошибка сборки, а не теста: пакет не собрался, читать первую строку с файл:строка
в списке есть ? у каталога задания в каталоге нет файла _test.go — тест не написан или назван неверно
пункт code_fmt красный, хотя gofmt и vet молчат ни в одном из каталогов cost, packs, deliver ещё нет своего теста: заготовки стенд положил уже ровными, и пункт без вашей работы не засчитывается написать тесты заданий — страницы 3, 5 и 6

Одно правило про порядок: если gofmt что-то поправил, ритуал начинается заново. Формат меняет номера строк, и сообщение упавшего теста, записанное до gofmt -w, будет указывать не туда.


Попробуйте сейчас: ритуал на всех заданиях.

Цель: три команды проходят чисто во всех каталогах дня.

1. Из корня дня:

▶ Выполните
cd ~/gocourse/day14
gofmt -l .
go vet ./...
go test ./...

2. Если gofmt -l . напечатал имена файлов — поправьте формат и повторите:

▶ Выполните
gofmt -w .

Готово, когда: gofmt -l . и go vet ./... молчат, а go test ./... печатает ok для cost, packs и deliver — пункт code_fmt.


Коммит: что и когда записывать

Репозиторий вы завели на первой странице, и каждое задание кончалось своим коммитом. Подпись автора (user.name и user.email) настраивается один раз на машину — это день 11.

Перед коммитом всегда стоит посмотреть, что именно записывается:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day14$ git status -s
 M deliver/total.go
?? cost/cost_test.go

Короткий вид git status -s показывает по строке на файл: M — изменён, ?? — ещё не под присмотром git. Дальше — как в дне 11:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day14$ git add .
stagiaire@lab:~/gocourse/day14$ git commit -m "тест к функции cost"

Что коммитить сегодня: исходники и тесты. Тест — такая же часть проекта, как код: без него следующий человек не узнает, какие случаи вы проверяли. Собранные программы не коммитят — их всегда можно собрать заново, для этого в каталоге дня лежит .gitignore. Правило в нём то же, что вы завели в дне 11, — одна строка prog: собирайте всегда как go build -o prog ., и одного правила хватает на все задания.

Коммитов за день должно набраться не меньше четырёх — по одному на законченный кусок работы, и все они уже сделаны по ходу дня:

Коммит Когда Страница
день 14: заготовки репозиторий заведён, ничего не тронуто 1
первый тест к cost задание cost сдано 3
таблица случаев для packs задание packs сдано 5
тест на границу доставки репродьюсер написан и падает 6
починка границы в total дефект исправлен, тест зелёный 6

Попробуйте сейчас: история дня.

Цель: вся работа дня записана, в истории не меньше четырёх коммитов с изменениями.

1. Посмотрите историю и не осталось ли незакоммиченного:

▶ Выполните
cd ~/gocourse/day14
git log --oneline
git status -s

2. Если git status -s что-то печатает — забытое задание или черновик, — запишите и это:

▶ Выполните
git add .
git commit -m "что сделали"
git log --oneline

Готово, когда: git log --oneline показывает не меньше четырёх строк, и по каждой понятно, что было сделано, — пункт git. Проверка считает только коммиты с изменениями и смотрит, что в истории оказались все три файла с тестами — из cost, packs и deliver: git ls-tree -r --name-only HEAD.


Словарик ошибок

Сегодняшние сообщения — от go test, и читаются они как сообщения компилятора: файл, строка, текст. Допишите в ~/errors.md то, что встретили сами: слева текст ошибки, тире, справа разбор своими словами.

Образец · набирать не нужно
no test files - файл с тестом назван не на _test.go, go test его не нашёл
undefined: testing - забыл import "testing" в файле теста

К концу дня в словарике должно быть не меньше 28 разборов — записи за все дни считаются вместе. Разделитель — тире с пробелами, двоеточие не считается: оно есть в каждом сообщении.

Что стоит записать сегодня, если встретили: [no test files], [no tests to run], undefined: testing, wrong signature for TestXxx, declared and not used: got, TestXxx redeclared in this block, found packages main и другой в одном каталоге, сообщение vet про %d и строку, а ещё — словами — «тест зелёный на заведомо неверной функции».


Итог дня

Что Как
тест — программа рядом с кодом файл имя_test.go, package main, import "testing"
тестовая функция func TestXxx(t *testing.T), имя после Test — с заглавной
сравнение got := f(…), want := …, if got != want { t.Errorf(…) }
запуск go test ., go test -v ., go test -run Имя ., go test ./...
упавший тест --- FAIL, строка файл:номер: ваше сообщение, FAIL
t.Errorf и t.Fatalf первый идёт дальше, второй прекращает функцию
какие случаи обычный, обе стороны границы, ноль, ошибочный вход
много случаев таблица случаев: добавить случай — одна строка
проверка теста сломать функцию нарочно: тест обязан покраснеть
порядок починки сначала падающий тест, потом правка, потом все тесты
перед коммитом gofmt -l ., go vet ., go test ., потом git commit

Завтра — контрольная точка блока: функции, ошибки как результат, разбиение задачи и тесты вместе.


Материал дня закончен. Задания — course lab 14, дополнительное чтение — course extra 14