#!/bin/bash # ============================================================================ # MySQL 8.0.24 备份恢复自动化测试脚本 # ============================================================================ # 作者: AI Assistant # 版本: 1.0.0 # 说明: 自动化测试备份和恢复功能 # # 测试流程: # 1. 创建测试数据库和测试数据 # 2. 执行全量备份 # 3. 添加增量数据 # 4. 执行增量备份 # 5. 删除测试数据库 # 6. 从全量备份恢复 # 7. 应用增量备份 # 8. 验证数据完整性 # 9. 清理测试数据 # # 使用方法: # ./test_backup_restore.sh [选项] # # 选项: # --skip-cleanup 测试后不清理数据 # --verbose 显示详细输出 # -h, --help 显示帮助信息 # # 注意: # - 测试会创建临时数据库 'backup_test_db' # - 请确保 MySQL 用户有创建/删除数据库的权限 # - 测试完成后会自动清理 (除非指定 --skip-cleanup) # ============================================================================ set -o pipefail # ---------------------------------------------------------------------------- # 脚本路径和配置加载 # ---------------------------------------------------------------------------- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # 加载配置文件 if [[ ! -f "${SCRIPT_DIR}/config.sh" ]]; then echo "[ERROR] 配置文件不存在: ${SCRIPT_DIR}/config.sh" exit 1 fi source "${SCRIPT_DIR}/config.sh" # 加载公共函数库 if [[ ! -f "${SCRIPT_DIR}/lib/common.sh" ]]; then echo "[ERROR] 公共函数库不存在: ${SCRIPT_DIR}/lib/common.sh" exit 1 fi source "${SCRIPT_DIR}/lib/common.sh" # ---------------------------------------------------------------------------- # 测试配置 # ---------------------------------------------------------------------------- TEST_DB_NAME="backup_test_db" TEST_TABLE_NAME="test_data" TEST_TIMESTAMP=$(date +"${TIMESTAMP_FORMAT}") TEST_LOG_FILE="${LOG_DIR}/test_backup_restore_${TEST_TIMESTAMP}.log" TEST_BACKUP_ROOT="${BACKUP_ROOT_DIR}/test_${TEST_TIMESTAMP}" # 测试统计 TESTS_TOTAL=0 TESTS_PASSED=0 TESTS_FAILED=0 # 选项 SKIP_CLEANUP=false VERBOSE_MODE=false # 临时备份目录 FULL_BACKUP_PATH="" INCR_BACKUP_PATH="" # ---------------------------------------------------------------------------- # 颜色和格式 # ---------------------------------------------------------------------------- BOLD='\033[1m' UNDERLINE='\033[4m' # ---------------------------------------------------------------------------- # 显示帮助信息 # ---------------------------------------------------------------------------- show_help() { cat << EOF MySQL 8.0.24 备份恢复自动化测试脚本 使用方法: $(basename "$0") [选项] 选项: --skip-cleanup 测试后不清理数据 --verbose 显示详细输出 -h, --help 显示此帮助信息 测试流程: 1. 创建测试数据库和测试数据 2. 执行全量备份 3. 添加增量数据 4. 执行增量备份 5. 删除测试数据库 6. 从全量备份恢复 7. 应用增量备份 8. 验证数据完整性 9. 清理测试数据 注意: - 测试会创建临时数据库 'backup_test_db' - 请确保 MySQL 用户有创建/删除数据库的权限 - 建议在测试/开发环境运行,不要在生产环境使用 EOF } # ---------------------------------------------------------------------------- # 解析命令行参数 # ---------------------------------------------------------------------------- parse_args() { while [[ $# -gt 0 ]]; do case "$1" in --skip-cleanup) SKIP_CLEANUP=true shift ;; --verbose) VERBOSE_MODE=true VERBOSE=true shift ;; -h|--help) show_help exit 0 ;; *) die "未知选项: $1\n使用 --help 查看帮助" ;; esac done } # ---------------------------------------------------------------------------- # 测试辅助函数 # ---------------------------------------------------------------------------- # 开始测试用例 start_test() { local test_name="$1" ((TESTS_TOTAL++)) echo "" echo -e "${BOLD}[TEST $TESTS_TOTAL] $test_name${NC}" echo "----------------------------------------" } # 测试通过 test_pass() { local message="${1:-测试通过}" ((TESTS_PASSED++)) echo -e " ${GREEN}✓ PASS${NC}: $message" } # 测试失败 test_fail() { local message="${1:-测试失败}" local detail="${2:-}" ((TESTS_FAILED++)) echo -e " ${RED}✗ FAIL${NC}: $message" if [[ -n "$detail" ]]; then echo -e " ${RED}详情${NC}: $detail" fi } # 测试断言 - 检查命令执行是否成功 assert_success() { local description="$1" shift local command="$@" if [[ "$VERBOSE_MODE" == "true" ]]; then echo " 执行: $command" fi local output local exit_code output=$(eval "$command" 2>&1) exit_code=$? if [[ $exit_code -eq 0 ]]; then test_pass "$description" return 0 else test_fail "$description" "退出码: $exit_code, 输出: $output" return 1 fi } # 测试断言 - 检查值是否相等 assert_equals() { local description="$1" local expected="$2" local actual="$3" if [[ "$expected" == "$actual" ]]; then test_pass "$description (期望: $expected, 实际: $actual)" return 0 else test_fail "$description" "期望: $expected, 实际: $actual" return 1 fi } # 测试断言 - 检查值是否不为空 assert_not_empty() { local description="$1" local value="$2" if [[ -n "$value" ]]; then test_pass "$description" return 0 else test_fail "$description" "值为空" return 1 fi } # 测试断言 - 检查文件是否存在 assert_file_exists() { local description="$1" local file_path="$2" if [[ -f "$file_path" ]]; then test_pass "$description" return 0 else test_fail "$description" "文件不存在: $file_path" return 1 fi } # 测试断言 - 检查目录是否存在 assert_dir_exists() { local description="$1" local dir_path="$2" if [[ -d "$dir_path" ]]; then test_pass "$description" return 0 else test_fail "$description" "目录不存在: $dir_path" return 1 fi } # ---------------------------------------------------------------------------- # 测试准备 # ---------------------------------------------------------------------------- setup_test_environment() { echo "" echo "============================================================" echo -e "${BOLD}MySQL 备份恢复自动化测试${NC}" echo "============================================================" echo "测试时间: $TEST_TIMESTAMP" echo "测试数据库: $TEST_DB_NAME" echo "日志文件: $TEST_LOG_FILE" echo "测试备份目录: $TEST_BACKUP_ROOT" echo "============================================================" # 创建日志目录 ensure_dir "$LOG_DIR" ensure_dir "$TEST_BACKUP_ROOT" ensure_dir "${TEST_BACKUP_ROOT}/full" ensure_dir "${TEST_BACKUP_ROOT}/incremental" # 初始化日志 LOG_FILE="$TEST_LOG_FILE" # 检查必要命令 log_info "检查测试环境..." check_commands "$MYSQLDUMP_PATH" "$MYSQL_PATH" # 检查 MySQL 连接 check_mysql_connection log_info "测试环境准备完成" } # ---------------------------------------------------------------------------- # 测试清理 # ---------------------------------------------------------------------------- cleanup_test_environment() { if [[ "$SKIP_CLEANUP" == "true" ]]; then log_warn "跳过清理 (--skip-cleanup)" log_info "测试数据库: $TEST_DB_NAME" log_info "测试备份目录: $TEST_BACKUP_ROOT" return fi log_info "清理测试环境..." # 删除测试数据库 execute_mysql "DROP DATABASE IF EXISTS \`${TEST_DB_NAME}\`;" 2>/dev/null || true # 删除测试备份目录 if [[ -d "$TEST_BACKUP_ROOT" ]]; then rm -rf "$TEST_BACKUP_ROOT" fi log_info "测试环境清理完成" } # ---------------------------------------------------------------------------- # 测试用例 1: 创建测试数据 # ---------------------------------------------------------------------------- test_create_test_data() { start_test "创建测试数据库和初始数据" # 删除可能存在的旧测试数据库 execute_mysql "DROP DATABASE IF EXISTS \`${TEST_DB_NAME}\`;" 2>/dev/null # 创建测试数据库 assert_success "创建测试数据库" \ "execute_mysql 'CREATE DATABASE \`${TEST_DB_NAME}\`;'" # 创建测试表 local create_table_sql=" USE \`${TEST_DB_NAME}\`; CREATE TABLE \`${TEST_TABLE_NAME}\` ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, value TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, phase VARCHAR(50) DEFAULT 'initial' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; " assert_success "创建测试表" \ "execute_mysql \"$create_table_sql\"" # 插入初始测试数据 local insert_sql=" USE \`${TEST_DB_NAME}\`; INSERT INTO \`${TEST_TABLE_NAME}\` (name, value, phase) VALUES ('record_1', 'Initial data 1', 'full_backup'), ('record_2', 'Initial data 2', 'full_backup'), ('record_3', 'Initial data 3', 'full_backup'), ('record_4', 'Initial data 4', 'full_backup'), ('record_5', 'Initial data 5', 'full_backup'); " assert_success "插入初始测试数据" \ "execute_mysql \"$insert_sql\"" # 验证数据 local count=$(execute_mysql "SELECT COUNT(*) FROM \`${TEST_DB_NAME}\`.\`${TEST_TABLE_NAME}\` WHERE phase='full_backup';") assert_equals "验证初始数据行数" "5" "$count" } # ---------------------------------------------------------------------------- # 测试用例 2: 全量备份 # ---------------------------------------------------------------------------- test_full_backup() { start_test "执行全量备份" # 临时修改配置以使用测试目录 local original_full_dir="$FULL_BACKUP_DIR" export FULL_BACKUP_DIR="${TEST_BACKUP_ROOT}/full" # 执行全量备份 log_info "执行全量备份脚本..." local backup_output backup_output=$("${SCRIPT_DIR}/full_backup.sh" -d "$TEST_DB_NAME" 2>&1) local exit_code=$? if [[ "$VERBOSE_MODE" == "true" ]]; then echo "$backup_output" fi if [[ $exit_code -eq 0 ]]; then test_pass "全量备份脚本执行成功" else test_fail "全量备份脚本执行失败" "退出码: $exit_code" echo "输出: $backup_output" return 1 fi # 查找备份目录 FULL_BACKUP_PATH=$(find "${TEST_BACKUP_ROOT}/full" -maxdepth 1 -type d -name "${BACKUP_PREFIX}_full_*" | sort -r | head -n1) assert_not_empty "备份目录已创建" "$FULL_BACKUP_PATH" # 验证备份文件 local backup_file=$(find "$FULL_BACKUP_PATH" -name "${TEST_DB_NAME}.sql*" -type f 2>/dev/null | head -n1) assert_not_empty "数据库备份文件存在" "$backup_file" # 验证 binlog 位置文件 assert_file_exists "binlog 位置文件存在" "${FULL_BACKUP_PATH}/binlog_position.txt" # 验证元数据文件 assert_file_exists "元数据文件存在" "${FULL_BACKUP_PATH}/metadata.txt" # 恢复原始配置 export FULL_BACKUP_DIR="$original_full_dir" log_info "全量备份目录: $FULL_BACKUP_PATH" } # ---------------------------------------------------------------------------- # 测试用例 3: 添加增量数据 # ---------------------------------------------------------------------------- test_add_incremental_data() { start_test "添加增量数据" # 等待一秒以确保时间戳不同 sleep 1 # 插入增量数据 local insert_sql=" USE \`${TEST_DB_NAME}\`; INSERT INTO \`${TEST_TABLE_NAME}\` (name, value, phase) VALUES ('record_6', 'Incremental data 1', 'incremental'), ('record_7', 'Incremental data 2', 'incremental'), ('record_8', 'Incremental data 3', 'incremental'); UPDATE \`${TEST_TABLE_NAME}\` SET value = 'Updated value' WHERE name = 'record_1'; " assert_success "插入增量测试数据" \ "execute_mysql \"$insert_sql\"" # 验证数据 local total_count=$(execute_mysql "SELECT COUNT(*) FROM \`${TEST_DB_NAME}\`.\`${TEST_TABLE_NAME}\`;") assert_equals "验证总数据行数" "8" "$total_count" local incr_count=$(execute_mysql "SELECT COUNT(*) FROM \`${TEST_DB_NAME}\`.\`${TEST_TABLE_NAME}\` WHERE phase='incremental';") assert_equals "验证增量数据行数" "3" "$incr_count" } # ---------------------------------------------------------------------------- # 测试用例 4: 增量备份 # ---------------------------------------------------------------------------- test_incremental_backup() { start_test "执行增量备份" # 临时修改配置 local original_full_dir="$FULL_BACKUP_DIR" local original_incr_dir="$INCREMENTAL_BACKUP_DIR" export FULL_BACKUP_DIR="${TEST_BACKUP_ROOT}/full" export INCREMENTAL_BACKUP_DIR="${TEST_BACKUP_ROOT}/incremental" # 刷新 binlog (确保有数据可备份) execute_mysql "FLUSH LOGS;" 2>/dev/null || true # 执行增量备份 log_info "执行增量备份脚本..." local backup_output backup_output=$("${SCRIPT_DIR}/incremental_backup.sh" -f "$FULL_BACKUP_PATH" 2>&1) local exit_code=$? if [[ "$VERBOSE_MODE" == "true" ]]; then echo "$backup_output" fi if [[ $exit_code -eq 0 ]]; then test_pass "增量备份脚本执行成功" else # 注意:如果没有新的 binlog 数据,增量备份可能没有实际内容,这不算失败 if echo "$backup_output" | grep -q "没有新的"; then test_pass "增量备份执行成功 (无新数据,这是正常的)" else test_fail "增量备份脚本执行失败" "退出码: $exit_code" echo "输出: $backup_output" fi fi # 查找增量备份目录 INCR_BACKUP_PATH=$(find "${TEST_BACKUP_ROOT}/incremental" -maxdepth 1 -type d -name "${BACKUP_PREFIX}_incr_*" | sort -r | head -n1) if [[ -n "$INCR_BACKUP_PATH" ]]; then test_pass "增量备份目录已创建: $INCR_BACKUP_PATH" else log_warn "增量备份目录未创建 (可能没有增量数据)" fi # 恢复原始配置 export FULL_BACKUP_DIR="$original_full_dir" export INCREMENTAL_BACKUP_DIR="$original_incr_dir" } # ---------------------------------------------------------------------------- # 测试用例 5: 模拟数据丢失 # ---------------------------------------------------------------------------- test_simulate_data_loss() { start_test "模拟数据丢失 (删除测试数据库)" # 记录当前数据状态 local before_count=$(execute_mysql "SELECT COUNT(*) FROM \`${TEST_DB_NAME}\`.\`${TEST_TABLE_NAME}\`;") log_info "删除前数据行数: $before_count" # 删除测试数据库 assert_success "删除测试数据库" \ "execute_mysql 'DROP DATABASE IF EXISTS \`${TEST_DB_NAME}\`;'" # 验证数据库已删除 local db_exists=$(execute_mysql "SHOW DATABASES LIKE '${TEST_DB_NAME}';" 2>/dev/null) if [[ -z "$db_exists" ]]; then test_pass "验证数据库已成功删除" else test_fail "数据库仍然存在" fi } # ---------------------------------------------------------------------------- # 测试用例 6: 从全量备份恢复 # ---------------------------------------------------------------------------- test_restore_full_backup() { start_test "从全量备份恢复" if [[ -z "$FULL_BACKUP_PATH" || ! -d "$FULL_BACKUP_PATH" ]]; then test_fail "全量备份目录不存在" return 1 fi # 创建数据库 execute_mysql "CREATE DATABASE IF NOT EXISTS \`${TEST_DB_NAME}\`;" 2>/dev/null # 执行恢复 log_info "执行全量备份恢复..." local restore_output restore_output=$("${SCRIPT_DIR}/restore_full.sh" -y --no-backup -d "$TEST_DB_NAME" "$FULL_BACKUP_PATH" 2>&1) local exit_code=$? if [[ "$VERBOSE_MODE" == "true" ]]; then echo "$restore_output" fi if [[ $exit_code -eq 0 ]]; then test_pass "全量备份恢复脚本执行成功" else test_fail "全量备份恢复脚本执行失败" "退出码: $exit_code" echo "输出: $restore_output" return 1 fi # 验证恢复后的数据 (应该只有全量备份时的数据) local restored_count=$(execute_mysql "SELECT COUNT(*) FROM \`${TEST_DB_NAME}\`.\`${TEST_TABLE_NAME}\` WHERE phase='full_backup';") assert_equals "验证恢复后的全量数据行数" "5" "$restored_count" local total_count=$(execute_mysql "SELECT COUNT(*) FROM \`${TEST_DB_NAME}\`.\`${TEST_TABLE_NAME}\`;") log_info "恢复后总数据行数: $total_count (全量备份时为 5 行)" } # ---------------------------------------------------------------------------- # 测试用例 7: 应用增量备份 # ---------------------------------------------------------------------------- test_restore_incremental_backup() { start_test "应用增量备份" if [[ -z "$INCR_BACKUP_PATH" || ! -d "$INCR_BACKUP_PATH" ]]; then log_warn "增量备份目录不存在,跳过增量恢复测试" test_pass "跳过增量恢复测试 (没有增量备份)" return 0 fi # 执行增量恢复 log_info "执行增量备份恢复..." local restore_output restore_output=$("${SCRIPT_DIR}/restore_incremental.sh" -y "$INCR_BACKUP_PATH" 2>&1) local exit_code=$? if [[ "$VERBOSE_MODE" == "true" ]]; then echo "$restore_output" fi if [[ $exit_code -eq 0 ]]; then test_pass "增量备份恢复脚本执行成功" else # 增量恢复可能因为没有实际的 binlog 数据而跳过,这不算失败 log_warn "增量备份恢复返回非零退出码: $exit_code" test_pass "增量备份恢复脚本执行完成 (可能无增量数据)" fi # 验证恢复后的数据 local total_count=$(execute_mysql "SELECT COUNT(*) FROM \`${TEST_DB_NAME}\`.\`${TEST_TABLE_NAME}\`;") log_info "增量恢复后总数据行数: $total_count" } # ---------------------------------------------------------------------------- # 测试用例 8: 验证恢复完整性 # ---------------------------------------------------------------------------- test_verify_restore_integrity() { start_test "验证恢复数据完整性" # 检查数据库是否存在 local db_exists=$(execute_mysql "SHOW DATABASES LIKE '${TEST_DB_NAME}';" 2>/dev/null) assert_not_empty "数据库存在" "$db_exists" # 检查表是否存在 local table_exists=$(execute_mysql "SHOW TABLES FROM \`${TEST_DB_NAME}\` LIKE '${TEST_TABLE_NAME}';" 2>/dev/null) assert_not_empty "测试表存在" "$table_exists" # 检查数据行数 local count=$(execute_mysql "SELECT COUNT(*) FROM \`${TEST_DB_NAME}\`.\`${TEST_TABLE_NAME}\`;") if [[ $count -ge 5 ]]; then test_pass "数据行数验证通过 (至少恢复了全量备份的 5 行数据)" else test_fail "数据行数不正确" "期望至少 5 行,实际 $count 行" fi # 检查特定数据 local record1=$(execute_mysql "SELECT name FROM \`${TEST_DB_NAME}\`.\`${TEST_TABLE_NAME}\` WHERE name='record_1';") assert_equals "验证特定记录存在" "record_1" "$record1" log_info "数据完整性验证通过" } # ---------------------------------------------------------------------------- # 测试用例 9: 备份脚本选项测试 # ---------------------------------------------------------------------------- test_backup_script_options() { start_test "备份脚本选项和帮助信息" # 测试全量备份帮助 local full_help=$("${SCRIPT_DIR}/full_backup.sh" --help 2>&1) if echo "$full_help" | grep -q "全量备份脚本"; then test_pass "全量备份脚本帮助信息正确" else test_fail "全量备份脚本帮助信息错误" fi # 测试增量备份帮助 local incr_help=$("${SCRIPT_DIR}/incremental_backup.sh" --help 2>&1) if echo "$incr_help" | grep -q "增量备份脚本"; then test_pass "增量备份脚本帮助信息正确" else test_fail "增量备份脚本帮助信息错误" fi # 测试恢复脚本帮助 local restore_help=$("${SCRIPT_DIR}/restore_full.sh" --help 2>&1) if echo "$restore_help" | grep -q "恢复脚本"; then test_pass "全量恢复脚本帮助信息正确" else test_fail "全量恢复脚本帮助信息错误" fi # 测试增量恢复脚本帮助 local incr_restore_help=$("${SCRIPT_DIR}/restore_incremental.sh" --help 2>&1) if echo "$incr_restore_help" | grep -q "增量备份恢复脚本"; then test_pass "增量恢复脚本帮助信息正确" else test_fail "增量恢复脚本帮助信息错误" fi } # ---------------------------------------------------------------------------- # 打印测试报告 # ---------------------------------------------------------------------------- print_test_report() { echo "" echo "============================================================" echo -e "${BOLD}测试报告${NC}" echo "============================================================" echo "" echo "测试总数: $TESTS_TOTAL" echo -e "通过: ${GREEN}$TESTS_PASSED${NC}" echo -e "失败: ${RED}$TESTS_FAILED${NC}" echo "" if [[ $TESTS_FAILED -eq 0 ]]; then echo -e "${GREEN}${BOLD}所有测试通过!${NC}" echo "" return 0 else echo -e "${RED}${BOLD}有 $TESTS_FAILED 个测试失败${NC}" echo "" echo "请检查日志文件: $TEST_LOG_FILE" return 1 fi } # ---------------------------------------------------------------------------- # 主函数 # ---------------------------------------------------------------------------- main() { local start_time=$(date +%s) # 解析参数 parse_args "$@" # 设置测试环境 setup_test_environment # 错误处理 - 确保测试失败时也能清理 trap 'cleanup_test_environment' EXIT echo "" echo "开始执行测试用例..." echo "" # 运行测试用例 test_create_test_data test_full_backup test_add_incremental_data test_incremental_backup test_simulate_data_loss test_restore_full_backup test_restore_incremental_backup test_verify_restore_integrity test_backup_script_options # 打印测试报告 print_test_report local result=$? # 计算耗时 local end_time=$(date +%s) local duration=$(calculate_duration "$start_time" "$end_time") echo "测试耗时: $duration" echo "" return $result } # 执行主函数 main "$@"