欢迎来到巧克力测试平台的"武功秘籍"!这里汇集了前人栽树的经验,让后人乘凉的智慧。掌握这些最佳实践,让你的代码更优雅、更高效、更可维护!
- 单一职责原则 (SRP): 一个类只负责一个功能
- 开闭原则 (OCP): 对扩展开放,对修改关闭
- 里氏替换原则 (LSP): 子类可以替换父类
- 接口隔离原则 (ISP): 使用多个专门的接口
- 依赖倒置原则 (DIP): 依赖抽象而不是具体实现
Don't Repeat Yourself - 避免重复代码,提取公共逻辑
Keep It Simple, Stupid - 保持简单,避免过度设计
// ❌ 不好的设计 - 职责混乱
const UserManagement = () => {
// 用户列表逻辑
// 用户表单逻辑
// 权限管理逻辑
// 数据统计逻辑
};
// ✅ 好的设计 - 职责清晰
const UserList = () => {
// 只负责用户列表展示
};
const UserForm = () => {
// 只负责用户表单
};
const UserPermission = () => {
// 只负责权限管理
};// ✅ 清晰的Props接口
interface TestCaseListProps {
dataSource: TestCase[];
loading?: boolean;
onEdit?: (record: TestCase) => void;
onDelete?: (id: number) => void;
onRefresh?: () => void;
}
const TestCaseList: React.FC<TestCaseListProps> = ({
dataSource,
loading = false,
onEdit,
onDelete,
onRefresh,
}) => {
// 组件实现
};// ✅ 提取业务逻辑到自定义Hook
const useTestCaseList = () => {
const [dataSource, setDataSource] = useState<TestCase[]>([]);
const [loading, setLoading] = useState(false);
const fetchData = useCallback(async (params: any) => {
setLoading(true);
try {
const response = await getCase(params);
setDataSource(response.data);
} catch (error) {
message.error('获取数据失败');
} finally {
setLoading(false);
}
}, []);
return {
dataSource,
loading,
fetchData,
};
};// ✅ 将共享状态提升到父组件
const TestSuiteManagement = () => {
const [selectedSuite, setSelectedSuite] = useState<TestSuite | null>(null);
return (
<div>
<TestSuiteList
onSelect={setSelectedSuite}
selectedId={selectedSuite?.id}
/>
<TestSuiteDetail
suite={selectedSuite}
onUpdate={handleUpdate}
/>
</div>
);
};interface TestPlanState {
plans: TestPlan[];
loading: boolean;
selectedPlan: TestPlan | null;
filters: FilterParams;
}
type TestPlanAction =
| { type: 'SET_LOADING'; payload: boolean }
| { type: 'SET_PLANS'; payload: TestPlan[] }
| { type: 'SELECT_PLAN'; payload: TestPlan }
| { type: 'UPDATE_FILTERS'; payload: Partial<FilterParams> };
const testPlanReducer = (state: TestPlanState, action: TestPlanAction): TestPlanState => {
switch (action.type) {
case 'SET_LOADING':
return { ...state, loading: action.payload };
case 'SET_PLANS':
return { ...state, plans: action.payload };
// ... 其他case
default:
return state;
}
};// ✅ 避免不必要的重渲染
const TestCaseItem = React.memo<TestCaseItemProps>(({
testCase,
onEdit,
onDelete
}) => {
return (
<div>
{/* 组件内容 */}
</div>
);
});const TestCaseList = ({ dataSource, filters }) => {
// ✅ 缓存计算结果
const filteredData = useMemo(() => {
return dataSource.filter(item =>
item.case_func.includes(filters.keyword)
);
}, [dataSource, filters.keyword]);
// ✅ 缓存回调函数
const handleEdit = useCallback((record: TestCase) => {
// 编辑逻辑
}, []);
return (
<ProTable
dataSource={filteredData}
onEdit={handleEdit}
/>
);
};// ✅ 路由级别的代码分割
const TestCaseList = lazy(() => import('./TestCaseList'));
const TestSuiteList = lazy(() => import('./TestSuiteList'));
// 在路由配置中使用
{
path: '/test/case',
component: () => (
<Suspense fallback={<PageLoading />}>
<TestCaseList />
</Suspense>
),
}class ErrorBoundary extends React.Component<
{ children: React.ReactNode },
{ hasError: boolean }
> {
constructor(props: any) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error) {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <div>Something went wrong.</div>;
}
return this.props.children;
}
}// ✅ 统一的错误处理
const useApiCall = <T>(apiFunction: (...args: any[]) => Promise<T>) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const execute = useCallback(async (...args: any[]) => {
setLoading(true);
setError(null);
try {
const result = await apiFunction(...args);
return result;
} catch (err) {
const errorMessage = err instanceof Error ? err.message : '未知错误';
setError(errorMessage);
message.error(errorMessage);
throw err;
} finally {
setLoading(false);
}
}, [apiFunction]);
return { execute, loading, error };
};# ✅ 符合RESTful规范的API设计
@app.route('/api/test_cases', methods=['GET'])
def get_test_cases():
"""获取测试用例列表"""
pass
@app.route('/api/test_cases', methods=['POST'])
def create_test_case():
"""创建测试用例"""
pass
@app.route('/api/test_cases/<int:case_id>', methods=['PUT'])
def update_test_case(case_id):
"""更新测试用例"""
pass
@app.route('/api/test_cases/<int:case_id>', methods=['DELETE'])
def delete_test_case(case_id):
"""删除测试用例"""
passfrom typing import Any, Optional
from dataclasses import dataclass
@dataclass
class ApiResponse:
success: bool
data: Any = None
message: str = ""
code: int = 200
def success_response(data: Any = None, message: str = "操作成功") -> dict:
"""成功响应"""
return ApiResponse(
success=True,
data=data,
message=message
).__dict__
def error_response(message: str, code: int = 400, data: Any = None) -> dict:
"""错误响应"""
return ApiResponse(
success=False,
data=data,
message=message,
code=code
).__dict__# ✅ 使用SQLAlchemy ORM
class TestCase(db.Model):
__tablename__ = 'test_cases'
id = db.Column(db.Integer, primary_key=True)
case_func = db.Column(db.String(255), nullable=False)
case_desc = db.Column(db.Text)
module_id = db.Column(db.Integer, db.ForeignKey('test_modules.id'))
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 关系定义
module = db.relationship('TestModule', backref='test_cases')
def to_dict(self):
"""转换为字典"""
return {
'id': self.id,
'case_func': self.case_func,
'case_desc': self.case_desc,
'module_id': self.module_id,
'created_at': self.created_at.isoformat(),
'updated_at': self.updated_at.isoformat()
}from contextlib import contextmanager
@contextmanager
def db_transaction():
"""数据库事务上下文管理器"""
try:
db.session.begin()
yield db.session
db.session.commit()
except Exception as e:
db.session.rollback()
raise e
finally:
db.session.close()
# 使用示例
def create_test_suite_with_cases(suite_data, case_ids):
with db_transaction() as session:
# 创建测试套件
suite = TestSuite(**suite_data)
session.add(suite)
session.flush() # 获取ID
# 关联测试用例
for case_id in case_ids:
association = SuiteCaseAssociation(
suite_id=suite.id,
case_id=case_id
)
session.add(association)# ✅ 使用预加载避免N+1查询
def get_test_suites_with_cases():
return TestSuite.query.options(
joinedload(TestSuite.test_cases)
).all()
# ✅ 使用索引优化查询
class TestCase(db.Model):
# 添加索引
__table_args__ = (
db.Index('idx_case_func', 'case_func'),
db.Index('idx_module_id', 'module_id'),
db.Index('idx_created_at', 'created_at'),
)from celery import Celery
from celery.utils.log import get_task_logger
logger = get_task_logger(__name__)
@celery.task(bind=True, max_retries=3)
def execute_test_suite(self, suite_id: int, env: str):
"""执行测试套件"""
try:
logger.info(f"开始执行测试套件 {suite_id}")
# 更新任务状态
self.update_state(
state='PROGRESS',
meta={'current': 0, 'total': 100, 'status': '准备中...'}
)
# 执行测试逻辑
result = run_test_suite(suite_id, env)
logger.info(f"测试套件 {suite_id} 执行完成")
return result
except Exception as exc:
logger.error(f"测试套件 {suite_id} 执行失败: {exc}")
# 重试逻辑
if self.request.retries < self.max_retries:
raise self.retry(countdown=60, exc=exc)
else:
# 记录最终失败
record_test_failure(suite_id, str(exc))
raise exc@celery.task
def monitor_test_execution(task_id: str):
"""监控测试执行状态"""
result = celery.AsyncResult(task_id)
if result.state == 'PENDING':
response = {
'state': result.state,
'status': '等待执行...'
}
elif result.state == 'PROGRESS':
response = {
'state': result.state,
'current': result.info.get('current', 0),
'total': result.info.get('total', 1),
'status': result.info.get('status', '')
}
elif result.state == 'SUCCESS':
response = {
'state': result.state,
'result': result.result
}
else:
response = {
'state': result.state,
'error': str(result.info)
}
return responsefrom marshmallow import Schema, fields, validate
class TestCaseSchema(Schema):
case_func = fields.Str(required=True, validate=validate.Length(min=1, max=255))
case_desc = fields.Str(validate=validate.Length(max=1000))
module_id = fields.Int(required=True, validate=validate.Range(min=1))
tags = fields.List(fields.Str())
def create_test_case():
schema = TestCaseSchema()
try:
data = schema.load(request.json)
except ValidationError as err:
return error_response("参数验证失败", data=err.messages)
# 创建测试用例逻辑
passfrom functools import wraps
from flask_jwt_extended import get_jwt_identity, verify_jwt_in_request
def require_permission(permission: str):
"""权限装饰器"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
verify_jwt_in_request()
current_user_id = get_jwt_identity()
if not has_permission(current_user_id, permission):
return error_response("权限不足", code=403)
return f(*args, **kwargs)
return decorated_function
return decorator
@app.route('/api/test_cases', methods=['POST'])
@require_permission('test_case:create')
def create_test_case():
passimport pytest
from unittest.mock import Mock, patch
class TestTestCaseService:
def setup_method(self):
"""每个测试方法前执行"""
self.service = TestCaseService()
def test_create_test_case_success(self):
"""测试创建测试用例成功"""
# Arrange
case_data = {
'case_func': 'test_login',
'case_desc': '测试登录功能',
'module_id': 1
}
# Act
result = self.service.create_test_case(case_data)
# Assert
assert result.success is True
assert result.data['case_func'] == 'test_login'
@patch('app.services.db.session.commit')
def test_create_test_case_db_error(self, mock_commit):
"""测试数据库错误情况"""
# Arrange
mock_commit.side_effect = Exception("Database error")
case_data = {'case_func': 'test_login'}
# Act & Assert
with pytest.raises(Exception):
self.service.create_test_case(case_data)class TestTestCaseAPI:
def test_create_test_case_api(self, client, auth_headers):
"""测试创建测试用例API"""
data = {
'case_func': 'test_api_login',
'case_desc': '测试API登录',
'module_id': 1
}
response = client.post(
'/api/test_cases',
json=data,
headers=auth_headers
)
assert response.status_code == 200
assert response.json['success'] is True
assert response.json['data']['case_func'] == 'test_api_login'- 代码是否遵循项目编码规范
- 是否有适当的错误处理
- 是否有必要的单元测试
- 是否有性能问题
- 是否有安全漏洞
- 是否有适当的注释和文档
# Python代码质量检查
flake8 app/
black --check app/
mypy app/
# JavaScript/TypeScript代码质量检查
eslint src/
prettier --check src/
tsc --noEmit# Python测试覆盖率
pytest --cov=app --cov-report=html
# JavaScript测试覆盖率
npm run test:coverage- 合理使用索引: 为常用查询字段添加索引
- 避免N+1查询: 使用预加载或批量查询
- 分页查询: 大数据量时使用分页
- 连接池: 配置合适的数据库连接池
from flask_caching import Cache
cache = Cache()
@cache.memoize(timeout=300)
def get_test_cases_by_module(module_id):
"""缓存测试用例查询结果"""
return TestCase.query.filter_by(module_id=module_id).all()
# 缓存失效
def update_test_case(case_id, data):
case = TestCase.query.get(case_id)
# 更新逻辑
# 清除相关缓存
cache.delete_memoized(get_test_cases_by_module, case.module_id)- 代码分割: 按路由或功能分割代码
- 懒加载: 延迟加载非关键资源
- 缓存策略: 合理使用浏览器缓存
- 图片优化: 使用适当的图片格式和大小
🎉 最佳实践大师! 你已经掌握了开发的精髓。记住,最佳实践不是教条,而是经验的总结。在实际开发中要灵活运用,持续改进!