构建速度提升3.8倍,镜像体积减少42%——Docker 27 buildx+manifests跨架构构建黄金组合,企业级落地全记录
2026/5/7 2:50:09
| 维度 | 描述 | 工具 |
|---|---|---|
| 代码风格 | PEP 8规范 | black, isort |
| 类型检查 | 类型注解检查 | mypy |
| 代码规范 | 最佳实践 | flake8, pylint |
| 安全检查 | 潜在漏洞 | bandit, safety |
| 测试覆盖 | 代码测试比例 | coverage |
| 工具 | 功能 | 性能 | 学习曲线 |
|---|---|---|---|
| black | 代码格式化 | 快 | 低 |
| flake8 | 代码检查 | 快 | 低 |
| mypy | 类型检查 | 中 | 中 |
| pylint | 全面检查 | 慢 | 高 |
| ruff | 快速linting | 极快 | 低 |
# pyproject.toml [tool.black] line-length = 88 target-version = ['py39', 'py310', 'py311'] include = '\.pyi?$' exclude = ''' /( \.git | \.venv | build | dist )/ ''' [tool.isort] profile = "black" line_length = 88 known_first_party = ["src"] skip = [".venv", "build", "dist"] [tool.mypy] python_version = "3.9" warn_return_any = true warn_unused_configs = true disallow_untyped_defs = false ignore_missing_imports = true [tool.ruff] line-length = 88 target-version = "py39" [tool.ruff.lint] select = ["E", "F", "W", "I", "N", "UP", "B", "C4"] ignore = ["E501"] # 行长度由black处理 [tool.coverage.run] source = ["src"] omit = ["*/tests/*", "*/test_*.py"] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "if __name__ == .__main__.:", "raise AssertionError()", ]import pytest from typing import List, Optional class DataValidator: """数据验证器""" @staticmethod def validate_email(email: str) -> bool: """验证邮箱格式""" import re pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' return bool(re.match(pattern, email)) @staticmethod def validate_positive(value: float) -> bool: """验证正数""" return value > 0 @staticmethod def validate_in_range(value: float, min_val: float, max_val: float) -> bool: """验证范围""" return min_val <= value <= max_val class TestDataValidator: """数据验证器测试""" @pytest.mark.parametrize("email,expected", [ ("test@example.com", True), ("user.name@domain.co.uk", True), ("invalid-email", False), ("@domain.com", False), ("user@", False), ("", False), ]) def test_validate_email(self, email, expected): assert DataValidator.validate_email(email) == expected @pytest.mark.parametrize("value,expected", [ (1.0, True), (0.0, False), (-1.0, False), (100.5, True), ]) def test_validate_positive(self, value, expected): assert DataValidator.validate_positive(value) == expected def test_validate_in_range(self): assert DataValidator.validate_in_range(5, 0, 10) == True assert DataValidator.validate_in_range(0, 0, 10) == True assert DataValidator.validate_in_range(10, 0, 10) == True assert DataValidator.validate_in_range(-1, 0, 10) == False assert DataValidator.validate_in_range(11, 0, 10) == False class TestEdgeCases: """边界情况测试""" def test_empty_string(self): assert DataValidator.validate_email("") == False def test_unicode_email(self): assert DataValidator.validate_email("用户@例子.广告") == False def test_very_long_email(self): long_email = "a" * 100 + "@example.com" # 应该能处理但可能返回False(取决于具体实现) result = DataValidator.validate_email(long_email) assert isinstance(result, bool)from unittest.mock import Mock, patch, MagicMock import pytest class APIClient: """API客户端""" def __init__(self, base_url: str): self.base_url = base_url self.session = None def fetch(self, endpoint: str) -> dict: """获取数据""" import requests response = requests.get(f"{self.base_url}/{endpoint}") return response.json() class TestAPIClient: """API客户端测试""" @patch('requests.get') def test_fetch_success(self, mock_get): """测试成功获取""" mock_response = Mock() mock_response.json.return_value = {"status": "success", "data": [1, 2, 3]} mock_get.return_value = mock_response client = APIClient("https://api.example.com") result = client.fetch("users") assert result["status"] == "success" assert result["data"] == [1, 2, 3] mock_get.assert_called_once_with("https://api.example.com/users") @patch('requests.get') def test_fetch_error(self, mock_get): """测试获取失败""" mock_get.side_effect = ConnectionError("Network error") client = APIClient("https://api.example.com") with pytest.raises(ConnectionError): client.fetch("users") def test_with_fixture(self, mock_get): """使用fixture的测试""" # fixture在conftest.py中定义 result = self.client.fetch("users") assert "status" in resultimport pytest import time class TestPerformance: """性能测试""" def test_sort_performance(self): """测试排序性能""" import random # 生成大量数据 data = [random.randint(0, 10000) for _ in range(10000)] start = time.perf_counter() sorted_data = sorted(data) elapsed = time.perf_counter() - start # 应该在1秒内完成 assert elapsed < 1.0, f"排序耗时 {elapsed:.2f}s,超过1秒" # 验证排序正确性 assert sorted_data == sorted(data) @pytest.mark.benchmark def test_list_comprehension_performance(self, benchmark): """基准测试列表推导式""" result = benchmark(lambda: [i**2 for i in range(10000)]) assert len(result) == 10000 # conftest.py def pytest_configure(config): config.addinivalue_line("markers", "benchmark: mark test as a benchmark") @pytest.fixture def sample_data(): """示例数据fixture""" return [i for i in range(100)]# .pre-commit-config.yaml repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files - id: check-merge-conflict - repo: https://github.com/psf/black rev: 23.3.0 hooks: - id: black language_version: python3.10 - repo: https://github.com/pycqa/isort rev: 5.12.0 hooks: - id: isort args: ["--profile", "black"] - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.0.261 hooks: - id: ruff args: ["--fix"] - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.3.0 hooks: - id: mypy additional_dependencies: [types-all]# .github/workflows/ci.yml name: CI on: push: branches: [main, develop] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: ['3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -e ".[dev]" - name: Lint with ruff run: ruff check src/ - name: Format check with black run: black --check src/ - name: Type check with mypy run: mypy src/ - name: Test with pytest run: | coverage run -m pytest tests/ coverage report --fail-under=80 - name: Upload coverage uses: codecov/codecov-action@v3 with: files: ./coverage.xml# 运行测试并生成覆盖率报告 $ coverage run -m pytest tests/ $ coverage report -m Name Stmts Miss Cover Missing ----------------------------------------------------- src/validators.py 45 5 89% 23,45,67 src/models.py 78 12 85% 34,56,78 tests/test_validators.py 60 0 100% - ----------------------------------------------------- TOTAL 183 17 91%# 使用radon进行复杂度分析 from radon.metrics import mi_visit, h_visit from radon.complexity import cc_visit def analyze_complexity(filepath: str): """代码复杂度分析""" with open(filepath, 'r') as f: source = f.read() # 圈复杂度 complexity = cc_visit(source) print("圈复杂度:") for item in complexity: if item.classname: name = f"{item.classname}.{item.name}" else: name = item.name print(f" {name}: {item.complexity}") # 维护性指数 mi = mi_visit(source, multi=True) print(f"\n维护性指数: {mi:.1f}") # Halstead指标 from radon.metrics import h_visit halstead = h_visit(source) print(f"难度: {halstead.difficulty:.1f}")- [ ] 代码符合PEP 8规范 - [ ] 函数和类有docstring - [ ] 类型注解完整 - [ ] 单元测试覆盖关键逻辑 - [ ] 没有硬编码的魔法数字 - [ ] 错误处理适当 - [ ] 没有安全漏洞 - [ ] 性能符合要求#!/bin/bash # pre-commit-check.sh set -e echo "运行代码检查..." # 格式化 black --check src/ echo "✓ 格式化检查通过" # 检查import isort --check-only --diff src/ echo "✓ import检查通过" # Lint ruff check src/ echo "✓ Lint检查通过" # 类型检查 mypy src/ echo "✓ 类型检查通过" # 测试 pytest tests/ -v echo "✓ 测试通过" echo "所有检查通过!"代码质量保障要点: