Python代码质量之从规范到自动化检查全过程
1. 技术分析1.1 代码质量维度维度描述工具代码风格PEP 8规范black, isort类型检查类型注解检查mypy代码规范最佳实践flake8, pylint安全检查潜在漏洞bandit, safety测试覆盖代码测试比例coverage1.2 工具对比工具功能性能学习曲线black代码格式化快低flake8代码检查快低mypy类型检查中中pylint全面检查慢高ruff快速linting极快低2. 核心功能实现2.1 代码格式化配置123456789101112131415161718192021222324252627282930313233343536373839# pyproject.toml[tool.black]line-length 88target-version [py39,py310,py311]include \.pyi?$exclude /(\.git| \.venv| build| dist)/[tool.isort]profile blackline_length 88known_first_party [src]skip [.venv,build,dist][tool.mypy]python_version 3.9warn_return_any truewarn_unused_configs truedisallow_untyped_defs falseignore_missing_imports true[tool.ruff]line-length 88target-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(),]2.2 单元测试实践12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667importpytestfromtypingimportList, OptionalclassDataValidator:数据验证器staticmethoddefvalidate_email(email:str)-bool:验证邮箱格式importrepatternr^[a-zA-Z0-9._%-][a-zA-Z0-9.-]\.[a-zA-Z]{2,}$returnbool(re.match(pattern, email))staticmethoddefvalidate_positive(value:float)-bool:验证正数returnvalue 0staticmethoddefvalidate_in_range(value:float, min_val:float, max_val:float)-bool:验证范围returnmin_val value max_valclassTestDataValidator:数据验证器测试pytest.mark.parametrize(email,expected, [(testexample.com,True),(user.namedomain.co.uk,True),(invalid-email,False),(domain.com,False),(user,False),(,False),])deftest_validate_email(self, email, expected):assertDataValidator.validate_email(email)expectedpytest.mark.parametrize(value,expected, [(1.0,True),(0.0,False),(-1.0,False),(100.5,True),])deftest_validate_positive(self, value, expected):assertDataValidator.validate_positive(value)expecteddeftest_validate_in_range(self):assertDataValidator.validate_in_range(5,0,10)TrueassertDataValidator.validate_in_range(0,0,10)TrueassertDataValidator.validate_in_range(10,0,10)TrueassertDataValidator.validate_in_range(-1,0,10)FalseassertDataValidator.validate_in_range(11,0,10)FalseclassTestEdgeCases:边界情况测试deftest_empty_string(self):assertDataValidator.validate_email()Falsedeftest_unicode_email(self):assertDataValidator.validate_email(用户例子.广告)Falsedeftest_very_long_email(self):long_emaila*100example.com# 应该能处理但可能返回False取决于具体实现resultDataValidator.validate_email(long_email)assertisinstance(result,bool)2.3 Mock与测试隔离123456789101112131415161718192021222324252627282930313233343536373839404142434445464748fromunittest.mockimportMock, patch, MagicMockimportpytestclassAPIClient:API客户端def__init__(self, base_url:str):self.base_urlbase_urlself.sessionNonedeffetch(self, endpoint:str)-dict:获取数据importrequestsresponserequests.get(f{self.base_url}/{endpoint})returnresponse.json()classTestAPIClient:API客户端测试patch(requests.get)deftest_fetch_success(self, mock_get):测试成功获取mock_responseMock()mock_response.json.return_value{status:success,data: [1,2,3]}mock_get.return_valuemock_responseclientAPIClient(https://api.example.com)resultclient.fetch(users)assertresult[status]successassertresult[data][1,2,3]mock_get.assert_called_once_with(https://api.example.com/users)patch(requests.get)deftest_fetch_error(self, mock_get):测试获取失败mock_get.side_effectConnectionError(Network error)clientAPIClient(https://api.example.com)with pytest.raises(ConnectionError):client.fetch(users)deftest_with_fixture(self, mock_get):使用fixture的测试# fixture在conftest.py中定义resultself.client.fetch(users)assertstatusinresult2.4 性能测试12345678910111213141516171819202122232425262728293031323334353637importpytestimporttimeclassTestPerformance:性能测试deftest_sort_performance(self):测试排序性能importrandom# 生成大量数据data[random.randint(0,10000)for_inrange(10000)]starttime.perf_counter()sorted_datasorted(data)elapsedtime.perf_counter()-start# 应该在1秒内完成assertelapsed 1.0, f排序耗时 {elapsed:.2f}s超过1秒# 验证排序正确性assertsorted_datasorted(data)pytest.mark.benchmarkdeftest_list_comprehension_performance(self, benchmark):基准测试列表推导式resultbenchmark(lambda: [i**2foriinrange(10000)])assertlen(result)10000# conftest.pydefpytest_configure(config):config.addinivalue_line(markers,benchmark: mark test as a benchmark)pytest.fixturedefsample_data():示例数据fixturereturn[iforiinrange(100)]3. 持续集成配置3.1 pre-commit配置123456789101112131415161718192021222324252627282930# .pre-commit-config.yamlrepos:-repo:https://github.com/pre-commit/pre-commit-hooksrev:v4.4.0hooks:-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/blackrev:23.3.0hooks:-id:blacklanguage_version:python3.10-repo:https://github.com/pycqa/isortrev:5.12.0hooks:-id:isortargs:[--profile,black]-repo:https://github.com/astral-sh/ruff-pre-commitrev:v0.0.261hooks:-id:ruffargs:[--fix]-repo:https://github.com/pre-commit/mirrors-mypyrev:v1.3.0hooks:-id:mypyadditional_dependencies:[types-all]3.2 GitHub Actions CI12345678910111213141516171819202122232425262728293031323334353637# .github/workflows/ci.ymlname:CIon:push:branches:[main,develop]pull_request:branches:[main]jobs:test:runs-on:ubuntu-lateststrategy:matrix:python-version:[3.9,3.10,3.11]steps:-uses:actions/checkoutv3-name:Set up Python ${{matrix.python-version}}uses:actions/setup-pythonv4with:python-version:${{matrix.python-version}}-name:Install dependenciesrun:|python -m pip install --upgrade pippip install -e.[dev]-name:Lint with ruffrun:ruff check src/-name:Format check with blackrun:black --check src/-name:Type check with mypyrun:mypy src/-name:Test with pytestrun:|coverage run -m pytest tests/coverage report --fail-under80-name:Upload coverageuses:codecov/codecov-actionv3with:files:./coverage.xml4. 代码质量指标4.1 覆盖率报告1234567891011# 运行测试并生成覆盖率报告$ coverage run-m pytest tests/$ coverage report-mName Stmts Miss Cover Missing-----------------------------------------------------src/validators.py45589%23,45,67src/models.py781285%34,56,78tests/test_validators.py600100%------------------------------------------------------TOTAL1831791%4.2 复杂度分析123456789101112131415161718192021222324252627# 使用radon进行复杂度分析fromradon.metricsimportmi_visit, h_visitfromradon.complexityimportcc_visitdefanalyze_complexity(filepath:str):代码复杂度分析withopen(filepath,r) as f:sourcef.read()# 圈复杂度complexitycc_visit(source)print(圈复杂度:)foritemincomplexity:ifitem.classname:namef{item.classname}.{item.name}else:nameitem.nameprint(f {name}: {item.complexity})# 维护性指数mimi_visit(source, multiTrue)print(f\n维护性指数: {mi:.1f})# Halstead指标fromradon.metricsimporth_visithalsteadh_visit(source)print(f难度: {halstead.difficulty:.1f})5. 最佳实践5.1 代码审查清单12345678-[ ] 代码符合PEP8规范-[ ] 函数和类有docstring-[ ] 类型注解完整-[ ] 单元测试覆盖关键逻辑-[ ] 没有硬编码的魔法数字-[ ] 错误处理适当-[ ] 没有安全漏洞-[ ] 性能符合要求5.2 提交前检查1234567891011121314151617181920#!/bin/bash# pre-commit-check.shset-eecho运行代码检查...# 格式化black --check src/echo✓ 格式化检查通过# 检查importisort --check-only --diffsrc/echo✓ import检查通过# Lintruff check src/echo✓ Lint检查通过# 类型检查mypy src/echo✓ 类型检查通过# 测试pytest tests/ -vecho✓ 测试通过echo所有检查通过!6. 总结代码质量保障要点自动化使用pre-commit和CI/CD自动化检查覆盖率保持80%的测试覆盖率持续改进定期审视和改进代码质量
上一篇/下一篇内容由系统自动关联
返回资讯列表 →