Python测试工具案例如何封装自动化测试

wen python案例 31

本文目录导读:

Python测试工具案例如何封装自动化测试

  1. 基础测试框架封装
  2. API测试封装
  3. UI自动化测试封装
  4. 数据驱动测试封装
  5. 测试报告生成
  6. 完整测试用例示例
  7. 配置文件示例
  8. 目录结构

我来为你详细介绍Python自动化测试工具的封装方法和实际案例。

基础测试框架封装

1 通用测试基类

# base_test.py
import unittest
import logging
import json
from datetime import datetime
from typing import Any, Dict, List, Optional
class BaseTest(unittest.TestCase):
    """测试基类"""
    @classmethod
    def setUpClass(cls):
        """类级别初始化"""
        cls.logger = logging.getLogger(cls.__name__)
        cls.start_time = datetime.now()
        cls.test_results = []
    def setUp(self):
        """用例级别初始化"""
        self.logger.info(f"开始执行测试: {self._testMethodName}")
        self.case_start_time = datetime.now()
    def tearDown(self):
        """用例级别清理"""
        duration = (datetime.now() - self.case_start_time).total_seconds()
        result = self._outcome.result
        if result.wasSuccessful():
            status = "PASS"
        else:
            status = "FAIL" if result.failures else "ERROR"
        self.test_results.append({
            "case": self._testMethodName,
            "status": status,
            "duration": duration
        })
        self.logger.info(f"测试完成: {self._testMethodName} - {status} ({duration}s)")
    @classmethod
    def tearDownClass(cls):
        """类级别清理"""
        total_duration = (datetime.now() - cls.start_time).total_seconds()
        total = len(cls.test_results)
        passed = sum(1 for r in cls.test_results if r['status'] == 'PASS')
        cls.logger.info(f"""
        ========== 测试报告 ==========
        总计: {total} | 通过: {passed} | 失败: {total - passed}
        耗时: {total_duration:.2f}s
        ==============================
        """)

API测试封装

1 HTTP请求封装

# api_client.py
import requests
import json
from typing import Dict, Optional, Any
from requests.exceptions import RequestException
import time
class APIClient:
    """API测试客户端封装"""
    def __init__(self, base_url: str, timeout: int = 30):
        self.base_url = base_url.rstrip('/')
        self.timeout = timeout
        self.session = requests.Session()
        self.session.headers.update({
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        })
        self.response_history = []
    def set_headers(self, headers: Dict[str, str]):
        """设置请求头"""
        self.session.headers.update(headers)
    def set_auth_token(self, token: str):
        """设置认证令牌"""
        self.session.headers.update({'Authorization': f'Bearer {token}'})
    def request(self, method: str, endpoint: str, **kwargs) -> requests.Response:
        """通用请求方法"""
        url = f"{self.base_url}/{endpoint.lstrip('/')}"
        kwargs.setdefault('timeout', self.timeout)
        try:
            response = self.session.request(method, url, **kwargs)
            self.response_history.append({
                'method': method,
                'url': url,
                'status_code': response.status_code,
                'timestamp': time.time()
            })
            return response
        except RequestException as e:
            raise APIClientError(f"请求失败: {str(e)}")
    def get(self, endpoint: str, params: Optional[Dict] = None) -> requests.Response:
        """GET请求"""
        return self.request('GET', endpoint, params=params)
    def post(self, endpoint: str, data: Any = None, json_data: Any = None) -> requests.Response:
        """POST请求"""
        return self.request('POST', endpoint, data=data, json=json_data)
    def put(self, endpoint: str, data: Any = None, json_data: Any = None) -> requests.Response:
        """PUT请求"""
        return self.request('PUT', endpoint, data=data, json=json_data)
    def delete(self, endpoint: str) -> requests.Response:
        """DELETE请求"""
        return self.request('DELETE', endpoint)
class APIClientError(Exception):
    """API客户端异常"""
    pass

2 API测试用例封装

# api_test.py
from base_test import BaseTest
from api_client import APIClient
import json
class APITestCase(BaseTest):
    """API测试用例基类"""
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        # 从配置文件读取配置
        cls.config = cls.load_config()
        cls.client = APIClient(cls.config['base_url'])
        # 如果需要进行认证
        if 'auth_token' in cls.config:
            cls.client.set_auth_token(cls.config['auth_token'])
    @classmethod
    def load_config(cls) -> dict:
        """加载配置文件"""
        config_path = getattr(cls, 'config_path', 'config.json')
        with open(config_path, 'r') as f:
            return json.load(f)
    def validate_response(self, response, expected_status: int = 200):
        """验证响应"""
        self.assertEqual(response.status_code, expected_status,
                        f"期望状态码 {expected_status}, 实际 {response.status_code}")
        return response.json()
    def validate_json_schema(self, data: dict, schema: dict):
        """验证JSON Schema"""
        # 这里可以使用 jsonschema 库
        from jsonschema import validate
        validate(instance=data, schema=schema)

UI自动化测试封装

1 WebDriver封装

# web_driver_manager.py
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.common.exceptions import TimeoutException, NoSuchElementException
from typing import Tuple, Optional, List
import logging
class WebDriverManager:
    """WebDriver管理器"""
    def __init__(self, browser: str = 'chrome', headless: bool = False):
        self.logger = logging.getLogger(self.__class__.__name__)
        self.driver = self._init_driver(browser, headless)
        self.wait = WebDriverWait(self.driver, 10)
    def _init_driver(self, browser: str, headless: bool) -> webdriver.Remote:
        """初始化浏览器驱动"""
        if browser.lower() == 'chrome':
            options = webdriver.ChromeOptions()
            if headless:
                options.add_argument('--headless')
            options.add_argument('--no-sandbox')
            options.add_argument('--disable-dev-shm-usage')
            return webdriver.Chrome(options=options)
        elif browser.lower() == 'firefox':
            options = webdriver.FirefoxOptions()
            if headless:
                options.add_argument('--headless')
            return webdriver.Firefox(options=options)
        else:
            raise ValueError(f"不支持的浏览器: {browser}")
    def navigate(self, url: str):
        """导航到URL"""
        self.logger.info(f"导航到: {url}")
        self.driver.get(url)
    def find_element(self, by: By, value: str, timeout: int = 10) -> webdriver.Remote:
        """查找元素"""
        try:
            element = WebDriverWait(self.driver, timeout).until(
                EC.presence_of_element_located((by, value))
            )
            return element
        except TimeoutException:
            self.logger.error(f"元素未找到: ({by}, {value})")
            raise
    def click_element(self, by: By, value: str):
        """点击元素"""
        element = self.find_element(by, value)
        element.click()
        self.logger.info(f"点击元素: ({by}, {value})")
    def input_text(self, by: By, value: str, text: str):
        """输入文本"""
        element = self.find_element(by, value)
        element.clear()
        element.send_keys(text)
        self.logger.info(f"输入文本到 ({by}, {value}): {text}")
    def get_text(self, by: By, value: str) -> str:
        """获取文本"""
        element = self.find_element(by, value)
        return element.text
    def take_screenshot(self, filepath: str):
        """截图"""
        self.driver.save_screenshot(filepath)
        self.logger.info(f"截图保存到: {filepath}")
    def close(self):
        """关闭浏览器"""
        if self.driver:
            self.driver.quit()

2 Page Object模式封装

# page_objects/base_page.py
from web_driver_manager import WebDriverManager
from selenium.webdriver.common.by import By
from typing import Tuple
class BasePage:
    """页面对象基类"""
    def __init__(self, driver_manager: WebDriverManager):
        self.driver = driver_manager.driver
        self.wait = driver_manager.wait
        self.dm = driver_manager
    def find_element(self, locator: Tuple[By, str]):
        """查找元素"""
        return self.dm.find_element(*locator)
    def click(self, locator: Tuple[By, str]):
        """点击操作"""
        self.dm.click_element(*locator)
    def input(self, locator: Tuple[By, str], text: str):
        """输入操作"""
        self.dm.input_text(*locator, text)
    def get_title(self) -> str:
        """获取页面标题"""
        return self.driver.title
    def get_current_url(self) -> str:
        """获取当前URL"""
        return self.driver.current_url
# page_objects/login_page.py
class LoginPage(BasePage):
    """登录页面"""
    # 定位器
    USERNAME_INPUT = (By.ID, "username")
    PASSWORD_INPUT = (By.ID, "password")
    LOGIN_BUTTON = (By.ID, "login-btn")
    ERROR_MESSAGE = (By.CLASS_NAME, "error-msg")
    def login(self, username: str, password: str):
        """执行登录操作"""
        self.input(self.USERNAME_INPUT, username)
        self.input(self.PASSWORD_INPUT, password)
        self.click(self.LOGIN_BUTTON)
    def get_error_message(self) -> str:
        """获取错误信息"""
        return self.find_element(self.ERROR_MESSAGE).text

数据驱动测试封装

1 数据提供器

# data_provider.py
import pandas as pd
import json
import yaml
from typing import List, Dict, Any
from pathlib import Path
class DataProvider:
    """测试数据提供器"""
    @staticmethod
    def from_csv(filepath: str) -> List[Dict[str, Any]]:
        """从CSV文件读取测试数据"""
        df = pd.read_csv(filepath)
        return df.to_dict('records')
    @staticmethod
    def from_json(filepath: str) -> List[Dict[str, Any]]:
        """从JSON文件读取测试数据"""
        with open(filepath, 'r', encoding='utf-8') as f:
            return json.load(f)
    @staticmethod
    def from_yaml(filepath: str) -> List[Dict[str, Any]]:
        """从YAML文件读取测试数据"""
        with open(filepath, 'r', encoding='utf-8') as f:
            return yaml.safe_load(f)
    @staticmethod
    def from_excel(filepath: str, sheet_name: str = 'Sheet1') -> List[Dict[str, Any]]:
        """从Excel文件读取测试数据"""
        df = pd.read_excel(filepath, sheet_name=sheet_name)
        return df.to_dict('records')

2 参数化测试

# parameterized_test.py
from base_test import BaseTest
from data_provider import DataProvider
from ddt import ddt, data, unpack
import json
@ddt
class ParameterizedTest(BaseTest):
    """参数化测试示例"""
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.data_provider = DataProvider()
    @data(*DataProvider.from_csv('test_data.csv'))
    @unpack
    def test_login(self, username, password, expected):
        """数据驱动的登录测试"""
        # 这里执行实际的登录测试
        result = self.login_test(username, password)
        self.assertEqual(result, expected)
    def login_test(self, username, password):
        """模拟登录测试"""
        # 实际测试逻辑
        if username == "admin" and password == "password123":
            return "success"
        return "failed"

测试报告生成

1 HTML报告生成器

# report_generator.py
import json
import time
from datetime import datetime
from typing import List, Dict, Any
from pathlib import Path
import jinja2
class TestReportGenerator:
    """测试报告生成器"""
    def __init__(self, output_dir: str = "reports"):
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(exist_ok=True)
    def generate_html_report(self, test_results: List[Dict[str, Any]], 
                            template_file: str = "report_template.html"):
        """生成HTML测试报告"""
        # 准备报告数据
        total_tests = len(test_results)
        passed_tests = sum(1 for r in test_results if r['status'] == 'PASS')
        failed_tests = total_tests - passed_tests
        pass_rate = (passed_tests / total_tests * 100) if total_tests > 0 else 0
        report_data = {
            "title": "自动化测试报告",
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "total": total_tests,
            "passed": passed_tests,
            "failed": failed_tests,
            "pass_rate": f"{pass_rate:.2f}%",
            "duration": sum(r.get('duration', 0) for r in test_results),
            "test_cases": test_results
        }
        # 渲染模板
        template_loader = jinja2.FileSystemLoader(searchpath="./templates")
        template_env = jinja2.Environment(loader=template_loader)
        template = template_env.get_template(template_file)
        html_content = template.render(report_data)
        # 保存报告
        report_path = self.output_dir / f"test_report_{int(time.time())}.html"
        with open(report_path, 'w', encoding='utf-8') as f:
            f.write(html_content)
        return report_path

完整测试用例示例

# test_suite.py
import unittest
import json
from api_test import APITestCase
from api_client import APIClient
from data_provider import DataProvider
from report_generator import TestReportGenerator
class TestUserAPI(APITestCase):
    """用户API测试"""
    config_path = "config/api_config.json"
    def test_create_user(self):
        """测试创建用户"""
        user_data = {
            "username": "testuser1",
            "email": "test@example.com",
            "password": "Test123!"
        }
        response = self.client.post("/api/users", json=user_data)
        data = self.validate_response(response, 201)
        self.assertIn("id", data)
        self.assertEqual(data["username"], user_data["username"])
    def test_get_user(self):
        """测试获取用户"""
        user_id = 1
        response = self.client.get(f"/api/users/{user_id}")
        data = self.validate_response(response, 200)
        self.assertEqual(data["id"], user_id)
    def test_get_user_not_found(self):
        """测试获取不存在的用户"""
        response = self.client.get("/api/users/99999")
        self.assertEqual(response.status_code, 404)
    def test_update_user(self):
        """测试更新用户"""
        user_id = 1
        update_data = {"email": "updated@example.com"}
        response = self.client.put(f"/api/users/{user_id}", json=update_data)
        data = self.validate_response(response, 200)
        self.assertEqual(data["email"], update_data["email"])
    def test_delete_user(self):
        """测试删除用户"""
        user_id = 1
        response = self.client.delete(f"/api/users/{user_id}")
        self.validate_response(response, 204)
if __name__ == "__main__":
    # 配置日志
    import logging
    logging.basicConfig(level=logging.INFO)
    # 运行测试
    suite = unittest.TestLoader().loadTestsFromTestCase(TestUserAPI)
    runner = unittest.TextTestRunner(verbosity=2)
    result = runner.run(suite)
    # 生成报告
    generator = TestReportGenerator()
    test_results = [
        {"case": test._testMethodName, "status": "PASS" if result.wasSuccessful() else "FAIL"}
        for test in suite._tests[0]._tests  # 简化处理
    ]
    report_path = generator.generate_html_report(test_results)
    print(f"测试报告已生成: {report_path}")

配置文件示例

config/api_config.json

{
    "base_url": "https://api.example.com",
    "auth_token": "your_auth_token_here",
    "timeout": 30,
    "headers": {
        "X-API-Key": "your_api_key"
    }
}

test_data.csv

username,password,expected
admin,password123,success
user1,wrongpass,failed
testuser,test123,success

目录结构

自动化测试项目/
├── config/
│   ├── api_config.json
│   └── test_config.yaml
├── data/
│   ├── test_data.csv
│   └── test_data.json
├── page_objects/
│   ├── __init__.py
│   ├── base_page.py
│   └── login_page.py
├── reports/
│   └── test_report_template.html
├── templates/
│   └── report_template.html
├── tests/
│   ├── __init__.py
│   ├── test_api.py
│   ├── test_ui.py
│   └── test_data_driven.py
├── utils/
│   ├── __init__.py
│   ├── api_client.py
│   ├── base_test.py
│   ├── data_provider.py
│   ├── report_generator.py
│   └── web_driver_manager.py
├── requirements.txt
└── run_tests.py

这样封装的自动化测试框架具有以下特点:

  1. 模块化设计:各组件职责清晰,易于维护
  2. 可扩展性:易于添加新的测试场景和功能
  3. 数据驱动:支持多种数据源
  4. 报告生成:自动生成测试报告
  5. 代码复用:通过基类和工具类减少重复代码
  6. 易于集成:可以与CI/CD工具集成

你可以根据具体项目需求调整和扩展这些封装。

抱歉,评论功能暂时关闭!