Егор, функции в bash есть, и тестируются так же, как везде: файл с функциями отдельно, запускающий скрипт отдельно.
# razbor.sh, только функции, сам ничего не запускает
vytashit_ostatok() {
printf '%s' "$1" | python3 -c 'import sys,re; m=re.search(r"data-ostatok=\"(\d+)\"", sys.stdin.read()); print(m.group(1) if m else "")'
}
# test_razbor.sh
. ./razbor.sh
proverit() {
got=$(vytashit_ostatok "$2")
if [ "$got" = "$3" ]; then echo "ok $1"; else echo "FAIL $1: ждал '$3', получил '$got'"; fi
}
proverit "обычная карточка" "$(cat obrazcy/karta.html)" "42"
proverit "поля нет" "<div></div>" ""
Плюс к этому две вещи, которые дают больше половины пользы от всех тестов вместе. Первая: set -euo pipefail в начале каждого скрипта, тогда упавшая команда посреди конвейера не утащит прогон дальше молча. Вторая: shellcheck на весь файл, он ловит незакавыченные переменные и подстановки, которые разваливаются на пробеле в пути.
У меня оба пункта висят в хуке перед отправкой в репозиторий, и поломок они поймали больше, чем написанные руками тесты.