第18章:测试驱动开发与项目管理
"质量不是检验出来的,而是设计和制造出来的。" —— 质量管理专家 W. Edwards Deming
在现代软件开发中,质量保障就像建筑工程的质量管控体系一样重要。本章将带你学习测试驱动开发(TDD)和项目管理的精髓,建立完整的软件质量保障体系。
学习目标
完成本章学习后,你将能够:
- 掌握测试驱动开发(TDD): 理解红绿重构循环,掌握单元测试、集成测试的编写技巧
- 建立全面测试策略: 学会设计测试金字塔,掌握性能测试和安全测试
- 实践项目管理: 掌握敏捷开发方法论,学会使用Git进行版本控制和团队协作
- 构建CI/CD流水线: 理解持续集成和持续部署,掌握自动化构建和部署
- 拥抱DevOps文化: 学会容器化技术,理解开发运维一体化的理念
18.1 测试驱动开发基础 - "工程质量检测实验室"
想象一下建造一座摩天大楼的过程。在施工前,工程师们会在质量检测实验室中对每一种建筑材料进行严格测试,确保钢材的强度、混凝土的配比、电缆的传导性都符合标准。只有通过了这些测试的材料,才能被用于实际建设。
测试驱动开发(TDD)就是软件开发中的"质量检测实验室"。我们先编写测试用例(相当于质量标准),然后编写代码让测试通过(相当于制造符合标准的材料),最后重构代码提升质量(相当于工艺改进)。
18.1.1 TDD核心理念 - "先检测 ,后制造"
TDD遵循"红-绿-重构"的开发循环:
- 红(Red): 编写一个失败的测试 - 相当于制定质量标准
- 绿(Green): 编写最简单的代码让测试通过 - 相当于制造合格产品
- 重构(Refactor): 改进代码质量,保持测试通过 - 相当于工艺优化
"""TDD核心框架 - 质量检测实验室这个框架演示了TDD的核心理念和实践方法:1. 红绿重构循环2. 测试优先的开发思维3. 持续重构的质量改进4. 快速反馈的开发体验"""import unittestimport pytestfrom typing import List, Dict, Optional, Anyfrom datetime import datetime, datefrom dataclasses import dataclassfrom decimal import Decimalimport logging# 配置日志logging.basicConfig(level=logging.INFO)logger = logging.getLogger(__name__)@dataclassclass TestResult:"""测试结果记录"""test_name: strstatus: str # passed, failed, skippedexecution_time: floaterror_message: Optional[str] = Nonetimestamp: datetime = Nonedef __post_init__(self):if self.timestamp is None:self.timestamp = datetime.now()class TDDDemo:"""TDD演示类 - 银行账户管理系统我们将使用银行账户系统来演示完整的TDD开发过程"""def __init__(self):self.test_results: List[TestResult] = []def log_test_result(self, test_name: str, status: str,execution_time: float, error_message: str = None):"""记录测试结果"""result = TestResult(test_name, status, execution_time, error_message)self.test_results.append(result)status_emoji = "✅" if status == "passed" else "❌" if status == "failed" else "⏸️"logger.info(f"{status_emoji} {test_name}: {status} ({execution_time:.3f}s)")if error_message:logger.error(f" 错误信息: {error_message}")# 第一轮:红 - 编写失败的测试class TestBankAccount(unittest.TestCase):"""银行账户测试类 - 质量标准制定在这里我们定义银行账户应该具备的所有功能和行为标准"""def setUp(self):"""测试前的准备工作"""# 这时候BankAccount类还不存在,测试会失败passdef test_create_account_with_initial_balance(self):"""测试1: 创建账户时可以设置初始余额"""# 红阶段:这个测试会失败,因为BankAccount类还不存在account = BankAccount("123456", "张三", 1000.0)self.assertEqual(account.balance, 1000.0)self.assertEqual(account.account_number, "123456")self.assertEqual(account.account_holder, "张三")def test_deposit_money(self):"""测试2: 存款功能"""account = BankAccount("123456", "张三", 500.0)account.deposit(200.0)self.assertEqual(account.balance, 700.0)def test_withdraw_money(self):"""测试3: 取款功能"""account = BankAccount("123456", "张三", 1000.0)success = account.withdraw(300.0)self.assertTrue(success)self.assertEqual(account.balance, 700.0)def test_withdraw_insufficient_funds(self):"""测试4: 余额不足时取款失败"""account = BankAccount("123456", "张三", 100.0)success = account.withdraw(200.0)self.assertFalse(success)self.assertEqual(account.balance, 100.0) # 余额不变def test_account_transaction_history(self):"""测试5: 交易历史记录"""account = BankAccount("123456", "张三", 1000.0)account.deposit(500.0)account.withdraw(200.0)history = account.get_transaction_history()self.assertEqual(len(history), 2)self.assertEqual(history[0]['type'], 'deposit')self.assertEqual(history[0]['amount'], 500.0)self.assertEqual(history[1]['type'], 'withdrawal')self.assertEqual(history[1]['amount'], 200.0)# 第二轮:绿 - 编写最简单的实现class BankAccount:"""银行账户类 - 符合标准的产品制造这是让测试通过的最简单实现"""def __init__(self, account_number: str, account_holder: str, initial_balance: float = 0.0):"""初始化银行账户"""self.account_number = account_numberself.account_holder = account_holderself.balance = Decimal(str(initial_balance)) # 使用Decimal避免浮点数精度问题self.transaction_history: List[Dict] = []self.created_at = datetime.now()logger.info(f"💳 创建账户: {account_number} | 户主: {account_holder} | 初始余额: {initial_balance}")def deposit(self, amount: float) -> bool:"""存款操作 - 资金流入检测"""if amount <= 0:logger.warning(f"❌ 存款失败: 金额必须大于0 (尝试存款: {amount})")return Falseself.balance += Decimal(str(amount))self._record_transaction('deposit', amount)logger.info(f"💰 存款成功: +{amount} | 余额: {self.balance}")return Truedef withdraw(self, amount: float) -> bool:"""取款操作 - 资金流出检测"""if amount <= 0:logger.warning(f"❌ 取款失败: 金额必须大于0 (尝试取款: {amount})")return Falseif self.balance < Decimal(str(amount)):logger.warning(f"❌ 取款失败: 余额不足 (余额: {self.balance}, 尝试取款: {amount})")return Falseself.balance -= Decimal(str(amount))self._record_transaction('withdrawal', amount)logger.info(f"💸 取款成功: -{amount} | 余额: {self.balance}")return Truedef _record_transaction(self, transaction_type: str, amount: float):"""记录交易历史 - 交易轨迹追踪"""transaction = {'type': transaction_type,'amount': amount,'timestamp': datetime.now().isoformat(),'balance_after': float(self.balance)}self.transaction_history.append(transaction)def get_transaction_history(self) -> List[Dict]:"""获取交易历史"""return self.transaction_history.copy()def get_balance(self) -> float:"""获取账户余额"""return float(self.balance)def __str__(self) -> str:return f"BankAccount({self.account_number}, {self.account_holder}, {self.balance})"def __repr__(self) -> str:return self.__str__()# 第三轮:重构 - 改进代码质量class EnhancedBankAccount(BankAccount):"""增强银行账户 - 工艺优化版本在保持测试通过的前提下,改进代码结构和质量"""def __init__(self, account_number: str, account_holder: str,initial_balance: float = 0.0, account_type: str = "savings"):super().__init__(account_number, account_holder, initial_balance)self.account_type = account_typeself.daily_withdrawal_limit = Decimal("10000.0") # 每日取款限额self.daily_withdrawn = Decimal("0.0") # 今日已取款金额self.last_transaction_date = date.today()def withdraw(self, amount: float) -> bool:"""增强版取款 - 添加每日限额检查"""if not self._check_daily_limit(amount):return Falsesuccess = super().withdraw(amount)if success:self._update_daily_withdrawal(amount)return successdef _check_daily_limit(self, amount: float) -> bool:"""检查每日取款限额"""today = date.today()# 如果是新的一天,重置每日取款金额if today != self.last_transaction_date:self.daily_withdrawn = Decimal("0.0")self.last_transaction_date = todayif self.daily_withdrawn + Decimal(str(amount)) > self.daily_withdrawal_limit:logger.warning(f"❌ 取款失败: 超过每日限额 (限额: {self.daily_withdrawal_limit}, "f"已取款: {self.daily_withdrawn}, 尝试取款: {amount})")return Falsereturn Truedef _update_daily_withdrawal(self, amount: float):"""更新每日取款金额"""self.daily_withdrawn += Decimal(str(amount))def get_daily_withdrawal_info(self) -> Dict:"""获取每日取款信息"""remaining_limit = self.daily_withdrawal_limit - self.daily_withdrawnreturn {'daily_limit': float(self.daily_withdrawal_limit),'withdrawn_today': float(self.daily_withdrawn),'remaining_limit': float(remaining_limit),'last_transaction_date': self.last_transaction_date.isoformat()}# TDD测试运行器class TDDTestRunner:"""TDD测试运行器 - 质量检测执行中心管理和执行所有测试,提供详细的测试报告"""def __init__(self):self.test_suite = unittest.TestSuite()self.test_results: List[TestResult] = []def add_test_class(self, test_class):"""添加测试类到测试套件"""tests = unittest.TestLoader().loadTestsFromTestCase(test_class)self.test_suite.addTests(tests)def run_tests(self) -> Dict[str, Any]:"""运行所有测试"""print("🧪 启动TDD测试执行...")print("=" * 60)# 运行测试runner = unittest.TextTestRunner(verbosity=2, stream=open('/dev/null', 'w'))result = runner.run(self.test_suite)# 统计结果total_tests = result.testsRunfailures = len(result.failures)errors = len(result.errors)passed = total_tests - failures - errors# 生成报告report = {'total_tests': total_tests,'passed': passed,'failed': failures,'errors': errors,'success_rate': (passed / total_tests * 100) if total_tests > 0 else 0,'details': {'failures': result.failures,'errors': result.errors}}self._print_test_report(report)return reportdef _print_test_report(self, report: Dict):"""打印测试报告"""print(f"\n📊 TDD测试报告")print("-" * 40)print(f"✅ 通过: {report['passed']}")print(f"❌ 失败: {report['failed']}")print(f"💥 错误: {report['errors']}")print(f"📈 成功率: {report['success_rate']:.1f}%")if report['failed'] > 0 or report['errors'] > 0:print(f"\n⚠️ 失败详情:")for failure in report['details']['failures']:print(f" - {failure[0]}: {failure[1].split('AssertionError:')[-1].strip()}")for error in report['details']['errors']:print(f" - {error[0]}: {error[1].split(':')[-1].strip()}")# TDD演示程序def demo_tdd_process():"""TDD完整流程演示"""print("=== TDD开发流程演示 ===\n")print("🏗️ TDD就像建筑工程的质量管控体系:")print("1. 📋 制定质量标准 (编写测试)")print("2. 🏭 制造合格产品 (编写代码)")print("3. 🔧 工艺改进优化 (重构代码)")print("4. 🔄 持续循环改进 (迭代开发)")print("\n第一阶段: 🔴 红 - 制定质量标准")print("编写测试用例,定义银行账户应该具备的功能...")# 运行测试(第一次会失败,因为还没有实现)test_runner = TDDTestRunner()test_runner.add_test_class(TestBankAccount)print("\n第二阶段: 🟢 绿 - 制造合格产品")print("实现BankAccount类,让所有测试通过...")# 运行测试(应该全部通过)result = test_runner.run_tests()print("\n第三阶段: 🔵 重构 - 工艺改进优化")print("改进代码质量,添加新功能,保持测试通过...")# 演示账户操作print("\n💼 银行账户系统演示:")account = EnhancedBankAccount("001", "李明", 5000.0, "checking")print(f"📊 账户信息: {account}")# 存款操作print(f"\n💰 存款操作测试:")account.deposit(1500.0)print(f" 存款后余额: {account.get_balance()}")# 取款操作print(f"\n💸 取款操作测试:")success1 = account.withdraw(800.0)print(f" 取款 800元: {'成功' if success1 else '失败'}")print(f" 余额: {account.get_balance()}")# 超限取款测试success2 = account.withdraw(15000.0)print(f" 取款 15000元: {'成功' if success2 else '失败'} (超过每日限额)")# 每日取款信息withdrawal_info = account.get_daily_withdrawal_info()print(f"\n📈 每日取款信息:")print(f" 每日限额: {withdrawal_info['daily_limit']}")print(f" 今日已取: {withdrawal_info['withdrawn_today']}")print(f" 剩余额度: {withdrawal_info['remaining_limit']}")# 交易历史history = account.get_transaction_history()print(f"\n📋 交易历史 ({len(history)}笔):")for i, transaction in enumerate(history, 1):print(f" {i}. {transaction['type']}: {transaction['amount']} "f"(余额: {transaction['balance_after']})")print(f"\n🎯 TDD核心价值:")print(f"✅ 质量保障: 所有功能都有测试覆盖")print(f"✅ 快速反馈: 立即发现代码问题")print(f"✅ 重构信心: 测试保证重构安全")print(f"✅ 文档价值: 测试即是活文档")print(f"✅ 设计驱动: 先思考接口再实现")# 运行演示if __name__ == "__main__":demo_tdd_process()### 18.1.2 pytest测试框架深入 - "专业检测设备"就像建筑工程需要各种专业检测设备一样,Python测试需要强大的测试框架。pytest是Python生态中最强大的测试框架,相当于质量检测实验室的全套专业设备。
"""pytest高级测试框架 - 专业检测设备集这个模块演示了pytest的高级特性:1. 参数化测试 - 批量检测2. 测试夹具(Fixture) - 标准化检测环境3. 测试标记(Mark) - 检测类型分类4. 插件系统 - 设备扩展能力"""import pytestimport timeimport tempfileimport jsonfrom pathlib import Pathfrom unittest.mock import Mock, patch, MagicMockfrom typing import List, Dict, Any, Generatorfrom dataclasses import dataclassimport requests@dataclassclass TestEnvironment:"""测试环境配置"""database_url: strapi_endpoint: strtest_data_path: strcleanup_required: bool = Trueclass DatabaseManager:"""数据库管理器 - 数据存储系统"""def __init__(self, connection_string: str):self.connection_string = connection_stringself.connected = Falseself.transactions = []def connect(self):"""连接数据库"""# 模拟连接过程time.sleep(0.1)self.connected = Truereturn Truedef disconnect(self):"""断开连接"""self.connected = Falseself.transactions.clear()def execute_query(self, query: str, params: tuple = None) -> List[Dict]:"""执行查询"""if not self.connected:raise ConnectionError("数据库未连接")# 模拟查询执行self.transactions.append({'query': query,'params': params,'timestamp': time.time()})# 根据查询类型返回模拟数据if 'SELECT' in query.upper():return [{'id': 1, 'name': 'test_data'}]return []class UserService:"""用户服务 - 业务逻辑层"""def __init__(self, db_manager: DatabaseManager):self.db_manager = db_managerdef create_user(self, username: str, email: str) -> Dict:"""创建用户"""if not username or not email:raise ValueError("用户名和邮箱不能为空")if '@' not in email:raise ValueError("邮箱格式不正确")# 检查用户是否已存在existing_users = self.db_manager.execute_query("SELECT * FROM users WHERE username = ? OR email = ?",(username, email))if existing_users:raise ValueError("用户名或邮箱已存在")# 创建新用户user_data = {'username': username,'email': email,'created_at': time.time()}self.db_manager.execute_query("INSERT INTO users (username, email, created_at) VALUES (?, ?, ?)",(username, email, user_data['created_at']))return user_datadef get_user_by_username(self, username: str) -> Dict:"""根据用户名获取用户"""users = self.db_manager.execute_query("SELECT * FROM users WHERE username = ?",(username,))if not users:raise ValueError(f"用户 {username} 不存在")return users[0]# pytest夹具 - 标准化检测环境@pytest.fixturedef db_manager():"""数据库管理器夹具 - 标准检测环境"""manager = DatabaseManager("sqlite:///:memory:")manager.connect()yield managermanager.disconnect()@pytest.fixturedef user_service(db_manager):"""用户服务夹具"""return UserService(db_manager)@pytest.fixturedef test_environment():"""测试环境夹具"""with tempfile.TemporaryDirectory() as temp_dir:env = TestEnvironment(database_url="sqlite:///:memory:",api_endpoint="http://localhost:8000",test_data_path=temp_dir)yield env# 自动清理@pytest.fixture(scope="session")def test_config():"""会话级配置夹具"""config = {'timeout': 30,'retry_count': 3,'debug_mode': True}return config# 参数化测试 - 批量检测class TestUserValidation:"""用户验证测试 - 输入数据质量检测"""@pytest.mark.parametrize("username,email,expected", [("valid_user", "user@example.com", True),("test123", "test@domain.org", True),("", "user@example.com", False), # 空用户名("valid_user", "", False), # 空邮箱("valid_user", "invalid_email", False), # 无效邮箱("user with spaces", "user@example.com", True), # 包含空格的用户名])def test_user_creation_validation(self, user_service, username, email, expected):"""参数化测试用户创建验证"""if expected:# 期望成功user = user_service.create_user(username, email)assert user['username'] == usernameassert user['email'] == emailassert 'created_at' in userelse:# 期望失败with pytest.raises(ValueError):user_service.create_user(username, email)@pytest.mark.parametrize("email", ["user@example.com","test.email@domain.co.uk","firstname+lastname@company.org","user.name123@test-domain.com"])def test_valid_email_formats(self, user_service, email):"""测试各种有效邮箱格式"""user = user_service.create_user("testuser", email)assert user['email'] == email@pytest.mark.parametrize("invalid_email", ["plainaddress","@missingdomain.com","missing@.com","spaces in@email.com","double..dot@domain.com"])def test_invalid_email_formats(self, user_service, invalid_email):"""测试无效邮箱格式"""with pytest.raises(ValueError, match="邮箱格式不正确"):user_service.create_user("testuser", invalid_email)# 测试标记 - 检测类型分类class TestDatabaseOperations:"""数据库操作测试"""@pytest.mark.unitdef test_database_connection(self, db_manager):"""单元测试:数据库连接"""assert db_manager.connected is True@pytest.mark.integrationdef test_user_service_integration(self, user_service):"""集成测试:用户服务集成"""user = user_service.create_user("integration_user", "int@test.com")retrieved_user = user_service.get_user_by_username("integration_user")assert retrieved_user['username'] == "integration_user"@pytest.mark.slow@pytest.mark.performancedef test_bulk_user_creation(self, user_service):"""性能测试:批量用户创建"""start_time = time.time()for i in range(100):user_service.create_user(f"user_{i}", f"user_{i}@test.com")execution_time = time.time() - start_timeassert execution_time < 5.0 # 应该在5秒内完成@pytest.mark.securitydef test_sql_injection_prevention(self, user_service):"""安全测试:SQL注入防护"""malicious_input = "'; DROP TABLE users; --"# 应该抛出验证错误,而不是执行SQL注入with pytest.raises(ValueError):user_service.create_user(malicious_input, "test@example.com")# Mock测试 - 模拟外部依赖class ExternalAPIService:"""外部API服务"""def __init__(self, base_url: str):self.base_url = base_urldef send_welcome_email(self, email: str, username: str) -> bool:"""发送欢迎邮件"""response = requests.post(f"{self.base_url}/send-email",json={'to': email,'subject': f'欢迎, {username}!','template': 'welcome'})return response.status_code == 200def verify_email_domain(self, email: str) -> bool:"""验证邮箱域名"""domain = email.split('@')[1]response = requests.get(f"{self.base_url}/verify-domain/{domain}")return response.json().get('valid', False)class EnhancedUserService(UserService):"""增强用户服务 - 包含外部API调用"""def __init__(self, db_manager: DatabaseManager, api_service: ExternalAPIService):super().__init__(db_manager)self.api_service = api_servicedef create_user_with_verification(self, username: str, email: str) -> Dict:"""创建用户并验证邮箱域名"""# 验证邮箱域名if not self.api_service.verify_email_domain(email):raise ValueError("邮箱域名无效")# 创建用户user = self.create_user(username, email)# 发送欢迎邮件email_sent = self.api_service.send_welcome_email(email, username)user['welcome_email_sent'] = email_sentreturn userclass TestMockingExternalServices:"""模拟外部服务测试"""@pytest.fixturedef mock_api_service(self):"""模拟API服务夹具"""return Mock(spec=ExternalAPIService)@pytest.fixturedef enhanced_user_service(self, db_manager, mock_api_service):"""增强用户服务夹具"""return EnhancedUserService(db_manager, mock_api_service)def test_user_creation_with_valid_domain(self, enhanced_user_service, mock_api_service):"""测试有效域名的用户创建"""# 配置Mock返回值mock_api_service.verify_email_domain.return_value = Truemock_api_service.send_welcome_email.return_value = Trueuser = enhanced_user_service.create_user_with_verification("testuser", "test@valid-domain.com")# 验证方法调用mock_api_service.verify_email_domain.assert_called_once_with("test@valid-domain.com")mock_api_service.send_welcome_email.assert_called_once_with("test@valid-domain.com", "testuser")assert user['welcome_email_sent'] is Truedef test_user_creation_with_invalid_domain(self, enhanced_user_service, mock_api_service):"""测试无效域名的用户创建"""# 配置Mock返回无效域名mock_api_service.verify_email_domain.return_value = Falsewith pytest.raises(ValueError, match="邮箱域名无效"):enhanced_user_service.create_user_with_verification("testuser", "test@invalid-domain.com")# 验证只调用了域名验证,没有发送邮件mock_api_service.verify_email_domain.assert_called_once()mock_api_service.send_welcome_email.assert_not_called()@patch('requests.post')@patch('requests.get')def test_with_requests_patches(self, mock_get, mock_post, user_service):"""使用patch装饰器模拟HTTP请求"""# 配置HTTP响应mock_get.return_value.json.return_value = {'valid': True}mock_post.return_value.status_code = 200api_service = ExternalAPIService("http://test-api.com")enhanced_service = EnhancedUserService(user_service.db_manager, api_service)user = enhanced_service.create_user_with_verification("testuser", "test@example.com")# 验证HTTP调用mock_get.assert_called_once()mock_post.assert_called_once()assert user['welcome_email_sent'] is True# 测试数据工厂class TestDataFactory:"""测试数据工厂 - 标准化测试材料生产"""@staticmethoddef create_user_data(username: str = None, email: str = None) -> Dict:"""创建用户测试数据"""import randomimport stringif username is None:username = ''.join(random.choices(string.ascii_lowercase, k=8))if email is None:domain = random.choice(['example.com', 'test.org', 'demo.net'])email = f"{username}@{domain}"return {'username': username,'email': email,'password': 'Test123!@#','full_name': f'Test User {username.title()}','age': random.randint(18, 80),'city': random.choice(['北京', '上海', '广州', '深圳'])}@staticmethoddef create_multiple_users(count: int) -> List[Dict]:"""批量创建用户测试数据"""return [TestDataFactory.create_user_data() for _ in range(count)]class TestWithDataFactory:"""使用测试数据工厂的测试"""def test_single_user_creation(self, user_service):"""测试单个用户创建"""user_data = TestDataFactory.create_user_data()user = user_service.create_user(user_data['username'], user_data['email'])assert user['username'] == user_data['username']assert user['email'] == user_data['email']def test_multiple_users_creation(self, user_service):"""测试批量用户创建"""users_data = TestDataFactory.create_multiple_users(5)for user_data in users_data:user = user_service.create_user(user_data['username'], user_data['email'])assert user['username'] == user_data['username']# 测试配置和运行器class PytestAdvancedRunner:"""pytest高级运行器"""def __init__(self, test_directory: str = "."):self.test_directory = test_directoryself.results = {}def run_unit_tests(self):"""运行单元测试"""return self._run_tests_with_marker("unit")def run_integration_tests(self):"""运行集成测试"""return self._run_tests_with_marker("integration")def run_performance_tests(self):"""运行性能测试"""return self._run_tests_with_marker("performance")def run_security_tests(self):"""运行安全测试"""return self._run_tests_with_marker("security")def _run_tests_with_marker(self, marker: str):"""运行指定标记的测试"""# 这里是概念性演示,实际使用时会调用pytestprint(f"🧪 运行 {marker} 测试...")# 模拟测试结果return {'marker': marker,'passed': 8,'failed': 0,'skipped': 2,'duration': 2.5}def generate_coverage_report(self):"""生成覆盖率报告"""return {'total_coverage': 95.5,'statements': 1000,'missing': 45,'excluded': 0,'branches': 250,'partial': 12}# pytest演示程序def demo_pytest_features():"""pytest特性演示"""print("=== pytest高级特性演示 ===\n")print("🔬 pytest就像专业的质量检测设备:")print("1. 🧪 测试夹具 - 标准化检测环境")print("2. 📊 参数化测试 - 批量检测能力")print("3. 🏷️ 测试标记 - 检测类型分类")print("4. 🎭 Mock模拟 - 隔离测试环境")print("5. 🏭 数据工厂 - 标准化测试材料")print("\n📊 测试分类执行演示:")runner = PytestAdvancedRunner()# 运行不同类型的测试test_types = [("单元测试", "unit"),("集成测试", "integration"),("性能测试", "performance"),("安全测试", "security")]for test_name, marker in test_types:result = runner._run_tests_with_marker(marker)print(f"✅ {test_name}: {result['passed']}通过 {result['failed']}失败 "f"({result['duration']:.1f}s)")print("\n📈 测试覆盖率报告:")coverage = runner.generate_coverage_report()print(f"✅ 总覆盖率: {coverage['total_coverage']:.1f}%")print(f" 代码行数: {coverage['statements']}")print(f" 未覆盖: {coverage['missing']}")print(f" 分支覆盖: {coverage['branches'] - coverage['partial']}/{coverage['branches']}")print("\n🎯 pytest核心优势:")print("✅ 简洁语法: assert语句即可完成断言")print("✅ 强大夹具: 自动 化测试环境管理")print("✅ 参数化: 一套测试代码验证多种情况")print("✅ 插件生态: 丰富的扩展插件支持")print("✅ 灵活标记: 灵活的测试分类和执行")print("\n💡 最佳实践建议:")print("🔹 使用夹具管理测试依赖")print("🔹 参数化测试覆盖边界情况")print("🔹 合理使用Mock隔离外部依赖")print("🔹 建立测试数据工厂标准化数据")print("🔹 使用标记分类管理不同类型测试")# 运行演示if __name__ == "__main__":demo_pytest_features()
18.2 全面测试策略 - "多层次质量保障体系"
18.2.1 测试金字塔架构 - "质量检测层级体系"
就像建筑工程有地基检测、结构检测、外观检测等多个层级一样,软件质量保障也需要建立多层次的测试体系。测试金字塔为我们提供了一个科学的测试策略框架。
"""测试金字塔架构实现 - 多层次质量保障体系这个模块演示了完整的测试金字塔架构:1. 单元测试 - 地基检测(快速、大量)2. 集成测试 - 结构检测(中等速度、适量)3. 系统测试 - 整体检测(较慢、少量)4. 验收测试 - 交付检测(最慢、最少)"""import unittestimport pytestimport timeimport jsonimport requestsfrom unittest.mock import Mock, patch, MagicMockfrom typing import List, Dict, Any, Optionalfrom dataclasses import dataclassfrom abc import ABC, abstractmethodimport loggingfrom contextlib import contextmanagerimport threadingimport subprocessimport tempfilefrom pathlib import Path# 测试配置@dataclassclass TestConfiguration:"""测试配置"""unit_test_timeout: float = 1.0integration_test_timeout: float = 10.0system_test_timeout: float = 60.0acceptance_test_timeout: float = 300.0parallel_execution: bool = Truecoverage_threshold: float = 80.0# 业务逻辑层 - 被测试的核心系统class Calculator:"""计算器 - 核心业务逻辑"""def add(self, a: float, b: float) -> float:"""加法"""return a + bdef subtract(self, a: float, b: float) -> float:"""减法"""return a - bdef multiply(self, a: float, b: float) -> float:"""乘法"""return a * bdef divide(self, a: float, b: float) -> float:"""除法"""if b == 0:raise ValueError("除数不能为零")return a / bdef power(self, base: float, exponent: float) -> float:"""幂运算"""return base ** exponentclass BankAccount:"""银行账户 - 业务实体"""def __init__(self, account_number: str, initial_balance: float = 0.0):self.account_number = account_numberself._balance = initial_balanceself.transaction_history = []@propertydef balance(self) -> float:"""获取余额"""return self._balancedef deposit(self, amount: float) -> bool:"""存款"""if amount <= 0:raise ValueError("存款金额必须大于零")self._balance += amountself.transaction_history.append({'type': 'deposit','amount': amount,'timestamp': time.time(),'balance_after': self._balance})return Truedef withdraw(self, amount: float) -> bool:"""取款"""if amount <= 0:raise ValueError("取款金额必须大于零")if amount > self._balance:raise ValueError("余额不足")self._balance -= amountself.transaction_history.append({'type': 'withdraw','amount': amount,'timestamp': time.time(),'balance_after': self._balance})return Trueclass BankService:"""银行服务 - 业务服务层"""def __init__(self):self.accounts: Dict[str, BankAccount] = {}self.external_api_client = Nonedef create_account(self, account_number: str, initial_balance: float = 0.0) -> BankAccount:"""创建账户"""if account_number in self.accounts:raise ValueError("账户已存在")account = BankAccount(account_number, initial_balance)self.accounts[account_number] = accountreturn accountdef get_account(self, account_number: str) -> BankAccount:"""获取账户"""if account_number not in self.accounts:raise ValueError("账户不存在")return self.accounts[account_number]def transfer(self, from_account: str, to_account: str, amount: float) -> bool:"""转账"""from_acc = self.get_account(from_account)to_acc = self.get_account(to_account)# 原子操作:要么全部成功,要么全部失败try:from_acc.withdraw(amount)to_acc.deposit(amount)return Trueexcept Exception as e:# 回滚操作raise e# 第一层:单元测试 - 地基检测class TestCalculatorUnit(unittest.TestCase):"""计算器单元测试 - 最基础的组件测试"""def setUp(self):"""测试前准备"""self.calculator = Calculator()def test_add_positive_numbers(self):"""测试正数加法"""result = self.calculator.add(2, 3)self.assertEqual(result, 5)def test_add_negative_numbers(self):"""测试负数加法"""result = self.calculator.add(-2, -3)self.assertEqual(result, -5)def test_subtract_positive_numbers(self):"""测试正数减法"""result = self.calculator.subtract(5, 3)self.assertEqual(result, 2)def test_multiply_numbers(self):"""测试乘法"""result = self.calculator.multiply(4, 5)self.assertEqual(result, 20)def test_divide_normal_case(self):"""测试正常除法"""result = self.calculator.divide(10, 2)self.assertEqual(result, 5)def test_divide_by_zero(self):"""测试除零异常"""with self.assertRaises(ValueError):self.calculator.divide(10, 0)def test_power_operation(self):"""测试幂运算"""result = self.calculator.power(2, 3)self.assertEqual(result, 8)class TestBankAccountUnit(unittest.TestCase):"""银行账户单元测试"""def setUp(self):"""测试前准备"""self.account = BankAccount("ACC001", 1000.0)def test_initial_balance(self):"""测试初始余额"""self.assertEqual(self.account.balance, 1000.0)def test_deposit_valid_amount(self):"""测试有效存款"""self.account.deposit(500.0)self.assertEqual(self.account.balance, 1500.0)def test_deposit_invalid_amount(self):"""测试无效存款"""with self.assertRaises(ValueError):self.account.deposit(-100.0)with self.assertRaises(ValueError):self.account.deposit(0.0)def test_withdraw_valid_amount(self):"""测试有效取款"""self.account.withdraw(300.0)self.assertEqual(self.account.balance, 700.0)def test_withdraw_insufficient_funds(self):"""测试余额不足"""with self.assertRaises(ValueError):self.account.withdraw(1500.0)def test_transaction_history(self):"""测试交易历史"""self.account.deposit(200.0)self.account.withdraw(100.0)self.assertEqual(len(self.account.transaction_history), 2)self.assertEqual(self.account.transaction_history[0]['type'], 'deposit')self.assertEqual(self.account.transaction_history[1]['type'], 'withdraw')# 第二层:集成测试 - 结构检测class TestBankServiceIntegration(unittest.TestCase):"""银行服务集成测试 - 多个组件协作测试"""def setUp(self):"""测试前准备"""self.bank_service = BankService()def test_create_and_get_account(self):"""测试创建和获取账户的集成"""# 创建账户account = self.bank_service.create_account("ACC001", 1000.0)self.assertEqual(account.account_number, "ACC001")self.assertEqual(account.balance, 1000.0)# 获取账户retrieved_account = self.bank_service.get_account("ACC001")self.assertEqual(retrieved_account.account_number, "ACC001")self.assertEqual(retrieved_account.balance, 1000.0)def test_account_operations_integration(self):"""测试账户操作集成"""# 创建账户self.bank_service.create_account("ACC001", 1000.0)account = self.bank_service.get_account("ACC001")# 执行操作account.deposit(500.0)account.withdraw(200.0)# 验证最终状态self.assertEqual(account.balance, 1300.0)self.assertEqual(len(account.transaction_history), 2)def test_transfer_between_accounts(self):"""测试账户间转账集成"""# 准备两个账户self.bank_service.create_account("ACC001", 1000.0)self.bank_service.create_account("ACC002", 500.0)# 执行转账success = self.bank_service.transfer("ACC001", "ACC002", 300.0)# 验证转账结果self.assertTrue(success)self.assertEqual(self.bank_service.get_account("ACC001").balance, 700.0)self.assertEqual(self.bank_service.get_account("ACC002").balance, 800.0)def test_transfer_insufficient_funds(self):"""测试转账余额不足集成"""# 准备两个账户self.bank_service.create_account("ACC001", 100.0)self.bank_service.create_account("ACC002", 500.0)# 尝试转账超过余额的金额with self.assertRaises(ValueError):self.bank_service.transfer("ACC001", "ACC002", 300.0)# 验证账户余额未改变self.assertEqual(self.bank_service.get_account("ACC001").balance, 100.0)self.assertEqual(self.bank_service.get_account("ACC002").balance, 500.0)# 第三层:系统测试 - 整体检测class BankAPIServer:"""银行API服务器 - 系统级组件"""def __init__(self, bank_service: BankService):self.bank_service = bank_serviceself.is_running = Falsedef start(self):"""启动服务器"""self.is_running = Truereturn Truedef stop(self):"""停止服务器"""self.is_running = Falsereturn Truedef handle_create_account(self, request_data: Dict) -> Dict:"""处理创建账户请求"""try:account_number = request_data['account_number']initial_balance = request_data.get('initial_balance', 0.0)account = self.bank_service.create_account(account_number, initial_balance)return {'status': 'success','data': {'account_number': account.account_number,'balance': account.balance}}except Exception as e:return {'status': 'error','message': str(e)}def handle_get_balance(self, account_number: str) -> Dict:"""处理查询余额请求"""try:account = self.bank_service.get_account(account_number)return {'status': 'success','data': {'account_number': account.account_number,'balance': account.balance}}except Exception as e:return {'status': 'error','message': str(e)}def handle_transfer(self, request_data: Dict) -> Dict:"""处理转账请求"""try:from_account = request_data['from_account']to_account = request_data['to_account']amount = request_data['amount']success = self.bank_service.transfer(from_account, to_account, amount)return {'status': 'success' if success else 'failed','data': {'from_account': from_account,'to_account': to_account,'amount': amount}}except Exception as e:return {'status': 'error','message': str(e)}class TestBankSystemEnd2End(unittest.TestCase):"""银行系统端到端测试 - 完整系统测试"""def setUp(self):"""测试前准备"""self.bank_service = BankService()self.api_server = BankAPIServer(self.bank_service)self.api_server.start()def tearDown(self):"""测试后清理"""self.api_server.stop()def test_complete_banking_workflow(self):"""测试完整银行业务流程"""# 步骤1:创建两个账户create_acc1_response = self.api_server.handle_create_account({'account_number': 'ACC001','initial_balance': 1000.0})self.assertEqual(create_acc1_response['status'], 'success')create_acc2_response = self.api_server.handle_create_account({'account_number': 'ACC002','initial_balance': 500.0})self.assertEqual(create_acc2_response['status'], 'success')# 步骤2:查询账户余额balance1_response = self.api_server.handle_get_balance('ACC001')self.assertEqual(balance1_response['status'], 'success')self.assertEqual(balance1_response['data']['balance'], 1000.0)# 步骤3:执行转账transfer_response = self.api_server.handle_transfer({'from_account': 'ACC001','to_account': 'ACC002','amount': 300.0})self.assertEqual(transfer_response['status'], 'success')# 步骤4:验证转账后余额balance1_after = self.api_server.handle_get_balance('ACC001')balance2_after = self.api_server.handle_get_balance('ACC002')self.assertEqual(balance1_after['data']['balance'], 700.0)self.assertEqual(balance2_after['data']['balance'], 800.0)def test_error_handling_workflow(self):"""测试错误处理流程"""# 尝试查询不存在的账户balance_response = self.api_server.handle_get_balance('NONEXISTENT')self.assertEqual(balance_response['status'], 'error')# 尝试创建重复账户self.api_server.handle_create_account({'account_number': 'ACC001','initial_balance': 1000.0})duplicate_response = self.api_server.handle_create_account({'account_number': 'ACC001','initial_balance': 500.0})self.assertEqual(duplicate_response['status'], 'error')# 第四层:验收测试 - 交付检测class BankAcceptanceTestSuite:"""银行系统验收测试套件 - 用户角度的完整测试"""def __init__(self):self.test_results = []self.bank_service = BankService()self.api_server = BankAPIServer(self.bank_service)def setup_test_environment(self):"""设置测试环境"""self.api_server.start()print("🏦 银行系统测试环境已启动")def teardown_test_environment(self):"""清理测试环境"""self.api_server.stop()print("🔚 银行系统测试环境已关闭")def test_new_customer_onboarding(self):"""测试新客户入驻流程"""print("\n🧪 测试场景:新客户入驻")# 用户故事:作为新客户,我想开设账户并进行首次存款try:# 步骤1:开设账户response = self.api_server.handle_create_account({'account_number': 'NEW_CUSTOMER_001','initial_balance': 0.0})assert response['status'] == 'success', "账户创建失败"print("✅ 账户创建成功")# 步骤2:查询初始余额balance_response = self.api_server.handle_get_balance('NEW_CUSTOMER_001')assert balance_response['status'] == 'success', "余额查询失败"assert balance_response['data']['balance'] == 0.0, "初始余额不正确"print("✅ 初始余额查询正确")# 步骤3:首次存款account = self.bank_service.get_account('NEW_CUSTOMER_001')account.deposit(1000.0)# 步骤4:验证存款后余额balance_after = self.api_server.handle_get_balance('NEW_CUSTOMER_001')assert balance_after['data']['balance'] == 1000.0, "存款后余额不正确"print("✅ 首次存款成功")self.test_results.append({'test_name': '新客户入驻流程','status': 'PASSED','message': '所有步骤执行正常'})except Exception as e:self.test_results.append({'test_name': '新客户入驻流程','status': 'FAILED','message': str(e)})print(f"❌ 测试失败: {e}")def test_daily_banking_operations(self):"""测试日常银行操作"""print("\n🧪 测试场景:日常银行操作")try:# 准备测试数据self.api_server.handle_create_account({'account_number': 'DAILY_USER_001','initial_balance': 2000.0})self.api_server.handle_create_account({'account_number': 'DAILY_USER_002','initial_balance': 3000.0})# 用户故事:作为日常用户,我需要进行存款、取款、转账操作# 存款操作account1 = self.bank_service.get_account('DAILY_USER_001')account1.deposit(500.0)print("✅ 存款操作完成")# 取款操作account1.withdraw(300.0)print("✅ 取款操作完成")# 转账操作self.bank_service.transfer('DAILY_USER_001', 'DAILY_USER_002', 200.0)print("✅ 转账操作完成")# 验证最终状态balance1 = self.api_server.handle_get_balance('DAILY_USER_001')balance2 = self.api_server.handle_get_balance('DAILY_USER_002')expected_balance1 = 2000.0 + 500.0 - 300.0 - 200.0 # 2000expected_balance2 = 3000.0 + 200.0 # 3200assert balance1['data']['balance'] == expected_balance1, f"用户1余额不正确,期望{expected_balance1},实际{balance1['data']['balance']}"assert balance2['data']['balance'] == expected_balance2, f"用户2余额不正确,期望{expected_balance2},实际{balance2['data']['balance']}"print("✅ 余额验证正确")self.test_results.append({'test_name': '日常银行操作','status': 'PASSED','message': '所有操作执行正常'})except Exception as e:self.test_results.append({'test_name': '日常银行操作','status': 'FAILED','message': str(e)})print(f"❌ 测试失败: {e}")def test_error_scenarios(self):"""测试错误场景"""print("\n🧪 测试场景:错误处理")try:# 准备测试账户self.api_server.handle_create_account({'account_number': 'ERROR_TEST_001','initial_balance': 100.0})# 测试余额不足转账try:self.bank_service.transfer('ERROR_TEST_001', 'DAILY_USER_001', 200.0)assert False, "应该抛出余额不足异常"except ValueError:print("✅ 余额不足错误处理正确")# 测试无效存款account = self.bank_service.get_account('ERROR_TEST_001')try:account.deposit(-50.0)assert False, "应该抛出无效存款异常"except ValueError:print("✅ 无效存款错误处理正确")# 测试查询不存在账户response = self.api_server.handle_get_balance('NONEXISTENT_ACCOUNT')assert response['status'] == 'error', "应该返回错误状态"print("✅ 账户不存在错误处理正确")self.test_results.append({'test_name': '错误场景处理','status': 'PASSED','message': '所有错误场景处理正确'})except Exception as e:self.test_results.append({'test_name': '错误场景处理','status': 'FAILED','message': str(e)})print(f"❌ 测试失败: {e}")def run_all_acceptance_tests(self):"""运行所有验收测试"""print("🚀 开始银行系统验收测试")self.setup_test_environment()try:self.test_new_customer_onboarding()self.test_daily_banking_operations()self.test_error_scenarios()finally:self.teardown_test_environment()return self.generate_acceptance_report()def generate_acceptance_report(self):"""生成验收测试报告"""passed_count = len([r for r in self.test_results if r['status'] == 'PASSED'])failed_count = len([r for r in self.test_results if r['status'] == 'FAILED'])total_count = len(self.test_results)report = {'total_tests': total_count,'passed': passed_count,'failed': failed_count,'success_rate': (passed_count / total_count * 100) if total_count > 0 else 0,'details': self.test_results}print(f"\n📊 验收测试报告:")print(f" 总测试数: {total_count}")print(f" 通过数: {passed_count}")print(f" 失败数: {failed_count}")print(f" 成功率: {report['success_rate']:.1f}%")return report# 测试金字塔管理器class TestPyramidManager:"""测试金字塔管理器 - 统一测试执行和报告"""def __init__(self, config: TestConfiguration):self.config = configself.test_results = {'unit': [],'integration': [],'system': [],'acceptance': []}def run_unit_tests(self):"""运行单元测试层"""print("🔬 运行单元测试 (地基检测)")# 创建测试套件suite = unittest.TestSuite()suite.addTest(unittest.makeSuite(TestCalculatorUnit))suite.addTest(unittest.makeSuite(TestBankAccountUnit))# 运行测试runner = unittest.TextTestRunner(verbosity=0)result = runner.run(suite)self.test_results['unit'] = {'tests_run': result.testsRun,'failures': len(result.failures),'errors': len(result.errors),'success_rate': ((result.testsRun - len(result.failures) - len(result.errors)) / result.testsRun * 100) if result.testsRun > 0 else 0}print(f"✅ 单元测试完成: {result.testsRun}个测试,{len(result.failures)}个失败,{len(result.errors)}个错误")return self.test_results['unit']def run_integration_tests(self):"""运行集成测试层"""print("🔧 运行集成测试 (结构检测)")suite = unittest.TestSuite()suite.addTest(unittest.makeSuite(TestBankServiceIntegration))runner = unittest.TextTestRunner(verbosity=0)result = runner.run(suite)self.test_results['integration'] = {'tests_run': result.testsRun,'failures': len(result.failures),'errors': len(result.errors),'success_rate': ((result.testsRun - len(result.failures) - len(result.errors)) / result.testsRun * 100) if result.testsRun > 0 else 0}print(f"✅ 集成测试完成: {result.testsRun}个测试,{len(result.failures)}个失败,{len(result.errors)}个错误")return self.test_results['integration']def run_system_tests(self):"""运行系统测试层"""print("🏗️ 运行系统测试 (整体检测)")suite = unittest.TestSuite()suite.addTest(unittest.makeSuite(TestBankSystemEnd2End))runner = unittest.TextTestRunner(verbosity=0)result = runner.run(suite)self.test_results['system'] = {'tests_run': result.testsRun,'failures': len(result.failures),'errors': len(result.errors),'success_rate': ((result.testsRun - len(result.failures) - len(result.errors)) / result.testsRun * 100) if result.testsRun > 0 else 0}print(f"✅ 系统测试完成: {result.testsRun}个测试,{len(result.failures)}个失败,{len(result.errors)}个错误")return self.test_results['system']def run_acceptance_tests(self):"""运行验收测试层"""print("🎯 运行验收测试 (交付检测)")acceptance_suite = BankAcceptanceTestSuite()report = acceptance_suite.run_all_acceptance_tests()self.test_results['acceptance'] = {'tests_run': report['total_tests'],'failures': report['failed'],'errors': 0,'success_rate': report['success_rate']}return self.test_results['acceptance']def run_complete_test_pyramid(self):"""运行完整测试金字塔"""print("🏗️ 开始执行完整测试金字塔")print("=" * 50)start_time = time.time()# 按金字塔顺序执行测试self.run_unit_tests()print()self.run_integration_tests()print()self.run_system_tests()print()self.run_acceptance_tests()print()total_time = time.time() - start_time# 生成综合报告return self.generate_pyramid_report(total_time)def generate_pyramid_report(self, execution_time: float):"""生成测试金字塔报告"""report = {'execution_time': execution_time,'layers': self.test_results,'summary': {}}# 计算总体统计total_tests = sum(layer['tests_run'] for layer in self.test_results.values())total_failures = sum(layer['failures'] for layer in self.test_results.values())total_errors = sum(layer['errors'] for layer in self.test_results.values())report['summary'] = {'total_tests': total_tests,'total_passed': total_tests - total_failures - total_errors,'total_failures': total_failures,'total_errors': total_errors,'overall_success_rate': ((total_tests - total_failures - total_errors) / total_tests * 100) if total_tests > 0 else 0}# 打印报告print("📊 测试金字塔执行报告")print("=" * 50)print(f"⏱️ 总执行时间: {execution_time:.2f}秒")print(f"📊 总测试数: {total_tests}")print(f"✅ 通过: {report['summary']['total_passed']}")print(f"❌ 失败: {total_failures}")print(f"💥 错误: {total_errors}")print(f"📈 总成功率: {report['summary']['overall_success_rate']:.1f}%")print("\n📋 各层详细结果:")layer_names = {'unit': '单元测试层','integration': '集成测试层','system': '系统测试层','acceptance': '验收测试层'}for layer_key, layer_data in self.test_results.items():layer_name = layer_names[layer_key]print(f" {layer_name}: {layer_data['tests_run']}个测试, "f"成功率{layer_data['success_rate']:.1f}%")print("\n💡 测试金字塔最佳实践:")print(" 🔬 单元测试: 快速反馈,大量执行")print(" 🔧 集成测试: 组件协作,适量覆盖")print(" 🏗️ 系统测试: 端到端验证,重点场景")print(" 🎯 验收测试: 用户角度,关键流程")return report# 演示程序def demo_test_pyramid():"""测试金字塔演示"""print("=== 测试金字塔架构演示 ===\n")print("🏗️ 测试金字塔 - 就像建筑工程的多层次质量检测:")print("📊 层次结构 (从下到上):")print(" 4️⃣ 验收测试 (用户视角) - 交付检测 - 少量、慢速")print(" 3️⃣ 系统测试 (端到端) - 整体检测 - 适量、中速")print(" 2️⃣ 集成测试 (组件间) - 结构检测 - 较多、较快")print(" 1️⃣ 单元测试 (组件内) - 地基检测 - 大量、快速")print("\n🎯 每层的特点和作用:")print("✅ 越往下层,测试数量越多,执行速度越快")print("✅ 越往上层,测试范围越广,更接近用户场景")print("✅ 底层发现问题成本低,顶层发现问题影响大")print("✅ 各层相互补充,形成完整的质量保障体系")# 运行完整测试金字塔config = TestConfiguration()manager = TestPyramidManager(config)print("\n🚀 执行完整测试金字塔:")report = manager.run_complete_test_pyramid()return report# 运行演示if __name__ == "__main__":demo_test_pyramid()