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

Упавший тест: как его читать

Зелёный 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.go
package 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
FAIL

t.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.go
package 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