Bash-скриптинг для QA
Updated Jul 2026
Bash — это клей, связывающий CI/CD-пайплайны, тестовые окружения и рабочие процессы автоматизации. Вам не нужно быть экспертом по shell-скриптингу, но вы должны уверенно писать скрипты для настройки тестовых окружений, запуска тест-сьютов и обработки результатов.
Базовый шаблон скрипта
Каждый bash-скрипт должен начинаться с этого:
#!/bin/bash
set -euo pipefail
| Флаг | Значение | Почему это важно |
|---|---|---|
set -e |
Завершение при любой ошибке | Предотвращает тихое продолжение скрипта после сбоя |
set -u |
Ошибка при неопределённых переменных | Ловит опечатки в именах переменных |
set -o pipefail |
Конвейер падает, если любая команда в нём падает | `cmd1 |
Без этих флагов скрипт может упасть незаметно — сброс базы данных не удался, но тесты всё равно запускаются на устаревших данных.
Скрипт настройки тестового окружения
#!/bin/bash
set -euo pipefail
echo "=== Resetting test database ==="
psql -h localhost -U testuser -d testdb -f reset.sql
echo "=== Starting test server ==="
npm run start:test &
SERVER_PID=$!
# Wait for server to be ready (up to 30 seconds)
echo "=== Waiting for server ==="
for i in {1..30}; do
if curl -s http://localhost:3000/health > /dev/null 2>&1; then
echo "Server ready after ${i}s"
break
fi
if [ $i -eq 30 ]; then
echo "ERROR: Server did not start within 30s"
kill $SERVER_PID 2>/dev/null || true
exit 1
fi
sleep 1
done
echo "=== Running tests ==="
pytest tests/ --junitxml=results.xml
TEST_EXIT=$?
echo "=== Stopping server ==="
kill $SERVER_PID 2>/dev/null || true
echo "=== Done (exit code: $TEST_EXIT) ==="
exit $TEST_EXIT
Ключевые паттерны в этом скрипте
- Фоновый процесс (
&и$!): запуск сервера в фоне и захват его PID - Цикл проверки здоровья: ожидание готовности сервера перед запуском тестов
- Захват кода выхода (
$?): сохранение кода выхода тестов перед очисткой - Очистка при завершении: остановка сервера независимо от результата тестов
- Корректная обработка ошибок:
kill $PID 2>/dev/null || trueне падает, если процесс уже завершён
Переменные окружения
Переменные окружения параметризуют запуски тестов для разных окружений.
#!/bin/bash
set -euo pipefail
# Default values with fallback
BASE_URL="${API_BASE_URL:-http://localhost:3000}"
DB_HOST="${TEST_DB_HOST:-localhost}"
WORKERS="${TEST_WORKERS:-4}"
echo "Running tests against: $BASE_URL"
echo "Database: $DB_HOST"
echo "Workers: $WORKERS"
API_BASE_URL="$BASE_URL" pytest tests/ -n "$WORKERS"
# Run against different environments
API_BASE_URL=https://staging.example.com ./run_tests.sh
API_BASE_URL=https://production.example.com TEST_WORKERS=1 ./run_tests.sh
Полезные конструкции Bash
Условное выполнение
# Run smoke tests first, only run full suite if smoke passes
pytest tests/smoke/ && pytest tests/full/
# Run cleanup regardless of test outcome
pytest tests/ || true
./cleanup.sh
Операции с файлами и директориями
# Create test output directory
mkdir -p test-results/screenshots
# Check if file exists
if [ -f "test-data.sql" ]; then
psql testdb < test-data.sql
else
echo "WARNING: test-data.sql not found"
fi
# Clean up old test artifacts
find test-results/ -name "*.png" -mtime +7 -delete
Циклы и параллельное выполнение
# Run tests for each environment
for env in staging production; do
echo "Testing $env..."
API_BASE_URL="https://${env}.example.com" pytest tests/smoke/ \
--junitxml="results-${env}.xml"
done
# Parallel execution with xargs
echo "staging production sandbox" | tr ' ' '\n' | \
xargs -P 3 -I {} bash -c 'API_BASE_URL="https://{}.example.com" pytest tests/smoke/'
Обработка строк
# Extract test count from pytest output
RESULT=$(pytest tests/ --tb=no 2>&1 | tail -1)
echo "Result: $RESULT"
# "5 passed, 2 failed in 12.3s"
# Check if tests failed
if echo "$RESULT" | grep -q "failed"; then
echo "TESTS FAILED"
exit 1
fi
Скрипты для CI/CD-пайплайнов
Вспомогательный скрипт для GitHub Actions
#!/bin/bash
set -euo pipefail
# Parse command line arguments
SUITE="${1:-smoke}"
ENVIRONMENT="${2:-staging}"
echo "Running $SUITE tests against $ENVIRONMENT"
# Set environment-specific variables
case "$ENVIRONMENT" in
staging)
export API_BASE_URL="https://api.staging.example.com"
export DB_HOST="staging-db.internal"
;;
production)
export API_BASE_URL="https://api.example.com"
export DB_HOST="prod-db.internal"
;;
*)
echo "Unknown environment: $ENVIRONMENT"
exit 1
;;
esac
# Run the appropriate test suite
case "$SUITE" in
smoke)
pytest tests/ -m smoke --junitxml=results.xml
;;
full)
pytest tests/ --junitxml=results.xml -n 4
;;
api)
pytest tests/api/ --junitxml=results.xml
;;
*)
echo "Unknown suite: $SUITE"
exit 1
;;
esac
Запуск тестов на основе Docker
#!/bin/bash
set -euo pipefail
echo "=== Building test image ==="
docker build -t test-runner -f Dockerfile.test .
echo "=== Starting dependencies ==="
docker compose -f docker-compose.test.yml up -d db redis
echo "=== Waiting for database ==="
for i in {1..30}; do
if docker compose -f docker-compose.test.yml exec -T db pg_isready; then
break
fi
sleep 1
done
echo "=== Running tests ==="
docker run --rm \
--network test-network \
-e API_BASE_URL=http://app:3000 \
-e DB_HOST=db \
-v "$(pwd)/test-results:/app/test-results" \
test-runner pytest tests/ --junitxml=/app/test-results/results.xml
TEST_EXIT=$?
echo "=== Cleaning up ==="
docker compose -f docker-compose.test.yml down
exit $TEST_EXIT
Trap для очистки
Гарантируйте выполнение очистки даже при сбое скрипта:
#!/bin/bash
set -euo pipefail
cleanup() {
echo "Cleaning up..."
kill $SERVER_PID 2>/dev/null || true
docker compose down 2>/dev/null || true
}
# Register cleanup to run on EXIT, regardless of how the script ends
trap cleanup EXIT
# Now start things up — cleanup is guaranteed to run
docker compose up -d
SERVER_PID=$!
pytest tests/
Справочник по основным конструкциям Bash
| Концепция | Синтаксис | Почему это важно |
|---|---|---|
set -euo pipefail |
Заголовок скрипта | Ловит ошибки вместо тихого продолжения |
$? |
Код выхода последней команды | Проверка успешности инструмента перед продолжением |
$! |
PID последнего фонового процесса | Отслеживание и завершение фоновых серверов |
${VAR:-default} |
Значение по умолчанию | Параметризация без обязательности переменной |
command & |
Запуск в фоне | Запуск серверов без блокировки |
trap cleanup EXIT |
Запуск при завершении | Гарантированная очистка |
xargs -P N |
Параллельное выполнение | Запуск повторяющихся задач параллельно |
2>/dev/null |
Подавление stderr | Скрытие ожидаемых сообщений об ошибках |
| ` | true` |
Практическое упражнение
Напишите bash-скрипт, который:
- Принимает имя окружения как аргумент командной строки (staging/production)
- Запускает локальную базу данных с помощью Docker
- Ожидает готовности базы данных (цикл проверки здоровья)
- Запускает дымовые тесты с pytest
- Захватывает код выхода тестов
- Очищает Docker-контейнеры (с помощью trap)
- Завершается с кодом выхода тестов
Ключевые выводы
- Всегда начинайте скрипты с
set -euo pipefailдля отлова ошибок - Используйте переменные окружения для параметризации между окружениями
- Циклы проверки здоровья предотвращают запуск тестов на неготовых сервисах
- Захватывайте коды выхода тестов перед очисткой
- Используйте
trap cleanup EXITдля гарантии выполнения очистки - Bash — это клей между инструментами; изучите достаточно, чтобы оркестрировать вашу тестовую инфраструктуру
Тезис для собеседования: «Я пишу автоматизацию тестирования на Python и TypeScript. Я использую ООП для структуры фреймворка — объекты страниц с предпочтением композиции над наследованием — и функциональные паттерны вроде map/filter для преобразования данных. Я уверенно владею async/await, регулярными выражениями для парсинга логов и Bash-скриптингом для задач CI-пайплайнов. Программирование для меня — не второстепенный навык, а способ создания надёжной, поддерживаемой тестовой инфраструктуры.»