Ритуал перед коммитом
Три команды, которые стоит выполнять перед каждым коммитом, вы уже знаете по отдельности. Сегодня они складываются в привычку — короткую проверку, после которой не стыдно записать работу в репозиторий.
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 -s2. Если
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