Срез в функции и тест
Со срезами функции и тесты пишутся как в блоке 3. Два отличия покажут опыты этой страницы.
Срез — параметр функции
Ситуация. Сумма остатков нужна в нескольких местах — пусть её считает функция.
package main
import "fmt"
func total(stock []int) int {
sum := 0
for _, qty := range stock {
sum += qty
}
return sum
}
func main() {
stock := []int{12, 40, 5}
fmt.Println(total(stock))
fmt.Println(total([]int{7, 3}))
fmt.Println(total([]int{}))
}Что увидите.
57
10
0Как это читать. Тип параметра — []int, без длины: функция принимает срез любой длины, хоть пустой. Аргументом можно передать переменную или сразу литерал []int{7, 3}. Внутри функции со срезом работают как обычно: range, len, индекс.
Попробуйте сейчас: что выведет функция со срезом.
Цель: предсказать два числа и сдать ответ.
1. Не запуская, прочитайте программу. Функция получает срез и число и меняет внутри оба:
Код для чтения · разбираем, набирать не нужноpackage main import "fmt" func reserve(stock []int, n int) { stock[0] = stock[0] - n n = 0 } func main() { stock := []int{12, 40} n := 5 reserve(stock, n) fmt.Println(stock[0], n) }2. Сдайте два числа через пробел:
▶ Выполните · выделенное замените своимcourse answer day16.q4 ЧИСЛО ЧИСЛО3. Проверьте запуском — программа целиком в черновик:
✎ Наберите в файлscratch/main.gopackage main import "fmt" func reserve(stock []int, n int) { stock[0] = stock[0] - n n = 0 } func main() { stock := []int{12, 40} n := 5 reserve(stock, n) fmt.Println(stock[0], n) }▶ Выполнитеcd ~/gocourse/day16/scratch go run .Готово, когда: пункт
q4вcourse check 16зелёный.
Разбор. Число n пришло в функцию копией — день 11: n = 0 меняет копию, в main осталось 5. А запись по индексу в срез-параметр видна снаружи: stock[0] в main стал 7.
Сегодня это факт: элементы среза, изменённые внутри функции, меняются и у вызвавшего. В функцию копируется окно, а ряд ячеек под ним общий; картинку разберём в день 17 — там же увидим, почему append внутри функции снаружи не виден.
Функция, которая возвращает срез
Функция может и отдавать срез. Пример — сколько штук дозаказать в каждую ячейку, чтобы она стала полной:
func toOrder(stock []int, full int) []int {
order := []int{}
for _, qty := range stock {
need := 0
if qty < full {
need = full - qty
}
order = append(order, need)
}
return order
}Для toOrder([]int{12, 40, 5}, 30) она возвращает [18 0 25]. Тест к ней по образцу дня 14 напрашивается сам — и не собирается:
got := toOrder([]int{12, 40, 5}, 30)
want := []int{18, 0, 25}
if got != want {
t.Errorf("toOrder = %v, хотели %v", got, want)
}stagiaire@lab:~/gocourse/day16/scratch$ go test .
# day16/scratch [day16/scratch.test]
./order_test.go:8:5: invalid operation: got != want (slice can only be compared to nil)
FAIL day16/scratch [build failed]
FAILСрезы в Go знаком == и != не сравниваются: разрешено только сравнение с nil — особым значением: срез без массива называют nil-срезом, о нём в день 17. Сравнивать приходится самим — как человек сверяет две описи: сначала число строк, потом строку за строкой.
func TestToOrder(t *testing.T) {
got := toOrder([]int{12, 40, 5}, 30)
want := []int{18, 0, 25}
if len(got) != len(want) {
t.Fatalf("toOrder: длина %d, хотели %d: %v", len(got), len(want), got)
}
for i := range want {
if got[i] != want[i] {
t.Errorf("toOrder: ячейка %d: %d, хотели %d", i+1, got[i], want[i])
}
}
}Как это читать. Длина — первой и через t.Fatalf из дня 14: если длины разные, цикл по want спросил бы у got индекс, которого нет, и тест упал бы паникой. Потом элементы сравниваются попарно; %v печатает срез целиком.
Что увидите, если в функции убрать if и считать need := full - qty всегда:
stagiaire@lab:~/gocourse/day16/scratch$ go test .
--- FAIL: TestToOrder (0.00s)
order_test.go:13: toOrder: ячейка 2: -10, хотели 0
FAIL
FAIL day16/scratch 0.009s
FAILА если цикл в функции начать с индекса 1, пропадёт первая ячейка — и сработает проверка длины:
stagiaire@lab:~/gocourse/day16/scratch$ go test .
--- FAIL: TestToOrder (0.00s)
order_test.go:9: toOrder: длина 2, хотели 3: [0 25]
FAIL
FAIL day16/scratch 0.010s
FAILТаблица случаев — это срез
Теперь можно объяснить форму, которую в дне 14 набирали по образцу:
cases := []struct {
name string
stock []int
limit int
want []int
}{
{"две ниже", []int{12, 3, 40, 5}, 10, []int{2, 4}},
{"никого ниже", []int{10, 20}, 10, []int{}},
}
for _, c := range cases {[]…{…} — срез, как []int{12, 40}: в фигурных скобках его элементы, по одному на строку. Элемент здесь не число, а запись из нескольких полей — что такое struct и как описывают поля, разберём в блоке 5. for _, c := range cases — обычный обход среза с этой страницы: c — очередной случай. Поле случая берут через точку: c.stock, c.want; подробнее — блок 5. Полями случая могут быть и срезы: stock []int, want []int.
Попробуйте сейчас: задание low.
Цель:
low_test.goв~/gocourse/day16/lowловит обе подмены функцииlowCells.1. Прочитайте условие и функцию:
▶ Выполнитеcd ~/gocourse/day16/low cat TASK.txt cat low.go go run .2. Напишите
low_test.go: сравнение длины и каждого элемента; среди случаев — такой, где ниже порога последняя ячейка. Можно одним тестом с таблицей, можно несколькими тестовыми функциями.3. Запустите:
▶ Выполнитеgo test . go test -v . gofmt -l .4. Проверьте силу теста: временно замените в
low.goстрокуlow = append(low, i+1)наlow = append(low, i)— тест обязан упасть. Верните файл из оригинала и прогоните тест ещё раз:▶ Выполнитеgo test . cp ~/.course/orig/day16/low/low.go low.go go test .5. Закоммитьте:
▶ Выполнитеcd ~/gocourse/day16 git add . git commit -m "low: тест к lowCells"Готово, когда:
go test .печатаетokна верной функции иFAILна подменённой — пунктыlowtestиlowedge.
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
invalid operation: got != want (slice can only be compared to nil) |
срезы сравнивают знаком | длина, потом цикл по элементам |
lowtest красный: тест проходит на подмене |
сравнивается только длина, а номера — нет | после проверки длины — сравнение got[i] и want[i] |
lowedge красный |
ни в одном случае ниже порога не оказалась последняя ячейка | добавьте случай, где последний элемент меньше порога |
| изменили срез внутри функции, и он «испортился» у вызвавшего | запись по индексу в срез-параметр видна снаружи | так устроен срез; как сделать копию — день 17 |
две строки lowCells redeclared in this block и other declaration of lowCells, потом FAIL day16/low [build failed] |
функцию объявили ещё раз в файле теста | в тесте функцию только вызывают, объявление остаётся в low.go |
Дальше: ритуал перед коммитом и словарик — course next