第五章:软件质量与工程实践的变迁#
mindmap
root((软件质量与工程实践))
代码质量
静态分析
代码审查
Ruff/SonarQube
测试金字塔
单元测试
集成测试
E2E测试
TDD/BDD/ATDD
红绿重构
行为驱动
验收驱动
技术债务
分类管理
质量门禁
代码重构
提取方法
单一职责
文档即代码
Sphinx/MkDocs
自动生成
AI辅助质量
AI测试生成
AI代码审查
“质量不是一种行为,而是一种习惯。” — 亚里士多德
5.1 代码质量:从人工审查到自动化分析#
代码审查的演进#
1970s: 代码走查(Code Walkthrough)— 打印代码,围坐讨论
1990s: 正式代码审查(Fagan Inspection)— 严格流程,角色分工
2000s: 轻量级代码审查 — Pull Request 模式
2010s: 自动化静态分析 — SonarQube、ESLint
2020s: AI 代码审查 — CodeRabbit、PR-Agent
静态分析工具#
# ruff — 极速 Python linter(Rust 编写)
# pyproject.toml 配置
"""
[tool.ruff]
target-version = "py312"
line-length = 88
[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP", "S", "B", "A", "C4"]
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101"] # 允许测试中使用 assert
"""
# 运行检查
# ruff check .
# ruff format .
5.2 测试金字塔#
╱╲
╱ ╲ E2E 测试(少量)
╱ E2E╲ - Playwright / Selenium
╱──────╲ - 慢、脆弱、但最接近用户
╱ ╲
╱ 集成测试 ╲ 集成测试(适量)
╱────────────╲ - 测试服务间交互
╱ ╲ - 需要外部依赖
╱ 单元测试 ╲ 单元测试(大量)
╱──────────────────╲ - 快速、稳定、独立
╱ ╲ - pytest / Jest / JUnit
单元测试示例#
# 被测代码
class ShoppingCart:
def __init__(self):
self.items = []
def add_item(self, name: str, price: float, quantity: int = 1):
if price < 0:
raise ValueError("Price cannot be negative")
if quantity < 1:
raise ValueError("Quantity must be at least 1")
self.items.append({"name": name, "price": price, "quantity": quantity})
@property
def total(self) -> float:
return sum(item["price"] * item["quantity"] for item in self.items)
def apply_discount(self, percentage: float) -> float:
if not 0 <= percentage <= 100:
raise ValueError("Discount must be between 0 and 100")
return self.total * (1 - percentage / 100)
# 测试代码
import pytest
class TestShoppingCart:
def setup_method(self):
self.cart = ShoppingCart()
def test_empty_cart_total_is_zero(self):
assert self.cart.total == 0
def test_add_single_item(self):
self.cart.add_item("Python Book", 49.99)
assert self.cart.total == 49.99
def test_add_multiple_items(self):
self.cart.add_item("Python Book", 49.99, quantity=2)
self.cart.add_item("Coffee", 4.99)
assert self.cart.total == pytest.approx(104.97)
def test_negative_price_raises_error(self):
with pytest.raises(ValueError, match="Price cannot be negative"):
self.cart.add_item("Bad Item", -10)
def test_apply_discount(self):
self.cart.add_item("Item", 100)
assert self.cart.apply_discount(20) == 80.0
@pytest.mark.parametrize("discount", [-1, 101, 150])
def test_invalid_discount_raises_error(self, discount):
self.cart.add_item("Item", 100)
with pytest.raises(ValueError):
self.cart.apply_discount(discount)
5.3 TDD、BDD、ATDD#
TDD(测试驱动开发)#
Red → Green → Refactor 循环:
1. Red: 写一个失败的测试
2. Green: 写最少的代码让测试通过
3. Refactor: 重构代码,保持测试通过
# TDD 示例:实现一个密码验证器
# Step 1: Red — 写失败的测试
def test_password_must_be_at_least_8_chars():
assert validate_password("short") == False
# Step 2: Green — 最简实现
def validate_password(password: str) -> bool:
return len(password) >= 8
# Step 3: 继续添加测试
def test_password_must_contain_uppercase():
assert validate_password("alllowercase123") == False
# Step 4: 扩展实现
def validate_password(password: str) -> bool:
if len(password) < 8:
return False
if not any(c.isupper() for c in password):
return False
return True
BDD(行为驱动开发)#
# features/login.feature
Feature: User Login
As a registered user
I want to log in to my account
So that I can access my dashboard
Scenario: Successful login
Given I am on the login page
When I enter valid credentials
And I click the login button
Then I should be redirected to the dashboard
And I should see a welcome message
Scenario: Failed login with wrong password
Given I am on the login page
When I enter an invalid password
And I click the login button
Then I should see an error message "Invalid credentials"
And I should remain on the login page
5.4 技术债务管理#
技术债务的分类#
有意的 无意的
┌─────────────────┬─────────────────┐
鲁莽的 │ "我们没时间做 │ "什么是分层 │
│ 设计,先上线" │ 架构?" │
├─────────────────┼─────────────────┤
谨慎的 │ "我们知道后果, │ "现在我们知道 │
│ 但现在必须发布" │ 应该怎么做了" │
└─────────────────┴─────────────────┘
管理策略#
# 在代码中标记技术债务
# TODO: 这里需要重构,当前实现不支持并发
# FIXME: 临时方案,需要在 Q2 替换为消息队列
# HACK: 绕过第三方库的 bug,等升级后移除
# DEBT: 缺少输入验证,安全风险
# 使用工具追踪
# SonarQube 可以自动检测和量化技术债务
# 配置质量门禁(Quality Gate)
# - 新代码覆盖率 > 80%
# - 新代码无严重问题
# - 技术债务比率 < 5%
5.5 代码重构的艺术#
常见重构手法#
# 重构前:过长的函数
def process_order(order_data):
# 验证(20行)
if not order_data.get('user_id'):
raise ValueError("Missing user_id")
if not order_data.get('items'):
raise ValueError("Missing items")
for item in order_data['items']:
if item['quantity'] < 1:
raise ValueError("Invalid quantity")
# 计算价格(15行)
subtotal = sum(i['price'] * i['quantity'] for i in order_data['items'])
tax = subtotal * 0.1
total = subtotal + tax
# 保存到数据库(10行)
order = Order(user_id=order_data['user_id'], total=total)
db.session.add(order)
db.session.commit()
# 发送通知(10行)
send_email(order_data['user_id'], f"Order {order.id} confirmed")
return order
# 重构后:提取方法,单一职责
def process_order(order_data):
validate_order(order_data)
total = calculate_total(order_data['items'])
order = save_order(order_data['user_id'], total)
notify_user(order)
return order
def validate_order(order_data):
if not order_data.get('user_id'):
raise ValueError("Missing user_id")
if not order_data.get('items'):
raise ValueError("Missing items")
for item in order_data['items']:
if item['quantity'] < 1:
raise ValueError("Invalid quantity")
def calculate_total(items) -> float:
subtotal = sum(i['price'] * i['quantity'] for i in items)
tax = subtotal * 0.1
return subtotal + tax
def save_order(user_id, total):
order = Order(user_id=user_id, total=total)
db.session.add(order)
db.session.commit()
return order
def notify_user(order):
send_email(order.user_id, f"Order {order.id} confirmed")
5.6 文档即代码(Docs as Code)#
# 文档与代码同仓库、同流程
project/
├── src/ # 源代码
├── tests/ # 测试
├── docs/ # 文档(Markdown/RST)
│ ├── conf.py # Sphinx 配置
│ ├── index.md # 首页
│ ├── api/ # API 文档(自动生成)
│ └── guides/ # 使用指南
├── .github/
│ └── workflows/
│ └── docs.yml # 文档 CI/CD
└── README.md
5.7 从人工到 AI 辅助质量保证#
传统方式 |
AI 辅助方式 |
|---|---|
人工编写测试用例 |
AI 自动生成测试用例 |
人工代码审查 |
AI 预审 + 人工复核 |
手动编写文档 |
AI 生成文档草稿 |
规则式静态分析 |
AI 语义级代码分析 |
手动回归测试 |
AI 智能选择回归范围 |
# AI 辅助生成测试的 Prompt 示例
"""
请为以下 Python 函数生成全面的 pytest 测试用例,包括:
1. 正常路径测试
2. 边界条件测试
3. 异常处理测试
4. 参数化测试
函数代码:
{paste your function here}
"""
5.8 本章小结#
软件质量不是测试出来的,而是设计和构建出来的。从 TDD 到 AI 辅助测试,从人工代码审查到 AI 代码审查,工具在变,但核心理念不变:尽早发现问题,持续改进质量。
在 AI 时代,质量保证的重点正在从"人写的代码有没有 Bug"转向"AI 生成的代码是否安全、正确、可维护"。这是一个全新的挑战,我们将在第九章深入探讨。
思考题
你的项目测试覆盖率是多少?你觉得够吗?
TDD 在 AI 辅助编程时代还有意义吗?
如何建立对 AI 生成代码的质量信心?