День 14 — глубже
Всё здесь необязательно. Материалы на английском помечены.
Официальный разбор первого теста (англ.)
- Add a test — https://go.dev/doc/tutorial/add-a-test Страница из вводного тура по модулям: тест к функции
Hello, тот же порядок — файл_test.go,func TestXxx(t *testing.T),t.Errorfиgo test. Полезно увидеть ту же схему на другом примере.
- Пакет testing — https://pkg.go.dev/testing Справочник. Первые три абзаца — про имена файлов и функций, дальше список всего, что умеет
t:Errorf,Fatalf,Log,Skip,Run,Helper. Читать целиком не надо, но знать, где смотреть, стоит.
- Table-driven tests — https://go.dev/wiki/TableDrivenTests Та самая форма со страницы 5 и объяснение, почему её любят: один цикл, много случаев, добавление случая — одна строка.
Что печатает go test, если попросить подробностей (англ.)
- go test — справка по ключам — https://pkg.go.dev/cmd/go#hdr-Testing_flags Здесь описаны
-run,-v,-count,-cover,-timeout. В курсе нужны первые три, остальные пригодятся в блоке 9.
Сколько строк функции проверил тест
Попробуйте сейчас: покрытие.
Цель: увидеть, какую долю строк вашего кода выполнил тест.
▶ Выполнитеcd ~/gocourse/day14/packs go test -cover .Число в процентах — доля строк пакета, которые тест выполнил хоть раз. Оно считает строки, а не случаи: тест без единого сравнения даст такое же покрытие, как хороший. Поэтому покрытие — подсказка «куда я вообще не заглядывал», а не оценка качества тестов.
Готово, когда: вы видите строку с процентом и можете сказать, какая ветка
packsосталась невыполненной, если процент меньше ста.
Подтесты: имя случая от Go, а не от вас
На странице 5 имя случая приходилось вставлять в сообщение руками — иначе непонятно, какая строка таблицы упала. У testing для этого есть t.Run: он запускает случай как отдельный маленький тест со своим именем.
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := price(c.qty)
if got != c.want {
t.Errorf("price(%d) = %d, хотели %d", c.qty, got, c.want)
}
})
}func(t *testing.T) { … } — функция без имени прямо на месте аргумента; такие функции разберём в блоке 5. Зато в выводе каждый случай виден отдельно, и -run умеет выбирать один случай по имени. В курсе до блока 5 пишем цикл без t.Run — форма со страницы 5 работает и понятна целиком.
Как тесты выглядят в самом Go
Исходники стандартной библиотеки лежат на машине рядом с компилятором. Посмотрите, например, тесты пакета strconv:
stagiaire@lab:~/gocourse/day14$ ls -1 /usr/local/go/src/strconv/
bytealg.go
bytealg_bootstrap.go
doc.go
example_test.go
export_test.go
import_test.go
isprint.go
makeisprint.go
number.go
number_test.go
quote.go
quote_test.go
strconv_test.goФайлы _test.go лежат ровно там же, где код, и пишутся по тем же правилам, что ваши. Откройте /usr/local/go/src/strconv/number_test.go в редакторе или через less — это тесты к разбору чисел, там же живёт TestAtoi, тест той самой strconv.Atoi из дня 07.
Внутри — та же таблица случаев, что на странице 5, только записана в две части: сначала описание полей, потом список случаев отдельной переменной.
type parseInt64Test struct {
in string
out int64
err error
}
var parseInt64Tests = []parseInt64Test{
{"", 0, ErrSyntax},
{"0", 0, nil},
{"1", 1, nil},
{"-1", -1, nil},
{"12345", 12345, nil},
{"9223372036854775808", 1<<63 - 1, ErrRange},
{"123%45", 0, ErrSyntax},
}Поля те же по смыслу: вход, ожидаемое, ожидаемая ошибка. Случаи — по одному в строке, и среди них ровно то, чему учит страница 4: пустая строка, ноль, граница int64 и строка с посторонним символом. Ходить по таблице TestAtoi тоже будет циклом — for i := range parseInt64Tests.
Мини-опыты
1. Тест на панику. Сделайте функцию, которая делит на количество, и вызовите её с нулём — в тесте. Что напечатает go test? Найдите в выводе слово panic и имя своего теста. Паника валит всю тестовую сборку, а не один тест: остальные тесты после неё не выполнятся.
2. Два теста, один падает. Оставьте в scratch два теста и сломайте ожидаемое в одном. Сколько раз в выводе встретится слово FAIL без -v и с -v? Почему у одного теста будет PASS, а у пакета — FAIL?
3. Кэш. Запустите go test . дважды и убедитесь, что второй раз печатается (cached). Потом допишите в файл теста пустую строку, сохраните и запустите снова — кэш пропал. Почему go test считает, что результат мог измениться?
4. Тест к чужой функции. Возьмите функцию из дня 13 — любую, которую писали не сегодня, — и напишите к ней тест на четыре случая по форме со страницы 5. Сколько случаев придётся придумать, прежде чем тест начнёт ловить подмену тела функции на return 0?