每次课 2 小时,共 20 小时 · 从"能写代码"到"能交付项目",通过 Level 5 晋级标准
核心 20% 内容(7 项)
帕累托法则:掌握以下 7 项,就能完成 Level 5 的 80% 练习。其余内容(CI/CD、日志系统、性能监控、ORM/数据库迁移、认证授权、WebSocket、async/await 等)在练习中自然接触或属于进阶方向,不需要在 Level 5 单独花大量时间学。
|----|-----------------------------------------|---------------------------|-------|
| 序号 | 核心知识 | 为什么重要 | 对应课程 |
| 1 | Git 基本操作(init/add/commit/push/pull) | 版本控制是工程化的第一课,不会 Git 无法协作 | S1-S2 |
| 2 | pytest 测试框架(assert/fixture/parametrize) | 测试是"能跑"和"能交付"的分界线 | S3-S4 |
| 3 | requests HTTP 库 | 现代 Web 开发的基础,调用 API 的标准方式 | S5 |
| 4 | Flask 构建 REST API | 从"用 API"到"造 API"的关键一步 | S6 |
| 5 | .env 环境变量与配置管理 | 敏感信息不入代码是生产级项目的底线 | S7 |
| 6 | 项目结构与打包(init.py / requirements.txt) | 从"单文件脚本"到"可维护项目" | S8 |
| 7 | Docker 基础部署 | "在我机器上能跑"和"在任何机器上都能跑"的区别 | S9 |
Session 1:Git 基础 --- 版本控制入门
时间分配
|------|-------|----------------------------------------------------|
| 阶段 | 时长 | 内容 |
| 概念学习 | 30min | Git 是什么、init/add/commit/status/log/diff、.gitignore |
| 动手练习 | 60min | 初始化仓库、多次提交、查看历史 |
| 复盘 | 30min | 回答复盘问题 |
掌握内容
- Git 安装与配置:
git config --global user.name "名字"、git config --global user.email "邮箱"
git init:初始化 Git 仓库
git add <file>/git add .:暂存修改到暂存区
git commit -m "消息":将暂存区的修改提交到仓库
git status:查看工作区状态
git log/git log --oneline --graph:查看提交历史
git diff:查看未暂存的修改
.gitignore文件:指定 Git 忽略的文件和目录
练习任务
- 初始化项目 :创建一个
my_project目录,git init,创建README.md写一行项目介绍,git add+git commit
- 多次修改 :修改
README.md三次,每次git add+git commit,用git log --oneline查看历史
- .gitignore :创建
.gitignore文件,忽略__pycache__/、.env、*.pyc,创建这些文件后git status确认被忽略
- 查看差异 :修改
README.md但不提交,用git diff查看修改内容
学习资源
复盘问题
git add和git commit的区别是什么?为什么需要两步而不是一步?
git status输出中 "Changes to be committed" 和 "Changes not staged for commit" 有什么区别?
.gitignore的作用是什么?为什么.env文件不应该被提交到 Git?
git log --oneline --graph和git log的输出有什么区别?
Session 2:Git 分支与远程协作
时间分配
|------|-------|---------------------------------|
| 阶段 | 时长 | 内容 |
| 概念学习 | 30min | 分支概念、branch/checkout/merge、远程仓库 |
| 动手练习 | 60min | 分支开发、GitHub 推送、冲突解决 |
| 复盘 | 30min | 回答复盘问题 |
掌握内容
git branch:查看/创建分支
git checkout <branch>/git switch <branch>:切换分支
git checkout -b <branch>/git switch -c <branch>:创建并切换分支
git merge <branch>:合并分支
- GitHub:创建远程仓库
git remote add origin <url>:添加远程仓库
git push -u origin main:推送到远程
git pull:拉取远程更新
git clone <url>:克隆远程仓库
- 合并冲突:手动编辑冲突文件,
git add+git commit
练习任务
- 分支开发 :在 main 分支创建项目,创建
feature/login分支,在分支上添加login.py,合并回 main
- GitHub 推送 :在 GitHub 创建仓库,
git remote add origin,推送项目
- 制造冲突 :在本地和 GitHub 上同时修改同一行,
git pull制造冲突,手动解决后提交
- 克隆项目:克隆一个 GitHub 开源项目到本地,查看其提交历史
学习资源
复盘问题
git merge和git rebase有什么区别?新手应该用哪个?
- 什么情况下会产生合并冲突?怎么解决?
git push和git pull分别做什么?为什么团队协作时pull很重要?
- 为什么不建议直接在
main分支上开发?
Session 3:pytest 测试基础
时间分配
|------|-------|----------------------------|
| 阶段 | 时长 | 内容 |
| 概念学习 | 30min | pytest 安装、测试函数、assert、运行测试 |
| 动手练习 | 60min | 编写测试、运行测试、分析失败 |
| 复盘 | 30min | 回答复盘问题 |
掌握内容
- 安装:
pip install pytest
- 测试函数命名:必须以
test_开头
- 测试文件命名:
test_*.py或*_test.py
assert语句:assert 条件或assert 条件, "错误消息"
- 运行测试:
pytest(运行所有)、pytest test_file.py(指定文件)
-
常用参数:
-v:详细输出
-k "表达式":只运行匹配的测试
--tb=short:简短错误回溯
-s:显示 print 输出
- 测试目录约定:
tests/目录
练习任务
- 计算器测试 :写一个
calculator.py(add, subtract, multiply, divide 函数),在tests/test_calculator.py中为每个函数写测试
- 故意失败:写一个故意错误的测试,运行 pytest 观察 failure 输出,理解 assertion error
- 过滤测试 :用
pytest -k "add"只运行名字包含 "add" 的测试
- 测试 Level 3:为 Level 3 写过的某个类(如 Student 类)编写测试
学习资源
复盘问题
- 为什么测试函数必须以
test_开头?不以test_开头会怎样?
assert a == b和if a != b: raise AssertionError有什么区别?为什么用assert?
pytest -k "add or subtract"会运行哪些测试?
--tb=short和默认的 traceback 有什么区别?什么场景下用哪个?
Session 4:pytest 进阶 --- fixture 与 TDD
时间分配
|------|-------|-----------------------------------|
| 阶段 | 时长 | 内容 |
| 概念学习 | 35min | fixture、parametrize、TDD 流程 |
| 动手练习 | 55min | TDD 开发密码验证器、fixture 和 parametrize |
| 复盘 | 30min | 回答复盘问题 |
掌握内容
@pytest.fixture:定义测试夹具(提供测试数据/环境)
- fixture scope:
function(默认)、module、session
@pytest.mark.parametrize:参数化测试,用一个测试函数测多组数据
conftest.py:共享 fixture,自动被 pytest 发现
@pytest.mark.skip/@pytest.mark.xfail:跳过/预期失败
- TDD 流程:Red(写测试,失败)→ Green(写代码,通过)→ Refactor(重构)
练习任务
- TDD 密码验证器 :用 TDD 方式开发
PasswordValidator类,至少 5 条规则(长度 ≥ 8、含大写、含数字、含特殊字符、不含空格),每条规则先写测试再写代码
- fixture 练习 :用
@pytest.fixture提供测试用户数据,多个测试函数复用
- parametrize 练习 :用
@pytest.mark.parametrize测试 divide 函数的多组输入输出,包括除以零的情况
- conftest.py :创建
conftest.py,定义共享 fixture(如临时目录),在多个测试文件中使用
学习资源
复盘问题
- fixture 和直接在测试函数里创建数据有什么区别?什么时候用 fixture?
scope="function"和scope="module"的 fixture 有什么区别?对性能有什么影响?
@pytest.mark.parametrize相比写多个测试函数有什么优势?
- TDD 的"先写测试"为什么能提高代码质量?如果先写代码再补测试,效果有什么不同?
Session 5:requests 库 --- HTTP 客户端
时间分配
|------|-------|-----------------------------|
| 阶段 | 时长 | 内容 |
| 概念学习 | 30min | HTTP 基础、GET/POST、requests 库 |
| 动手练习 | 60min | 调用 API、解析 JSON、异常处理 |
| 复盘 | 30min | 回答复盘问题 |
掌握内容
- 安装:
pip install requests
requests.get(url):发送 GET 请求
requests.post(url, json=data):发送 POST 请求
response.json():将 JSON 响应解析为 Python 字典/列表
response.status_code:HTTP 状态码(200 成功、404 未找到、500 服务器错误)
response.text:原始文本响应
- 查询参数:
requests.get(url, params={"key": "value"})
- 请求头:
requests.get(url, headers={"Authorization": "Bearer xxx"})
- 超时:
requests.get(url, timeout=5)
- 异常处理:
requests.exceptions.ConnectionError、Timeout、HTTPError
练习任务
- 获取用户列表 :调用
https://jsonplaceholder.typicode.com/users,打印所有用户的姓名和邮箱
- 查询参数 :调用
https://jsonplaceholder.typicode.com/posts?userId=1,获取特定用户的帖子
- POST 请求 :向
https://jsonplaceholder.typicode.com/posts发送 POST 请求,创建一篇新帖子
- 异常处理 :包装一个
safe_get(url)函数,处理超时、连接错误和 HTTP 错误
学习资源
复盘问题
response.json()和response.text有什么区别?什么时候用哪个?
- 为什么每次请求都应该设置
timeout?不设置会发生什么?
- GET 和 POST 的区别是什么?什么时候应该用 POST?
- 常见的 HTTP 状态码 200、201、400、404、500 分别代表什么?
Session 6:Flask --- 构建 REST API
时间分配
|------|-------|-----------------------------|
| 阶段 | 时长 | 内容 |
| 概念学习 | 35min | Flask 基础、路由、JSON 响应、HTTP 方法 |
| 动手练习 | 55min | 构建 CRUD API |
| 复盘 | 30min | 回答复盘问题 |
掌握内容
- 安装:
pip install flask
- 创建应用:
app = Flask(__name__)
- 路由:
@app.route('/path')
- JSON 响应:
jsonify({"key": "value"})
- HTTP 方法:
@app.route('/path', methods=['GET', 'POST'])
- 路由参数:
@app.route('/users/<int:user_id>')
- 请求体:
request.get_json()
- 查询参数:
request.args.get('key')
- 运行开发服务器:
app.run(debug=True, port=5000)
练习任务
- Hello World :创建 Flask 应用,
/返回{"message": "Hello, World!"}
- 用户列表 API :创建内存中的用户列表,
GET /api/users返回所有用户
- 创建用户 :
POST /api/users,从request.get_json()获取数据,添加到列表,返回 201 状态码
- 单个用户 :
GET /api/users/<int:user_id>返回单个用户,不存在返回 404
学习资源
复盘问题
@app.route('/api/users')和@app.route('/api/users/')(尾部斜杠)有什么区别?
jsonify()和直接return {"key": "value"}有什么区别?Flask 2.0+ 有什么变化?
debug=True在开发时有什么用?为什么绝对不能在生产环境使用?
request.get_json()什么时候返回 None?怎么处理?
Session 7:环境变量与配置管理
时间分配
|------|-------|---------------------------------|
| 阶段 | 时长 | 内容 |
| 概念学习 | 30min | 环境变量、.env 文件、python-dotenv、配置模式 |
| 动手练习 | 60min | 重构代码、创建配置类 |
| 复盘 | 30min | 回答复盘问题 |
掌握内容
- 环境变量读取:
os.environ['KEY']/os.getenv('KEY', 'default')
.env文件:KEY=value格式
python-dotenv:from dotenv import load_dotenv; load_dotenv()
.gitignore确保.env不被提交
-
配置类模式:
- 12-Factor App 原则:配置存储在环境变量中
练习任务
- 创建 .env :创建
.env文件,写入API_KEY=your_key和DATABASE_URL=sqlite:///app.db
- 加载配置 :用
python-dotenv加载.env,在代码中读取环境变量
- 重构 requests 代码:把 S5 的 API key 从硬编码改为从环境变量读取
- 配置类 :创建
config.py,定义DevelopmentConfig和ProductionConfig,在 Flask 中使用
学习资源
复盘问题
- 为什么不应该把 API key 和数据库密码硬编码在代码里?
.env文件为什么不能提交到 Git?如果已经提交了怎么补救?(提示:git filter-branch 或 BFG)
os.getenv('KEY', 'default')的第二个参数有什么用?什么时候用?
- 12-Factor App 的配置原则是什么?为什么推荐环境变量而不是配置文件?
Session 8:项目结构与打包
时间分配
|------|-------|-------------------------------------------------|
| 阶段 | 时长 | 内容 |
| 概念学习 | 35min | 包结构、init.py、requirements.txt、pyproject.toml |
| 动手练习 | 55min | 重构项目结构、写 README |
| 复盘 | 30min | 回答复盘问题 |
掌握内容
- Python 包:目录含
__init__.py即为包
-
标准项目结构:
requirements.txt:pip freeze > requirements.txt
pyproject.toml:现代项目元数据格式
README.md:项目说明、安装步骤、使用方法
- 绝对导入 vs 相对导入:
from app.routes import xxxvsfrom .routes import xxx
__main__.py:支持python -m my_package运行
练习任务
- 重构项目:将 S6 的 Flask 应用重构为标准包结构(app/ 包、tests/ 目录)
- 依赖管理 :创建
requirements.txt,用pip freeze > requirements.txt导出依赖
- README :写一个完整的
README.md,包含项目介绍、安装步骤(pip install -r requirements.txt)、运行方法
- 模块运行 :添加
__main__.py,用python -m my_app运行
学习资源
复盘问题
__init__.py的作用是什么?空文件和有内容的__init__.py有什么区别?
- 绝对导入(
from app.routes import xxx)和相对导入(from .routes import xxx)有什么区别?应该用哪个?
requirements.txt和pyproject.toml有什么区别?现代项目应该用哪个?
python -m my_package和直接python run.py有什么区别?为什么推荐-m?
Session 9:部署基础 --- Docker 入门
时间分配
|------|-------|-------------------------------|
| 阶段 | 时长 | 内容 |
| 概念学习 | 40min | Docker 概念、Dockerfile 语法、构建与运行 |
| 动手练习 | 50min | Docker 化 Flask 应用 |
| 复盘 | 30min | 回答复盘问题 |
掌握内容
- Docker 概念:镜像(image)= 模板,容器(container)= 运行实例
-
Dockerfile 常用指令:
FROM python:3.12-slim:基础镜像
WORKDIR /app:工作目录
COPY . /app:复制文件
RUN pip install -r requirements.txt:安装依赖
EXPOSE 5000:声明端口
CMD ["python", "run.py"]:启动命令
- 构建镜像:
docker build -t myapp .
- 运行容器:
docker run -p 5000:5000 myapp
- 端口映射:
-p 主机端口:容器端口
- 环境变量:
docker run -e KEY=value或--env-file .env
.dockerignore:类似.gitignore,排除不需要的文件
练习任务
- 编写 Dockerfile:为 S6 的 Flask 应用编写 Dockerfile
- 构建镜像 :
docker build -t my-flask-app .
- 运行容器 :
docker run -p 5000:5000 my-flask-app,在浏览器访问http://localhost:5000
- 环境变量注入 :用
docker run -e API_KEY=xxx -p 5000:5000 my-flask-app注入环境变量
学习资源
复盘问题
- Docker 镜像和容器有什么区别?用一句话解释。
COPY . /app和ADD . /app有什么区别?应该用哪个?
- 为什么需要
.dockerignore?不用会导致什么问题?
docker run -p 5000:5000中的两个5000分别代表什么?如果只写一个会怎样?
Session 10:结业项目 --- 完整 Web 应用
时间分配
|------|-------|-------------------------|
| 阶段 | 时长 | 内容 |
| 项目规划 | 20min | 确定项目需求、设计 API 端点、规划项目结构 |
| 开发实现 | 70min | 编写代码、写测试、调试 |
| 部署 | 20min | Docker 化、运行 |
| 复盘 | 10min | 自检晋级标准 |
项目:天气查询 Web 应用
功能要求
-
API 端点 :
GET /api/weather/<city>:查询城市天气(调用外部天气 API)
GET /api/history:返回查询历史
GET /api/health:健康检查端点
- 测试:至少 5 个测试用例(含 fixture 和 parametrize)
- 配置 :API key 存储在
.env
- 结构:标准包结构
- 部署:Dockerfile + docker run
评分标准
- 项目结构规范(app/ 包、tests/ 目录、README.md、requirements.txt)
- 至少 3 个 API 端点,返回正确的 JSON 响应
- 至少 5 个测试用例,全部通过
- API key 从
.env读取,不硬编码
- Git 提交历史清晰(至少 5 次有意义的 commit)
- Dockerfile 可构建,容器可运行
- 浏览器能访问
http://localhost:5000/api/weather/Beijing
- 不查教程独立完成
学习资源
- OpenWeatherMap API(免费天气 API)
- wttr.in(更简单的天气 API,无需 key)
复盘问题
- 回顾整个 Level 5,哪个 Session 最难?为什么?
- 如果让你重新设计这个项目,你会做什么改变?
- 你觉得自己最薄弱的环节是什么?怎么补?
- 你现在能独立搭建一个 Flask + Docker 的项目吗?如果不能,卡在哪里?
常见错误速查表
|----|-------------------------------|---------------------------------|-----------------------------------------------------|
| # | 错误 | 场景 | 正确做法 |
| 1 | git commit 前忘记 git add | 修改了文件但没暂存 | commit 前先 git status 检查 |
| 2 | .env 提交到 Git | API key 泄露 | .gitignore 中加 .env;如已提交用 git rm --cached .env |
| 3 | 测试函数不以 test_ 开头 | pytest 不运行该测试 | 函数名必须 test_ 前缀 |
| 4 | assert if x > 0 | 语法错误 | assert x > 0(assert 后跟表达式,不跟 if) |
| 5 | response.json 少写 () | 拿到方法对象而非字典 | response.json()(加括号调用) |
| 6 | requests 不设 timeout | 网络问题导致永久阻塞 | requests.get(url, timeout=5) |
| 7 | Flask debug=True 部署到生产 | 交互式调试器暴露,可执行任意代码 | 生产环境用 app.run(debug=False) 或用生产服务器 |
| 8 | 密钥硬编码在代码里 | 安全风险 | 用 .env + os.getenv() |
| 9 | Dockerfile 没有 .dockerignore | 镜像包含 .git、__pycache__ 等无用文件 | 创建 .dockerignore 排除不必要文件 |
| 10 | docker run 忘记 -p | 容器内服务运行但外部无法访问 | docker run -p 5000:5000 myapp |
晋级标准自检表
Level 5 是五级路线图的最终等级。以下 10 项全部打勾才算 Level 5 过关:
-
- 能用 Git 管理项目,包括分支创建、合并、远程推送
-
- 能用 pytest 编写测试,使用
assert、@pytest.fixture、@pytest.mark.parametrize
- 能用 pytest 编写测试,使用
-
- 理解 TDD 流程,能先写测试再写实现代码
-
- 能用
requests调用 REST API,处理 JSON 响应和异常
- 能用
-
- 能用 Flask 构建包含 GET/POST 的 REST API,返回正确的 JSON 和状态码
-
- 能正确使用
.env管理配置,不硬编码敏感信息
- 能正确使用
-
- 能将项目组织成标准包结构,含
tests/目录和README.md
- 能将项目组织成标准包结构,含
-
- 能编写 Dockerfile 并用
docker build+docker run运行应用
- 能编写 Dockerfile 并用
-
- 能用 Git 管理完整开发流程(分支开发 → 提交 → 合并 → 推送)
-
- 能独立完成 S10 结业项目,不查教程
如果任何一项未达标:回到对应 Session 重新练习,不要跳过。Level 5 的核心不是学新语法,而是工程化思维------这些能力在真实项目中每天都在用。