Тест: функция не трогает вход
В дне 14 тест сравнивал число с числом. Функции над срезами возвращают срезы, и у них есть второе обещание: входной срез после вызова прежний. Сегодня тест проверяет оба.
Срезы в тесте сравнивают через slices.Equal
got != []int{12} в тесте не соберётся — ни один срез, кроме nil, так не сравнивают:
stagiaire@lab:~/gocourse/day17/scratch$ go test .
# day17/scratch [day17/scratch.test]
./main_test.go:7:5: invalid operation: got != []int{…} (slice can only be compared to nil)
FAIL day17/scratch [build failed]
FAILСравнивают функцией со страницы 5: if !slices.Equal(got, want).
Фильтр на месте
Ситуация. В чужом коде часто встречается такой фильтр «убрать пустые ячейки» — без нового массива:
package main
import "fmt"
func withoutZero(stock []int) []int {
res := stock[:0]
for _, q := range stock {
if q != 0 {
res = append(res, q)
}
}
return res
}
func main() {
stock := []int{0, 12, 0, 7, 5}
full := withoutZero(stock)
fmt.Println(full)
fmt.Println(stock)
}Что увидите.
[12 7 5]
[12 7 5 7 5]Как это читать. stock[:0] — окно нулевой длины на тот же массив, ёмкость 5. Каждый append в res пишет в начало массива stock:
до цикла: ┌────┬────┬────┬────┬────┐ res: len 0 cap 5
│ 0 │ 12 │ 0 │ 7 │ 5 │
└────┴────┴────┴────┴────┘
после: ┌────┬────┬────┬────┬────┐ res: len 3 cap 5
│ 12 │ 7 │ 5 │ 7 │ 5 │
└────┴────┴────┴────┴────┘
└──── res ─────┘Это те же «два среза на один массив»: результат верный, а вход испорчен. Приём законен, когда вход больше не нужен, и вреден, когда вызывающий им ещё пользуется.
Тест, который это пропускает, и тест, который ловит
Тест по образцу дня 14 проверяет только результат:
package main
import (
"slices"
"testing"
)
func TestWithoutZero(t *testing.T) {
stock := []int{0, 12, 0, 7, 5}
got := withoutZero(stock)
want := []int{12, 7, 5}
if !slices.Equal(got, want) {
t.Errorf("withoutZero = %v, хотели %v", got, want)
}
}stagiaire@lab:~/gocourse/day17/scratch$ go test .
ok day17/scratch 0.006sЗелёный — а функция портит вход. Добавим второе сравнение: сам stock после вызова против его прежнего содержимого, записанного отдельным литералом.
package main
import (
"slices"
"testing"
)
func TestWithoutZero(t *testing.T) {
stock := []int{0, 12, 0, 7, 5}
got := withoutZero(stock)
want := []int{12, 7, 5}
if !slices.Equal(got, want) {
t.Errorf("withoutZero = %v, хотели %v", got, want)
}
before := []int{0, 12, 0, 7, 5}
if !slices.Equal(stock, before) {
t.Errorf("входной срез изменился: %v, был %v", stock, before)
}
}stagiaire@lab:~/gocourse/day17/scratch$ go test .
--- FAIL: TestWithoutZero (0.00s)
main_test.go:17: входной срез изменился: [12 7 5 7 5], был [0 12 0 7 5]
FAIL
FAIL day17/scratch 0.005s
FAILТеперь тест красный. Починка — одна строка: var res []int вместо res := stock[:0], и у результата свой массив. После неё тот же тест печатает ok.
Две детали, без которых проверка входа не работает:
- прежнее содержимое — отдельный литерал, а не
before := stock:before := stock— ещё одно окно на тот же массив, испортится вместе соstock, и сравнение всегда даст «равны»; - во входе пустая ячейка стоит раньше полной: если все пустые в конце, фильтр на месте перепишет ячейки теми же числами, и порчу не видно.
%v в t.Errorf печатает срез так же, как Println: в квадратных скобках.
Попробуйте сейчас: задание keep — тест на результат и на вход.
Цель:
nonempty_test.goв~/gocourse/day17/keepпадает и на функции, которая портит вход, и на функции с неверным результатом.1. Прочитайте условие и функцию:
▶ Выполнитеcd ~/gocourse/day17/keep cat TASK.txt cat nonempty.go go run .2. Наберите
nonempty_test.goпо образцуTestWithoutZeroвыше. Во входе — ячейка ровно сminQtyшт., ячейка меньшеminQtyи пустая ячейка раньше полной. Числа считайте от константы:minQty + 10,minQty - 1— тогда тест не зависит от вашего числа.3. Проверьте силу теста приёмом дня 14: временно замените в
nonempty.goстрокуvar res []intнаres := stock[:0], запуститеgo test .— тест обязан упасть на проверке входа — и верните строку.4. Проверьте и закоммитьте:
▶ Выполнитеgo test -v . diff ~/.course/orig/day17/keep/nonempty.go nonempty.go git add . git commit -m "keep: тест на результат и на вход"Готово, когда:
go test .печатаетokна исходной функции,diffпуст, пунктыkeepиkeepresзелёные. Проверка подменит функцию двумя неверными версиями — одна портит вход, другая ставит ноль в ячейку ровно в порог, не меняя длины результата, — и ещё одной верной, записанной иначе.
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
invalid operation: got != []int{…} (slice can only be compared to nil) и [build failed] |
срезы сравнивают оператором | !slices.Equal(got, want) |
undefined: slices в файле теста |
у файла теста свой import |
добавить "slices" в import теста |
| тест зелёный на функции, которая портит вход | вход после вызова не сверяется | второе slices.Equal: stock против литерала |
| проверка входа есть, а порча не ловится | before := stock — окно на тот же массив, или пустые ячейки стоят только в конце входа |
прежнее содержимое — отдельным литералом, пустую ячейку — первой |
Дальше: трассировка с колонками len и cap — course next