← 返回首页
集成

集成测试与端到端测试

📂 python ⏱ 4 min 767 words

集成测试与端到端测试

集成测试验证组件之间的协作,端到端测试验证整个系统流程。本文将介绍测试数据库、API测试和浏览器自动化测试。

测试数据库配置

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.models import Base, User, Product

@pytest.fixture(scope="session")
def engine():
    """创建测试数据库引擎"""
    engine = create_engine("sqlite:///test.db")
    Base.metadata.create_all(engine)
    yield engine
    Base.metadata.drop_all(engine)
    engine.dispose()

@pytest.fixture(scope="function")
def db_session(engine):
    """每个测试函数使用独立会话"""
    Session = sessionmaker(bind=engine)
    session = Session()
    yield session
    session.rollback()
    session.close()

@pytest.fixture(scope="session")
def test_data():
    """测试数据工厂"""
    return {
        "users": [
            {"id": 1, "name": "Alice", "email": "alice@test.com"},
            {"id": 2, "name": "Bob", "email": "bob@test.com"},
        ],
        "products": [
            {"id": 1, "name": "Laptop", "price": 999.99},
            {"id": 2, "name": "Phone", "price": 599.99},
        ]
    }

# 使用Docker进行集成测试
@pytest.fixture(scope="session")
def docker_postgres():
    """使用Docker运行PostgreSQL"""
    import docker
    client = docker.from_env()
    
    container = client.containers.run(
        "postgres:15",
        environment={
            "POSTGRES_DB": "testdb",
            "POSTGRES_USER": "test",
            "POSTGRES_PASSWORD": "test"
        },
        ports={"5432/tcp": 5432},
        detach=True
    )
    
    # 等待数据库就绪
    import time
    time.sleep(5)
    
    yield f"postgresql://test:test@localhost:5432/testdb"
    
    container.stop()
    container.remove()

API测试

import pytest
from fastapi.testclient import TestClient
from unittest.mock import Mock, patch
from app import app
from app.database import get_db

client = TestClient(app)

# 测试数据库依赖注入
def override_get_db():
    db = Mock()
    db.query.return_value.filter.return_value.first.return_value = Mock(
        id=1, name="Test User"
    )
    return db

app.dependency_overrides[get_db] = override_get_db

class TestUserAPI:
    """用户API测试套件"""
    
    def test_get_users(self):
        """测试获取用户列表"""
        response = client.get("/api/users")
        assert response.status_code == 200
        assert isinstance(response.json(), list)
    
    def test_create_user(self):
        """测试创建用户"""
        user_data = {
            "name": "Alice",
            "email": "alice@example.com",
            "password": "secure123"
        }
        response = client.post("/api/users", json=user_data)
        assert response.status_code == 201
        data = response.json()
        assert data["name"] == "Alice"
        assert "id" in data
    
    def test_create_user_invalid_email(self):
        """测试无效邮箱"""
        user_data = {
            "name": "Bob",
            "email": "invalid-email",
            "password": "secure123"
        }
        response = client.post("/api/users", json=user_data)
        assert response.status_code == 422
        assert "email" in response.json()["detail"][0]["loc"]
    
    def test_get_user_not_found(self):
        """测试用户不存在"""
        response = client.get("/api/users/999")
        assert response.status_code == 404
    
    def test_update_user(self):
        """测试更新用户"""
        update_data = {"name": "Updated Name"}
        response = client.put("/api/users/1", json=update_data)
        assert response.status_code == 200
        assert response.json()["name"] == "Updated Name"
    
    def test_delete_user(self):
        """测试删除用户"""
        response = client.delete("/api/users/1")
        assert response.status_code == 204

# 测试认证
class TestAuthentication:
    """认证测试"""
    
    @pytest.fixture
    def auth_token(self):
        """获取认证token"""
        response = client.post("/auth/token", data={
            "username": "testuser",
            "password": "testpass"
        })
        return response.json()["access_token"]
    
    def test_protected_endpoint(self, auth_token):
        """测试受保护端点"""
        headers = {"Authorization": f"Bearer {auth_token}"}
        response = client.get("/api/profile", headers=headers)
        assert response.status_code == 200
    
    def test_unauthorized_access(self):
        """测试未授权访问"""
        response = client.get("/api/profile")
        assert response.status_code == 401

测试异步API

import pytest
from httpx import AsyncClient
from app import app

@pytest.mark.asyncio
async def test_async_endpoint():
    """测试异步端点"""
    async with AsyncClient(app=app, base_url="http://test") as client:
        response = await client.get("/api/async")
        assert response.status_code == 200

@pytest.mark.asyncio
async def test_websocket():
    """测试WebSocket"""
    async with AsyncClient(app=app, base_url="http://test") as client:
        async with client.websocket_connect("/ws") as websocket:
            await websocket.send_text("Hello")
            data = await websocket.receive_text()
            assert data == "Echo: Hello"

Selenium浏览器自动化

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
import pytest

@pytest.fixture(scope="module")
def browser():
    """配置浏览器驱动"""
    options = Options()
    options.add_argument("--headless")  # 无头模式
    options.add_argument("--no-sandbox")
    options.add_argument("--disable-dev-shm-usage")
    
    driver = webdriver.Chrome(options=options)
    driver.implicitly_wait(10)
    yield driver
    driver.quit()

class TestWebApplication:
    """Web应用端到端测试"""
    
    def test_homepage_loads(self, browser):
        """测试首页加载"""
        browser.get("http://localhost:8000")
        
        # 等待页面加载
        WebDriverWait(browser, 10).until(
            EC.presence_of_element_located((By.TAG_NAME, "h1"))
        )
        
        # 验证标题
        title = browser.find_element(By.TAG_NAME, "h1")
        assert title.text == "Welcome"
    
    def test_user_login(self, browser):
        """测试用户登录"""
        browser.get("http://localhost:8000/login")
        
        # 填写表单
        username = browser.find_element(By.NAME, "username")
        password = browser.find_element(By.NAME, "password")
        submit = browser.find_element(By.TYPE, "submit")
        
        username.send_keys("testuser")
        password.send_keys("testpass123")
        submit.click()
        
        # 等待登录成功
        WebDriverWait(browser, 10).until(
            EC.presence_of_element_located((By.CLASS_NAME, "dashboard"))
        )
        
        # 验证登录状态
        welcome = browser.find_element(By.CLASS_NAME, "user-welcome")
        assert "testuser" in welcome.text
    
    def test_search_functionality(self, browser):
        """测试搜索功能"""
        browser.get("http://localhost:8000")
        
        search_box = browser.find_element(By.ID, "search-input")
        search_button = browser.find_element(By.ID, "search-button")
        
        search_box.send_keys("Python tutorial")
        search_button.click()
        
        # 等待搜索结果
        WebDriverWait(browser, 10).until(
            EC.presence_of_element_located((By.CLASS_NAME, "search-results"))
        )
        
        results = browser.find_elements(By.CLASS_NAME, "result-item")
        assert len(results) > 0

# 页面对象模式
class LoginPage:
    """登录页面对象"""
    
    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)
    
    def navigate(self):
        self.driver.get("http://localhost:8000/login")
    
    def login(self, username, password):
        username_field = self.driver.find_element(By.NAME, "username")
        password_field = self.driver.find_element(By.NAME, "password")
        submit_button = self.driver.find_element(By.TYPE, "submit")
        
        username_field.send_keys(username)
        password_field.send_keys(password)
        submit_button.click()
        
        return self.wait.until(
            EC.presence_of_element_located((By.CLASS_NAME, "dashboard"))
        )
    
    def get_error_message(self):
        return self.driver.find_element(By.CLASS_NAME, "error-message").text

# 使用页面对象
def test_login_with_page_object(browser):
    login_page = LoginPage(browser)
    login_page.navigate()
    
    dashboard = login_page.login("testuser", "wrongpass")
    assert login_page.get_error_message() == "Invalid credentials"

测试数据管理

import factory
from app.models import User, Product

# 使用factory_boy创建测试数据
class UserFactory(factory.Factory):
    class Meta:
        model = User
    
    id = factory.Sequence(lambda n: n + 1)
    name = factory.Faker("name")
    email = factory.Faker("email")
    password = factory.PostGenerationMethodCall("hash_password", "testpass")

class ProductFactory(factory.Factory):
    class Meta:
        model = Product
    
    id = factory.Sequence(lambda n: n + 1)
    name = factory.Faker("word")
    price = factory.Faker("pydecimal", left_digits=4, right_digits=2, positive=True)
    stock = factory.Faker("random_int", min=0, max=1000)

# 使用测试数据
@pytest.fixture
def sample_users():
    return UserFactory.create_batch(5)

@pytest.fixture
def sample_products():
    return ProductFactory.create_batch(10)

def test_user_product_association(sample_users, sample_products):
    user = sample_users[0]
    product = sample_products[0]
    
    user.products.append(product)
    assert product in user.products

测试性能

import time
import statistics

class TestPerformance:
    """性能测试"""
    
    def test_api_response_time(self):
        """测试API响应时间"""
        times = []
        
        for _ in range(100):
            start = time.time()
            response = client.get("/api/users")
            end = time.time()
            
            assert response.status_code == 200
            times.append(end - start)
        
        avg_time = statistics.mean(times)
        p95_time = sorted(times)[int(len(times) * 0.95)]
        
        assert avg_time < 0.1  # 平均响应时间<100ms
        assert p95_time < 0.2  # 95%请求<200ms
    
    def test_database_query_performance(self, db_session):
        """测试数据库查询性能"""
        # 插入大量数据
        for i in range(10000):
            user = User(name=f"User {i}", email=f"user{i}@test.com")
            db_session.add(user)
        db_session.commit()
        
        start = time.time()
        users = db_session.query(User).filter(User.name.like("User 1%")).all()
        end = time.time()
        
        assert len(users) > 0
        assert end - start < 0.5  # 查询时间<500ms

测试报告和覆盖

# pytest-html报告
# pytest --html=report.html --self-contained-html

# 覆盖率报告
# pytest --cov=src --cov-report=html --cov-report=xml

# 使用pytest-benchmark
def test_benchmark_sorting(benchmark):
    """基准测试"""
    data = list(range(10000))
    benchmark(lambda: sorted(data))

最佳实践

  1. 测试隔离:每个测试独立,无共享状态
  2. 测试数据管理:使用工厂模式创建测试数据
  3. 页面对象模式:Selenium测试使用POM提高可维护性
  4. 测试环境:使用Docker提供一致的测试环境
  5. 测试报告:生成详细的测试报告和覆盖率报告

总结

集成测试和端到端测试确保系统整体功能正确。通过测试数据库、API测试和浏览器自动化,可以验证从数据库到前端的完整流程。