跳转至

数据库设计测试用例

测试目标: 验证数据库设计的正确性和性能 测试类型: 设计测试、范式测试、约束测试、性能测试 涉及组件: 表设计、索引设计、约束设计、关系设计


📋 测试概述

测试目标

  1. 设计测试: 验证数据库设计的合理性
  2. 范式测试: 验证数据库范式符合性
  3. 约束测试: 验证约束的正确性
  4. 性能测试: 验证设计的性能

测试环境

  • 数据库: MySQL 8.0+, PostgreSQL 13+
  • 测试框架: pytest
  • 设计工具: ER图工具

🧪 测试用例列表

1. 表设计测试

测试用例1.1: 表创建

测试目标: 验证表创建的正确性

测试代码:

Python
import pytest
import mysql.connector
from mysql.connector import Error

def test_table_creation():
    """测试表创建"""
    conn = mysql.connector.connect(
        host='localhost',
        user='test_user',
        password='test_password',
        database='test_db'
    )

    cursor = conn.cursor(dictionary=True)

    try:
        # 创建表
        create_table_query = """
        CREATE TABLE users (
            id INT AUTO_INCREMENT PRIMARY KEY,
            name VARCHAR(100) NOT NULL,
            email VARCHAR(255) NOT NULL UNIQUE,
            age INT CHECK (age >= 0 AND age <= 150),
            status ENUM('active', 'inactive', 'pending') DEFAULT 'active',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """

        cursor.execute(create_table_query)

        # 验证表创建
        cursor.execute("SHOW TABLES LIKE 'users'")
        result = cursor.fetchone()

        assert result is not None, "表创建失败"
        assert 'users' in result

        # 验证表结构
        cursor.execute("DESCRIBE users")
        columns = cursor.fetchall()

        column_names = [col['Field'] for col in columns]
        assert 'id' in column_names
        assert 'name' in column_names
        assert 'email' in column_names
        assert 'age' in column_names
        assert 'status' in column_names
        assert 'created_at' in column_names
        assert 'updated_at' in column_names

        print("✓ 表创建测试通过")

    finally:
        cursor.execute("DROP TABLE IF EXISTS users")
        conn.commit()
        cursor.close()
        conn.close()

预期结果: 表成功创建,结构正确


测试用例1.2: 数据类型

测试目标: 验证数据类型选择

测试代码:

Python
def test_data_types():
    """测试数据类型"""
    conn = mysql.connector.connect(
        host='localhost',
        user='test_user',
        password='test_password',
        database='test_db'
    )

    cursor = conn.cursor()

    try:
        # 创建包含各种数据类型的表
        create_table_query = """
        CREATE TABLE test_types (
            id INT AUTO_INCREMENT PRIMARY KEY,
            int_col INT,
            varchar_col VARCHAR(255),
            text_col TEXT,
            decimal_col DECIMAL(10, 2),
            date_col DATE,
            datetime_col DATETIME,
            boolean_col BOOLEAN,
            json_col JSON
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """

        cursor.execute(create_table_query)

        # 插入测试数据
        insert_query = """
        INSERT INTO test_types (
            int_col, varchar_col, text_col, decimal_col,
            date_col, datetime_col, boolean_col, json_col
        ) VALUES (
            100, 'Test', 'Long text content', 123.45,
            '2024-01-01', '2024-01-01 12:00:00', TRUE,
            '{"key": "value"}'
        )
        """

        cursor.execute(insert_query)
        conn.commit()

        # 验证数据插入
        cursor.execute("SELECT * FROM test_types")
        result = cursor.fetchone()

        assert result[1] == 100  # int_col
        assert result[2] == 'Test'  # varchar_col
        assert result[3] == 'Long text content'  # text_col
        assert abs(result[4] - 123.45) < 0.01  # decimal_col
        assert result[5] == '2024-01-01'  # date_col
        assert result[6] is not None  # datetime_col
        assert result[7] == 1  # boolean_col
        assert result[8] is not None  # json_col

        print("✓ 数据类型测试通过")

    finally:
        cursor.execute("DROP TABLE IF EXISTS test_types")
        conn.commit()
        cursor.close()
        conn.close()

预期结果: 所有数据类型正确存储和检索


2. 范式测试

测试用例2.1: 第一范式(1NF)

测试目标: 验证第一范式

测试代码:

Python
def test_first_normal_form():
    """测试第一范式"""
    conn = mysql.connector.connect(
        host='localhost',
        user='test_user',
        password='test_password',
        database='test_db'
    )

    cursor = conn.cursor()

    try:
        # 创建符合1NF的表
        create_table_query = """
        CREATE TABLE orders (
            id INT AUTO_INCREMENT PRIMARY KEY,
            user_id INT NOT NULL,
            product_id INT NOT NULL,
            quantity INT NOT NULL,
            price DECIMAL(10, 2) NOT NULL,
            order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id),
            FOREIGN KEY (product_id) REFERENCES products(id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """

        cursor.execute(create_table_query)

        # 验证原子性
        # 每个字段都是原子的,不可再分
        cursor.execute("DESCRIBE orders")
        columns = cursor.fetchall()

        # 验证没有重复组
        # 1NF要求没有重复的列组
        # 这里简化验证,实际应该检查所有列的组合

        print("✓ 第一范式测试通过")

    finally:
        cursor.execute("DROP TABLE IF EXISTS orders")
        conn.commit()
        cursor.close()
        conn.close()

预期结果: 表符合第一范式


测试用例2.2: 第二范式(2NF)

测试目标: 验证第二范式

测试代码:

Python
def test_second_normal_form():
    """测试第二范式"""
    conn = mysql.connector.connect(
        host='localhost',
        user='test_user',
        password='test_password',
        database='test_db'
    )

    cursor = conn.cursor()

    try:
        # 创建符合2NF的表
        # 2NF要求:1) 符合1NF 2) 所有非主属性完全依赖于主键

        create_table_query = """
        CREATE TABLE order_items (
            id INT AUTO_INCREMENT PRIMARY KEY,
            order_id INT NOT NULL,
            product_id INT NOT NULL,
            quantity INT NOT NULL,
            unit_price DECIMAL(10, 2) NOT NULL,
            FOREIGN KEY (order_id) REFERENCES orders(id),
            FOREIGN KEY (product_id) REFERENCES products(id),
            UNIQUE KEY (order_id, product_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """

        cursor.execute(create_table_query)

        # 验证完全依赖
        # 所有非主属性都完全依赖于主键
        # quantity和unit_price依赖于order_id和product_id的组合

        print("✓ 第二范式测试通过")

    finally:
        cursor.execute("DROP TABLE IF EXISTS order_items")
        conn.commit()
        cursor.close()
        conn.close()

预期结果: 表符合第二范式


测试用例2.3: 第三范式(3NF)

测试目标: 验证第三范式

测试代码:

Python
def test_third_normal_form():
    """测试第三范式"""
    conn = mysql.connector.connect(
        host='localhost',
        user='test_user',
        password='test_password',
        database='test_db'
    )

    cursor = conn.cursor()

    try:
        # 创建符合3NF的表
        # 3NF要求:1) 符合2NF 2) 没有传递依赖

        # 用户表
        create_users_query = """
        CREATE TABLE users (
            id INT AUTO_INCREMENT PRIMARY KEY,
            name VARCHAR(100) NOT NULL,
            email VARCHAR(255) NOT NULL UNIQUE,
            city_id INT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (city_id) REFERENCES cities(id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """

        # 城市表
        create_cities_query = """
        CREATE TABLE cities (
            id INT AUTO_INCREMENT PRIMARY KEY,
            name VARCHAR(100) NOT NULL,
            country VARCHAR(100) NOT NULL
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """

        cursor.execute(create_cities_query)
        cursor.execute(create_users_query)

        # 验证无传递依赖
        # 用户表中的city_id依赖于id,而不是name
        # 避免了用户->城市->国家的传递依赖

        print("✓ 第三范式测试通过")

    finally:
        cursor.execute("DROP TABLE IF EXISTS users")
        cursor.execute("DROP TABLE IF EXISTS cities")
        conn.commit()
        cursor.close()
        conn.close()

预期结果: 表符合第三范式


3. 约束测试

测试用例3.1: 主键约束

测试目标: 验证主键约束

测试代码:

Python
def test_primary_key_constraint():
    """测试主键约束"""
    conn = mysql.connector.connect(
        host='localhost',
        user='test_user',
        password='test_password',
        database='test_db'
    )

    cursor = conn.cursor()

    try:
        # 创建带主键的表
        create_table_query = """
        CREATE TABLE users (
            id INT AUTO_INCREMENT PRIMARY KEY,
            name VARCHAR(100) NOT NULL,
            email VARCHAR(255) NOT NULL
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """

        cursor.execute(create_table_query)

        # 插入数据
        cursor.execute("INSERT INTO users (name, email) VALUES ('User 1', 'user1@example.com')")
        conn.commit()

        # 尝试插入重复主键
        try:
            cursor.execute("INSERT INTO users (id, name, email) VALUES (1, 'User 2', 'user2@example.com')")
            conn.commit()
            assert False, "主键约束未生效"
        except Error as e:
            assert e.errno == 1062, "错误代码不正确"

        print("✓ 主键约束测试通过")

    finally:
        cursor.execute("DROP TABLE IF EXISTS users")
        conn.commit()
        cursor.close()
        conn.close()

预期结果: 主键约束正确阻止重复


测试用例3.2: 外键约束

测试目标: 验证外键约束

测试代码:

Python
def test_foreign_key_constraint():
    """测试外键约束"""
    conn = mysql.connector.connect(
        host='localhost',
        user='test_user',
        password='test_password',
        database='test_db'
    )

    cursor = conn.cursor()

    try:
        # 创建父表
        create_users_query = """
        CREATE TABLE users (
            id INT AUTO_INCREMENT PRIMARY KEY,
            name VARCHAR(100) NOT NULL
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """

        # 创建子表
        create_orders_query = """
        CREATE TABLE orders (
            id INT AUTO_INCREMENT PRIMARY KEY,
            user_id INT NOT NULL,
            amount DECIMAL(10, 2) NOT NULL,
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """

        cursor.execute(create_users_query)
        cursor.execute(create_orders_query)

        # 插入用户
        cursor.execute("INSERT INTO users (name) VALUES ('User 1')")
        conn.commit()

        # 插入有效订单
        cursor.execute("INSERT INTO orders (user_id, amount) VALUES (1, 100.00)")
        conn.commit()

        # 尝试插入无效外键
        try:
            cursor.execute("INSERT INTO orders (user_id, amount) VALUES (999, 200.00)")
            conn.commit()
            assert False, "外键约束未生效"
        except Error as e:
            assert e.errno == 1452, "错误代码不正确"

        # 测试级联删除
        cursor.execute("DELETE FROM users WHERE id = 1")
        conn.commit()

        cursor.execute("SELECT COUNT(*) FROM orders WHERE user_id = 1")
        result = cursor.fetchone()
        assert result[0] == 0, "级联删除未生效"

        print("✓ 外键约束测试通过")

    finally:
        cursor.execute("DROP TABLE IF EXISTS orders")
        cursor.execute("DROP TABLE IF EXISTS users")
        conn.commit()
        cursor.close()
        conn.close()

预期结果: 外键约束正确工作


测试用例3.3: 唯一约束

测试目标: 验证唯一约束

测试代码:

Python
def test_unique_constraint():
    """测试唯一约束"""
    conn = mysql.connector.connect(
        host='localhost',
        user='test_user',
        password='test_password',
        database='test_db'
    )

    cursor = conn.cursor()

    try:
        # 创建带唯一约束的表
        create_table_query = """
        CREATE TABLE users (
            id INT AUTO_INCREMENT PRIMARY KEY,
            email VARCHAR(255) NOT NULL UNIQUE,
            username VARCHAR(50) UNIQUE,
            name VARCHAR(100) NOT NULL
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """

        cursor.execute(create_table_query)

        # 插入数据
        cursor.execute("INSERT INTO users (email, username, name) VALUES ('user1@example.com', 'user1', 'User 1')")
        conn.commit()

        # 尝试插入重复email
        try:
            cursor.execute("INSERT INTO users (email, username, name) VALUES ('user1@example.com', 'user2', 'User 2')")
            conn.commit()
            assert False, "唯一约束未生效"
        except Error as e:
            assert e.errno == 1062, "错误代码不正确"

        # 尝试插入重复username
        try:
            cursor.execute("INSERT INTO users (email, username, name) VALUES ('user2@example.com', 'user1', 'User 3')")
            conn.commit()
            assert False, "唯一约束未生效"
        except Error as e:
            assert e.errno == 1062, "错误代码不正确"

        print("✓ 唯一约束测试通过")

    finally:
        cursor.execute("DROP TABLE IF EXISTS users")
        conn.commit()
        cursor.close()
        conn.close()

预期结果: 唯一约束正确阻止重复


4. 索引设计测试

测试用例4.1: 索引创建

测试目标: 验证索引创建

测试代码:

Python
def test_index_creation():
    """测试索引创建"""
    conn = mysql.connector.connect(
        host='localhost',
        user='test_user',
        password='test_password',
        database='test_db'
    )

    cursor = conn.cursor()

    try:
        # 创建表
        create_table_query = """
        CREATE TABLE users (
            id INT AUTO_INCREMENT PRIMARY KEY,
            name VARCHAR(100) NOT NULL,
            email VARCHAR(255) NOT NULL,
            status VARCHAR(20) NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """

        cursor.execute(create_table_query)

        # 创建索引
        # 普通索引
        cursor.execute("CREATE INDEX idx_email ON users(email)")

        # 复合索引
        cursor.execute("CREATE INDEX idx_status_created ON users(status, created_at)")

        # 唯一索引
        cursor.execute("CREATE UNIQUE INDEX idx_name ON users(name)")

        # 插入测试数据(使用参数化查询)
        for i in range(100):
            cursor.execute(
                "INSERT INTO users (name, email, status) VALUES (%s, %s, %s)",
                (f'User {i}', f'user{i}@example.com', 'active')
            )

        conn.commit()

        # 验证索引存在
        cursor.execute("SHOW INDEX FROM users")
        indexes = cursor.fetchall()

        index_names = [idx['Key_name'] for idx in indexes]
        assert 'idx_email' in index_names  # assert断言:条件为False时抛出AssertionError
        assert 'idx_status_created' in index_names
        assert 'idx_name' in index_names

        # 验证索引使用
        cursor.execute("EXPLAIN SELECT * FROM users WHERE email = 'user50@example.com'")
        result = cursor.fetchone()

        # 应该使用idx_email索引
        assert 'idx_email' in result['Extra'] or result['key'] == 'idx_email'

        print("✓ 索引创建测试通过")

    finally:
        cursor.execute("DROP TABLE IF EXISTS users")
        conn.commit()
        cursor.close()
        conn.close()

预期结果: 索引成功创建并被使用


测试用例4.2: 索引性能

测试目标: 验证索引性能

测试代码:

Python
import time

def test_index_performance():
    """测试索引性能"""
    conn = mysql.connector.connect(
        host='localhost',
        user='test_user',
        password='test_password',
        database='test_db'
    )

    cursor = conn.cursor()

    try:
        # 创建表
        create_table_query = """
        CREATE TABLE users (
            id INT AUTO_INCREMENT PRIMARY KEY,
            name VARCHAR(100) NOT NULL,
            email VARCHAR(255) NOT NULL,
            status VARCHAR(20) NOT NULL
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """

        cursor.execute(create_table_query)

        # 插入大量数据(使用参数化查询)
        for i in range(10000):
            cursor.execute(
                "INSERT INTO users (name, email, status) VALUES (%s, %s, %s)",
                (f'User {i}', f'user{i}@example.com', 'active')
            )

        conn.commit()

        # 测试无索引查询
        start_time = time.time()
        cursor.execute("SELECT * FROM users WHERE email = 'user5000@example.com'")
        result = cursor.fetchone()
        no_index_time = time.time() - start_time

        # 创建索引
        cursor.execute("CREATE INDEX idx_email ON users(email)")
        conn.commit()

        # 测试有索引查询
        start_time = time.time()
        cursor.execute("SELECT * FROM users WHERE email = 'user5000@example.com'")
        result = cursor.fetchone()
        with_index_time = time.time() - start_time

        # 验证索引提升性能
        assert with_index_time < no_index_time, "索引未提升性能"

        print(f"无索引查询时间: {no_index_time:.4f}s")
        print(f"有索引查询时间: {with_index_time:.4f}s")
        print(f"性能提升: {(no_index_time - with_index_time) / no_index_time * 100:.2f}%")

        print("✓ 索引性能测试通过")

    finally:
        cursor.execute("DROP TABLE IF EXISTS users")
        conn.commit()
        cursor.close()
        conn.close()

预期结果: 索引显著提升查询性能


5. 关系设计测试

测试用例5.1: 一对一关系

测试目标: 验证一对一关系

测试代码:

Python
def test_one_to_one_relationship():
    """测试一对一关系"""
    conn = mysql.connector.connect(
        host='localhost',
        user='test_user',
        password='test_password',
        database='test_db'
    )

    cursor = conn.cursor()

    try:
        # 用户表
        create_users_query = """
        CREATE TABLE users (
            id INT AUTO_INCREMENT PRIMARY KEY,
            name VARCHAR(100) NOT NULL,
            email VARCHAR(255) NOT NULL UNIQUE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """

        # 用户详情表
        create_user_profiles_query = """
        CREATE TABLE user_profiles (
            id INT AUTO_INCREMENT PRIMARY KEY,
            user_id INT NOT NULL UNIQUE,
            bio TEXT,
            avatar_url VARCHAR(255),
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """

        cursor.execute(create_users_query)
        cursor.execute(create_user_profiles_query)

        # 插入数据
        cursor.execute("INSERT INTO users (name, email) VALUES ('User 1', 'user1@example.com')")
        conn.commit()

        cursor.execute("INSERT INTO user_profiles (user_id, bio, avatar_url) VALUES (1, 'Bio text', 'http://example.com/avatar.png')")
        conn.commit()

        # 验证一对一关系
        # 每个用户只能有一个profile
        # 每个profile只能属于一个用户

        cursor.execute("SELECT COUNT(*) FROM user_profiles WHERE user_id = 1")
        result = cursor.fetchone()
        assert result[0] == 1, "一对一关系未正确实现"

        print("✓ 一对一关系测试通过")

    finally:
        cursor.execute("DROP TABLE IF EXISTS user_profiles")
        cursor.execute("DROP TABLE IF EXISTS users")
        conn.commit()
        cursor.close()
        conn.close()

预期结果: 一对一关系正确实现


测试用例5.2: 一对多关系

测试目标: 验证一对多关系

测试代码:

Python
def test_one_to_many_relationship():
    """测试一对多关系"""
    conn = mysql.connector.connect(
        host='localhost',
        user='test_user',
        password='test_password',
        database='test_db'
    )

    cursor = conn.cursor()

    try:  # try/except捕获异常
        # 用户表
        create_users_query = """
        CREATE TABLE users (
            id INT AUTO_INCREMENT PRIMARY KEY,
            name VARCHAR(100) NOT NULL,
            email VARCHAR(255) NOT NULL UNIQUE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """

        # 订单表
        create_orders_query = """
        CREATE TABLE orders (
            id INT AUTO_INCREMENT PRIMARY KEY,
            user_id INT NOT NULL,
            amount DECIMAL(10, 2) NOT NULL,
            order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """

        cursor.execute(create_users_query)
        cursor.execute(create_orders_query)

        # 插入数据
        cursor.execute("INSERT INTO users (name, email) VALUES ('User 1', 'user1@example.com')")
        conn.commit()

        # 一个用户可以有多个订单(使用参数化查询)
        for i in range(5):
            cursor.execute(
                "INSERT INTO orders (user_id, amount) VALUES (%s, %s)",
                (1, 100.00 + i * 10)
            )

        conn.commit()

        # 验证一对多关系
        # 一个用户可以有多个订单
        cursor.execute("SELECT COUNT(*) FROM orders WHERE user_id = 1")
        result = cursor.fetchone()
        assert result[0] == 5, "一对多关系未正确实现"

        # 一个订单只能属于一个用户
        cursor.execute("SELECT user_id FROM orders WHERE id = 1")
        result = cursor.fetchone()
        assert result[0] == 1, "外键关系未正确"

        print("✓ 一对多关系测试通过")

    finally:
        cursor.execute("DROP TABLE IF EXISTS orders")
        cursor.execute("DROP TABLE IF EXISTS users")
        conn.commit()
        cursor.close()
        conn.close()

预期结果: 一对多关系正确实现


📊 测试执行

运行所有测试

Bash
# 运行所有测试
pytest tests/test_database_design.py -v

# 运行特定测试
pytest tests/test_database_design.py::test_table_creation -v

# 生成覆盖率报告
pytest tests/test_database_design.py --cov=database_design --cov-report=html

✅ 验证方法

1. 自动化验证

  • 运行所有测试用例
  • 检查断言是否通过
  • 记录测试结果

2. 设计审查

  • ER图审查
  • 范式检查
  • 约束验证
  • 性能评估

3. 文档验证

  • 设计文档完整性
  • 数据字典准确性
  • 关系图正确性

📝 测试报告

测试报告应包含:

  1. 测试概览
  2. 测试用例数量
  3. 通过/失败统计
  4. 设计评分

  5. 详细结果

  6. 每个测试用例的结果
  7. 设计问题列表
  8. 改进建议

  9. 问题分析

  10. 设计缺陷分析
  11. 性能瓶颈分析
  12. 优化建议

测试完成标准: 所有测试用例通过 推荐测试频率: 每次数据库设计更新 测试维护周期: 每周