Упавший тест: как его читать
Зелёный ok — не самое интересное, что умеет go test. Смысл теста в том, что он однажды покраснеет и скажет, где именно перестало сходиться. Эта страница — про то, что он в этот момент печатает.
Что печатает упавший тест
Ситуация. В тесте к price ожидаемое посчитано неверно: написали 13000 вместо 13500. Для go test это то же самое, что сломанная функция: числа не сошлись.
1 package main
2
3 import "testing"
4
5 func TestPrice(t *testing.T) {
6 got := price(3)
7 want := 13000
8 if got != want {
9 t.Errorf("price(3) = %d, хотели %d", got, want)
10 }
11 }Что увидите.
stagiaire@lab:~/gocourse/day14/scratch$ go test .
--- FAIL: TestPrice (0.00s)
price_test.go:9: price(3) = 13500, хотели 13000
FAIL
FAIL day14/scratch 0.008s
FAILКак это читать. Пять строк, и каждая про своё.
| Строка | Что говорит |
|---|---|
--- FAIL: TestPrice (0.00s) |
какая тестовая функция не прошла и сколько работала |
price_test.go:9: price(3) = 13500, хотели 13000 |
самая важная: файл и строка, где сработал t.Errorf, и ваше собственное сообщение |
FAIL |
итог по пакету: были неудачи |
FAIL day14/scratch 0.008s |
какой пакет и сколько времени |
FAIL |
итог команды целиком |
Читается это так же, как сообщение компилятора из дня 01: файл, строка, текст. Разница в том, что текст после двоеточия пишет не Go, а вы сами — в t.Errorf. Насколько понятной будет строка, которую вы прочитаете через месяц, зависит только от того, что вы туда положили.
Полезное сообщение отвечает на три вопроса: что вызывали, что получили, что ожидали.
| Сообщение | Чем плохо или хорошо |
|---|---|
t.Errorf("не сошлось") |
не видно ни аргумента, ни чисел: придётся лезть в код теста |
t.Errorf("ошибка в price") |
то же самое плюс слово «ошибка», которое ничего не добавляет |
t.Errorf("%d", got) |
одно число без объяснения, что это |
t.Errorf("price(3) = %d, хотели %d", got, want) |
видно вызов, полученное и ожидаемое — чинить можно, не открывая тест |
Код возврата у упавшего go test — 1, как у программы с os.Exit(1) из дня 07. Это пригодится, когда тесты начнёт запускать не человек, а автоматика.
Попробуйте сейчас: уронить свой тест.
Цель: увидеть вывод упавшего теста на своём файле и вернуть его в зелёное.
1. В
scratch/price_test.goзаменитеwant := 13500наwant := 13000и запустите:▶ Выполнитеcd ~/gocourse/day14/scratch go test .2. Найдите в выводе номер строки и убедитесь, что в этой строке вашего файла действительно стоит
t.Errorf.3. Верните
want := 13500и запустите снова — сноваok.Готово, когда: вы видели строку
price_test.go:9:с сообщением и вернули тест в зелёное состояние.
Подробный вывод: go test -v
Ключ -v (verbose, «подробно») показывает каждый тест по отдельности — и те, что прошли:
stagiaire@lab:~/gocourse/day14/scratch$ go test -v .
=== RUN TestPrice
--- PASS: TestPrice (0.00s)
PASS
ok day14/scratch 0.006s=== RUN — тест запущен, --- PASS — прошёл. У упавшего теста порядок строк другой: сообщения печатаются между RUN и FAIL, потому что они появляются по ходу работы теста, а итог известен только в конце:
stagiaire@lab:~/gocourse/day14/scratch$ go test -v .
=== RUN TestPrice
price_test.go:9: price(3) = 13500, хотели 13000
--- FAIL: TestPrice (0.00s)
FAIL
FAIL day14/scratch 0.007s
FAILБез -v про удачные тесты не печатается ничего: молчание и есть хорошая новость. С -v видно, что тестов действительно несколько и что запустились все, — это первое, чем проверяют «а мой тест вообще выполняется?».
Две проверки в одной функции
В одной тестовой функции проверок может быть сколько угодно. Каждая несошедшаяся печатает свою строку:
1 package main
2
3 import "testing"
4
5 func TestPrice(t *testing.T) {
6 got3 := price(3)
7 if got3 != 13000 {
8 t.Errorf("price(3) = %d, хотели %d", got3, 13000)
9 }
10 got12 := price(12)
11 if got12 != 48000 {
12 t.Errorf("price(12) = %d, хотели %d", got12, 48000)
13 }
14 }Ожидаемые числа здесь нарочно неверны — обе проверки не сойдутся.
Попробуйте сейчас: сколько строк напечатает упавший тест.
Цель: предсказать число строк с именем файла в выводе и сдать ответ.
1. Не запуская: посмотрите на программу с двумя проверками выше. Обе не сойдутся. Сколько строк, начинающихся с
price_test.go:, напечатаетgo test .? Вывод этого прогона напечатан сразу под блоком — смотрите туда после того, как отправите ответ.2. Сдать число:
▶ Выполните · выделенное замените своимcourse answer day14.q1 ЧИСЛОВместо
ЧИСЛО— количество таких строк.3. Потом наберите этот тест — уже без номеров строк — и проверьте запуском:
✎ Наберите в файлscratch/price_test.gopackage main import "testing" func TestPrice(t *testing.T) { got3 := price(3) if got3 != 13000 { t.Errorf("price(3) = %d, хотели %d", got3, 13000) } got12 := price(12) if got12 != 48000 { t.Errorf("price(12) = %d, хотели %d", got12, 48000) } }▶ Выполнитеcd ~/gocourse/day14/scratch go test .Готово, когда: пункт
q1вcourse check 14зелёный, а вывод совпал с предсказанием.
Что увидите.
stagiaire@lab:~/gocourse/day14/scratch$ go test .
--- FAIL: TestPrice (0.00s)
price_test.go:8: price(3) = 13500, хотели 13000
price_test.go:12: price(12) = 48600, хотели 48000
FAIL
FAIL day14/scratch 0.006s
FAILt.Errorf не останавливает тестовую функцию: он помечает её неудачной, печатает строку — и выполнение идёт дальше. Поэтому за один прогон видно все расхождения сразу, а не по одному за запуск.
t.Fatalf: остановить эту тестовую функцию
Иногда идти дальше бессмысленно: если первая же проверка показала, что функция возвращает чушь, остальные строки только засорят вывод. Для этого есть t.Fatalf — то же самое, что t.Errorf, плюс «дальше не выполнять».
В программе выше заменим t.Errorf в строке 8 на t.Fatalf:
5 func TestPrice(t *testing.T) {
6 got3 := price(3)
7 if got3 != 13000 {
8 t.Fatalf("price(3) = %d, хотели %d", got3, 13000)
9 }
10 got12 := price(12)
11 if got12 != 48000 {
12 t.Errorf("price(12) = %d, хотели %d", got12, 48000)
13 }
14 }Попробуйте сейчас: сколько строк напечатает Fatalf.
Цель: предсказать вывод того же теста, где первая проверка —
t.Fatalf.1. Не запуская: в программе выше первая проверка зовёт
t.Fatalf, вторая —t.Errorf, и обе не сходятся. Сколько строк, начинающихся сprice_test.go:, напечатаетgo test .? Вывод этого прогона напечатан сразу под блоком — смотрите туда после того, как отправите ответ.2. Сдать число:
▶ Выполните · выделенное замените своимcourse answer day14.q2 ЧИСЛО3. Проверьте запуском: замените в своём файле
t.Errorfпервой проверки наt.Fatalfи снова запустите.▶ Выполнитеcd ~/gocourse/day14/scratch go test .Готово, когда: пункт
q2зелёный, а вывод совпал с предсказанием. После опыта вернитеt.Errorf— дальше он удобнее.
Что увидите.
stagiaire@lab:~/gocourse/day14/scratch$ go test .
--- FAIL: TestPrice (0.00s)
price_test.go:8: price(3) = 13500, хотели 13000
FAIL
FAIL day14/scratch 0.004s
FAILВторая проверка не выполнилась вовсе: Fatalf прекратил тестовую функцию на строке 8. Обратите внимание — про вторую проверку теперь ничего не известно: она не прошла и не упала, её просто не было.
| Что берут | Когда |
|---|---|
t.Errorf |
обычный случай: проверок несколько, и про каждую хочется знать |
t.Fatalf |
дальше проверять нечего или опасно: функция вернула ошибку, а вы собирались смотреть на её результат |
В курсе по умолчанию — t.Errorf. t.Fatalf понадобится в дне 15 и дальше, когда проверяемая функция начнёт возвращать два значения, и второе — error.
Запустить один тест: go test -run
Тестовых функций в файле бывает несколько. Когда чините одну, гонять все не нужно: -run берёт те, чьё имя подходит под образец.
stagiaire@lab:~/gocourse/day14/scratch$ go test -v .
=== RUN TestPrice
--- PASS: TestPrice (0.00s)
=== RUN TestPriceBulk
--- PASS: TestPriceBulk (0.00s)
PASS
ok day14/scratch 0.008s
stagiaire@lab:~/gocourse/day14/scratch$ go test -v -run TestPriceBulk .
=== RUN TestPriceBulk
--- PASS: TestPriceBulk (0.00s)
PASS
ok day14/scratch 0.007sОбразец — не точное имя, а кусок имени: -run TestPrice запустит и TestPrice, и TestPriceBulk, потому что имя второго начинается так же. Если под образец не подошло ничего, go test честно предупреждает:
ok day14/scratch 0.006s [no tests to run]Строка [no tests to run] — это «я запустил ноль тестов». Зелёное ok здесь ничего не значит: опечатка в имени после -run даёт ровно такой же вывод, как удачный прогон.
| Команда | Что делает |
|---|---|
go test . |
все тесты пакета в текущем каталоге, кратко |
go test -v . |
то же, но с именем каждого теста и его исходом |
go test -run TestPrice . |
только тесты, в имени которых есть TestPrice |
go test -count=1 . |
прогнать заново, не беря результат из кэша |
go test ./... |
все пакеты модуля, из любого его каталога |
Попробуйте сейчас: второй тест и
-run.Цель: в файле два теста, и вы умеете запускать один из них.
1. Допишите в
scratch/price_test.goвторую тестовую функцию — про заказ со скидкой:✎ Наберите в файлscratch/price_test.gopackage main import "testing" func TestPrice(t *testing.T) { got := price(3) want := 13500 if got != want { t.Errorf("price(3) = %d, хотели %d", got, want) } } func TestPriceBulk(t *testing.T) { got := price(12) want := 48600 if got != want { t.Errorf("price(12) = %d, хотели %d", got, want) } }2. Запустите оба теста подробно, потом только второй:
▶ Выполнитеcd ~/gocourse/day14/scratch go test -v . go test -v -run TestPriceBulk .Готово, когда: в первом выводе две строки
=== RUN, во втором — одна, и обе команды кончились наok— пунктыt_verboseиt_run.
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
--- FAIL есть, а строки с именем файла нет |
вместо t.Errorf вызван t.Fail() — он валит тест, но текста не печатает вовсе |
заменить на t.Errorf и положить в него вызов, полученное и ожидаемое. Пустое сообщение t.Errorf("") строку с файлом печатает: она просто пустая после двоеточия |
вместо --- FAIL в выводе panic: и длинный список строк со стрелками |
тест не упал, а уронил всю сборку паникой: деление на ноль, обращение за край | читать первую строку после panic: — это причина; список ниже — путь вызовов, он разбирается в блоке 4 |
номер строки в сообщении указывает на t.Errorf, а не на ошибку в функции |
так и задумано: тест показывает, где сравнили, а не где дефект | по сообщению искать дефект в проверяемой функции |
ok при опечатке в -run |
образец не подошёл ни к одному имени | смотреть на [no tests to run] в конце строки |
(cached) вместо прогона |
с прошлого раза ничего не менялось | go test -count=1 . |
(*testing.common).Errorf format %d has arg "мало" of wrong type string |
в t.Errorf %d, а передана строка |
привести формат и аргументы в соответствие, как в fmt.Printf; разбор — на странице 7 |
| тест падает, а числа в сообщении правильные | ожидаемое посчитано неверно — дефект в тесте, а не в функции | пересчитать ожидаемое по условию на бумаге |
Дальше: тест, который всегда зелёный, — course next