欢迎来到"Python教程从零基础到实战"系列的第十六期!
前面我们从环境搭建一路学到了自动化运维,掌握了 Python 的方方面面。但一个重要的问题我们还没有深入讨论:**你怎么确定自己的代码是对的?**
这一期,我们要聊的是软件工程中不可或缺的两个话题:**单元测试**和**代码质量**。学会写测试,你的代码就不再是"能跑就行"的草稿,而是可靠的生产级代码。学会维护代码质量,你的项目才能在大���上保持整洁。
准备好了吗?让我们开始吧!
---
## 一、为什么需要单元测试?
想象一下这个场景:你花了一周时间写了一个数据处理模块,功能完美运行。同事帮你改了两行代码后,你的模块突然报错了——但他不知道自己的改动破坏了你原来的逻辑。
这就是单元测试要解决的问题:**当你(或别人)修改代码时,能立刻知道哪些功能被破坏了。**
单元测试的核心思想很简单:**把程序拆成小块,每一小块单独测试。** 比如一个函数应该接受正确的输入并返回期望的输出。
### 1.1 pytest —— Python 最流行的测试框架
Python 自带 unittest 模块,但社区更推荐用 **pytest**,因为它写起来简单得多。
```bash
pipinstallpytest
```
先看一个基本示例。假设你有一个计算薪水的函数:
```python
# salary_calculator.py
defcalculate_salary(base_salary: float, overtime_hours: float = 0, bonus: float = 0) -> float:
"""计算总薪水:基本工资 + 加班费(每小时1.5倍) + 奖金"""
if base_salary < 0:
raiseValueError("基本工资不能为负数")
overtime_pay = overtime_hours * (base_salary / 160) * 1.5# 每月标准工时160小时
return base_salary + overtime_pay + bonus
```
对应的测试文件:
```python
# test_salary_calculator.py
import pytest
from salary_calculator import calculate_salary
deftest_basic_salary():
"""测试只有基本工资的情况"""
assert calculate_salary(10000) == 10000
deftest_overtime_pay():
"""测试加班费的计算"""
# 基本工资10000,加班20小时
# 每小时费率 = 10000 / 160 = 62.5
# 加班费 = 20 * 62.5 * 1.5 = 1875
result = calculate_salary(10000, overtime_hours=20)
assert result == 11875
deftest_with_bonus():
"""测试加上奖金的情况"""
result = calculate_salary(8000, overtime_hours=10, bonus=2000)
expected = 8000 + (10 * (8000 / 160) * 1.5) + 2000
assert result == expected
deftest_negative_salary_raises_error():
"""测试负数工资应抛出异常"""
with pytest.raises(ValueError, match="基本工资不能为负数"):
calculate_salary(-5000)
deftest_zero_overtime():
"""测试零加班的情况"""
assert calculate_salary(5000, overtime_hours=0) == 5000
```
运行测试:
```bash
pytesttest_salary_calculator.py-v
```
输出效果:
```
test_salary_calculator.py::test_basic_salary PASSED
test_salary_calculator.py::test_overtime_pay PASSED
test_salary_calculator.py::test_with_bonus PASSED
test_salary_calculator.py::test_negative_salary_raises_error PASSED
test_salary_calculator.py::test_zero_overtime PASSED
========================= 5 passed in 0.02s =========================
```
### 1.2 测试的参数化 —— 一次写多个测试
如果要对多种输入都测试一遍,手动写很多个函数很麻烦。pytest 提供了 `@pytest.mark.parametrize`:
```python
import pytest
from salary_calculator import calculate_salary
@pytest.mark.parametrize(
"base,overtime,bonus,expected",
[
(10000, 0, 0, 10000),
(5000, 0, 0, 5000),
(8000, 10, 0, 8000 + 10 * (8000/160) * 1.5),
(10000, 0, 3000, 13000),
(0, 0, 0, 0),
],
)
deftest_salary_parametrized(base, overtime, bonus, expected):
"""用参数化测试覆盖多种场景"""
assert calculate_salary(base, overtime_hours=overtime, bonus=bonus) == expected
```
一行注解就生成了 5 个测试用例,简洁明了!
---
## 二、Mock —— 测试那些"连接外部"的代码
现实中的代码很少是自给自足的。你的函数可能要读取数据库、调用 API、或者发送电子邮件。测试的时候当然不希望每次运行都真的去查数据库或发邮件。
这时候就需要 **Mock**——模拟(伪装)那些外部依赖。
### 2.1 模拟 API 调用
假设你有一个从天气 API 获取数据的函数:
```python
# weather_service.py
import requests
defget_temperature(city: str) -> float:
"""从天气服务获取指定城市的温度"""
url = f"https://api.weather.example.com/current?city={city}"
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
return data["temperature"]
```
直接测试这个函数需要真实的 API 环境,我们用 Mock 来替代:
```python
# test_weather_service.py
import pytest
from unittest.mock import patch, MagicMock
from weather_service import get_temperature
@patch("weather_service.requests.get")
deftest_get_temperature(mock_get):
"""模拟 API 返回"""
# 准备 mock 对象
mock_response = MagicMock()
mock_response.json.return_value = {"temperature": 23.5}
mock_get.return_value = mock_response
# 调用真实函数(内部会走到我们的 mock)
result = get_temperature("北京")
# 验证
assert result == 23.5
mock_get.assert_called_once()
mock_get.assert_called_with(
"https://api.weather.example.com/current?city=%E5%8C%97%E4%BA%AC",
timeout=10
)
```
`@patch` 装饰器会自动把 `requests.get` 替换为一个 Mock 对象。你的代码以为自己在调 API,实际上拿到的是你准备好的假数据。
### 2.2 模拟数据库查询
再比如一个用户查询函数:
```python
# user_repo.py
classUserRepository:
def__init__(self, db_connection):
self.db = db_connection
defget_user_by_email(self, email: str) -> dict:
"""通过邮箱查找用户"""
cursor = self.db.cursor()
cursor.execute("SELECT * FROM users WHERE email = %s", (email,))
row = cursor.fetchone()
if row:
return {
"id": row[0],
"name": row[1],
"email": row[2],
"created_at": row[3]
}
returnNone
defcreate_user(self, name: str, email: str) -> int:
"""创建新用户"""
cursor = self.db.cursor()
cursor.execute(
"INSERT INTO users (name, email) VALUES (%s, %s)",
(name, email)
)
self.db.commit()
return cursor.lastrowid
```
对应的测试:
```python
# test_user_repo.py
import pytest
from unittest.mock import MagicMock
from user_repo import UserRepository
deftest_get_user_by_email_found():
"""测试:用户存在时的查询"""
mock_db = MagicMock()
mock_cursor = mock_db.cursor.return_value
mock_cursor.fetchone.return_value = (1, "张三", "zhangsan@example.com", "2026-01-15")
repo = UserRepository(mock_db)
result = repo.get_user_by_email("zhangsan@example.com")
assert result == {
"id": 1,
"name": "张三",
"email": "zhangsan@example.com",
"created_at": "2026-01-15",
}
mock_cursor.execute.assert_called_once()
deftest_get_user_by_email_not_found():
"""测试:用户不存在时返回 None"""
mock_db = MagicMock()
mock_cursor = mock_db.cursor.return_value
mock_cursor.fetchone.return_value = None
repo = UserRepository(mock_db)
result = repo.get_user_by_email("nobody@example.com")
assert result isNone
deftest_create_user():
"""测试:创建新用户"""
mock_db = MagicMock()
mock_cursor = mock_db.cursor.return_value
mock_cursor.lastrowid = 42
repo = UserRepository(mock_db)
user_id = repo.create_user("李四", "lisi@example.com")
assert user_id == 42
mock_db.commit.assert_called_once()
```
看到没有?**完全没有真实的数据库参与**,但测试覆盖了所有关键路径。这就是 Mock 的力量。
---
## 三、测试覆盖率 —— 你的代码被测试了多少?
光写了测试还不够,你得知道自己**测得够不够**。覆盖率工具会告诉你哪些代码被执行过了,哪些还没被覆盖。
```bash
pipinstallpytest-cov
```
运行带覆盖率的测试:
```bash
pytest--cov=salary_calculator--cov-report=term-missingtest_salary_calculator.py
```
输出类似这样:
```
Name Stmts Miss Cover Missing
--------------------------------------------------------
salary_calculator.py 8 0 100%
--------------------------------------------------------
TOTAL 8 0 100%
```
如果某一行没被覆盖到,"Missing" 栏会告诉你具体是哪一行。
再给你一个实际项目里常见的例子:
```python
# order_manager.py
classOrderManager:
def__init__(self):
self.orders = {}
self.next_id = 1
defcreate_order(self, user_id: str, items: list, total_amount: float) -> int:
"""创建订单"""
ifnot items:
raiseValueError("订单不能为空")
if total_amount <= 0:
raiseValueError("金额必须为正数")
order_id = self.next_id
self.orders[order_id] = {
"id": order_id,
"user_id": user_id,
"items": items,
"total_amount": total_amount,
"status": "pending"
}
self.next_id += 1
return order_id
defcancel_order(self, order_id: int) -> bool:
"""取消订单"""
if order_id notinself.orders:
returnFalse
order = self.orders[order_id]
if order["status"] == "cancelled":
returnFalse
order["status"] = "cancelled"
returnTrue
defget_order_status(self, order_id: int) -> dict:
"""查询订单状态"""
if order_id notinself.orders:
return {"error": "订单不存在"}
returnself.orders[order_id]
```
测试它:
```python
# test_order_manager.py
import pytest
from order_manager import OrderManager
@pytest.fixture
defmanager():
"""创建一个空的 OrderManager 实例供各测试复用"""
return OrderManager()
deftest_create_order(manager):
"""正常创建订单"""
order_id = manager.create_order("user_001", [{"name": "键盘", "price": 299}], 299.0)
assert order_id == 1
status = manager.get_order_status(order_id)
assert status["status"] == "pending"
assert status["total_amount"] == 299.0
deftest_create_second_order(manager):
"""连续创建,ID 递增"""
id1 = manager.create_order("user_001", [("A", 10)], 10)
id2 = manager.create_order("user_002", [("B", 20)], 20)
assert id1 == 1
assert id2 == 2
deftest_create_order_empty_items(manager):
"""空列表应抛出异常"""
with pytest.raises(ValueError, match="订单不能为空"):
manager.create_order("user_001", [], 0)
deftest_create_order_negative_amount(manager):
"""负数金额应抛出异常"""
with pytest.raises(ValueError, match="金额必须为正数"):
manager.create_order("user_001", [("item", 1)], -5)
deftest_cancel_order(manager):
"""正常取消订单"""
oid = manager.create_order("user_001", [("A", 10)], 10)
assert manager.cancel_order(oid) isTrue
deftest_cancel_already_cancelled(manager):
"""取消已取消的订单应失败"""
oid = manager.create_order("user_001", [("A", 10)], 10)
manager.cancel_order(oid)
assert manager.cancel_order(oid) isFalse
deftest_cancel_nonexistent_order(manager):
"""取消不存在的订单"""
assert manager.cancel_order(999) isFalse
deftest_get_nonexistent_order(manager):
"""查询不存在的订单返回错误"""
result = manager.get_order_status(999)
assert"error"in result
deftest_create_order_multi_items(manager):
"""创建包含多个商品的订单"""
items = [
{"name": "机械键盘", "price": 599},
{"name": "鼠标垫", "price": 49},
{"name": "USB线", "price": 15},
]
total = sum(item["price"] for item in items)
oid = manager.create_order("user_002", items, total)
status = manager.get_order_status(oid)
assertlen(status["items"]) == 3
assert status["total_amount"] == total
```
运行覆盖率:
```bash
pytest--cov=order_manager--cov-report=term-missingtest_order_manager.py
```
目标:**核心业务代码覆盖率 >= 80%**,关键模块争取 95%+。
### 3.1 覆盖率达标建议
-`pytest.ini` 或 `pyproject.toml` 中可以设置最低覆盖率门槛:
```ini
# pyproject.toml
[tool.pytest.ini_options]
addopts = --cov=. --cov-fail-under=80
```
这样如果覆盖率低于 80%,CI 流程会自动报错,防止低质量代码合入主干。
---
## 四、代码质量工具
写好代码的同时,让代码风格保持一致也非常重要。团队里五个人写代码,五种缩进五种命名,看着就头疼。
下面三个工具是 Python 社区的"标配三剑客":
### 4.1 Black —— 自动格式化
Black 是一个激进的 Python 代码格式化工具。它只有一个目标:**让你的代码格式永远一致,无需争论。**
```bash
pipinstallblack
```
使用方式:
```bash
# 检查格式(不修改文件)
black--checkmy_project/
# 自动格式化所有 Python 文件
blackmy_project/
```
举个例子,不管你怎么写,Black 都会统一成这样:
```python
# 你写的(乱糟糟的)
defhello(name,
age=25,city="Beijing"
):print(f"Hi{name},you are {age} from {city}")
# Black 格式化后
defhello(name, age=25, city="Beijing"):
print(f"Hi {name}, you are {age} from {city}")
```
### 4.2 Ruff —— 超快的 Linter
Ruff 是近年来最受欢迎的 Python lint 工具,用 Rust 编写,速度比 flake8 + 一堆插件快 10-100 倍。
```bash
pipinstallruff
```
使用方式:
```bash
# 检查代码质量问题
ruffcheckmy_project/
# 自动修复能安全修复的问题
ruffcheck--fixmy_project/
# 查看详细报告
ruffcheck--output-format=fullmy_project/
```
Ruff 能发现很多问题:
```python
# utils.py
import os, sys # Ruff: 多个 import 应分开
unused_var = "hello"# Ruff: 定义了但没使用的变量
defcalc(x):
return x + 1
print("done") # Ruff: 永远不会执行的代码(dead code)
```
运行 `ruff check` 会给出:
```
utils.py:1:8: F401 [*] `os` and `sys` imported but unused
utils.py:3:1: F841 local variable `unused_var` is assigned to but never used
utils.py:6:5: F401 [*] `os` and `sys` imported but unused
utils.py:7:5: F601 assignment to `x` after potential reference to `x`
```
### 4.3 mypy —— 静态类型检查
如果你的代码中有类型注解,mypy 可以在不运行代码的情况下发现类型错误:
```bash
pipinstallmypy
```
```python
# calculator.py
defdivide(a: float, b: float) -> float:
return a / b
defmultiply(a: int, b: int) -> int:
return a * b
# 类型检查示例
result1 = divide(10.0, 3.0) # OK
result2 = multiply("5", "3") # mypy 会报错:参数类型错误
result3: str = divide(10.0, 3.0) # mypy 会报错:返回的是 float 却赋给了 str
```
运行 `mypy calculator.py`:
```
calculator.py:9: error: Argument 1 to "multiply" has incompatible type "str"; expected "int"
calculator.py:10: error: Incompatible types in assignment (expression has type "float", variable has type "str")
```
有了这些工具,你可以把它们集成到 IDE 或 CI 流程中,让代码质量问题在提交前就被自动发现。
---
## 五、端到端实战:一个完整的测试驱动开发示例
理论学得差不多了,现在来一个综合练习。我们用**测试驱动开发(TDD)**的方式实现一个小项目:**一个简单的备忘录应用**。
先写测试,再写代码。
### Step 1:定义测试
```python
# test_memo.py
import pytest
from memo import MemoApp, MemoItem
deftest_create_memo():
"""创建备忘录"""
app = MemoApp()
item_id = app.create("记住购买 supplies", "urgent")
items = app.list_all()
assertlen(items) == 1
assert items[0].content == "记住购买 supplies"
assert items[0].priority == "urgent"
assert items[0].done isFalse
deftest_complete_memo():
"""标记已完成"""
app = MemoApp()
item_id = app.create("学会 pytest", "normal")
app.complete(item_id)
item = app.get(item_id)
assert item.done isTrue
deftest_delete_memo():
"""删除备忘录"""
app = MemoApp()
item_id = app.create("删除我", "low")
app.delete(item_id)
assertlen(app.list_all()) == 0
deftest_filter_by_priority():
"""按优先级筛选"""
app = MemoApp()
app.create("重要事项", "urgent")
app.create("随便想想", "low")
app.create("中等重要", "normal")
urgent_items = app.filter_by_priority("urgent")
assertlen(urgent_items) == 1
assert urgent_items[0].content == "重要事项"
deftest_stats():
"""统计信息"""
app = MemoApp()
app.create("任务一", "urgent")
app.create("任务二", "normal")
app.create("任务三", "urgent")
stats = app.stats()
assert stats["total"] == 3
assert stats["done"] == 0
assert stats["undone"] == 3
assert stats["by_priority"]["urgent"] == 2
assert stats["by_priority"]["normal"] == 1
```
### Step 2:实现代码
```python
# memo.py
from dataclasses import dataclass, field
from typing import Dict, List, Optional
@dataclass
classMemoItem:
"""备忘录条目"""
id: int
content: str
priority: str
done: bool = False
classMemoApp:
"""简单的备忘录应用"""
def__init__(self):
self._items: Dict[int, MemoItem] = {}
self._next_id: int = 1
defcreate(self, content: str, priority: str = "normal") -> int:
"""创建备忘录条目"""
item = MemoItem(
id=self._next_id,
content=content,
priority=priority
)
self._items[self._next_id] = item
self._next_id += 1
return item.id
defcomplete(self, item_id: int) -> bool:
"""标记备忘录为已完成"""
if item_id notinself._items:
returnFalse
self._items[item_id].done = True
returnTrue
defdelete(self, item_id: int) -> bool:
"""删除备忘录"""
if item_id inself._items:
delself._items[item_id]
returnTrue
returnFalse
defget(self, item_id: int) -> Optional[MemoItem]:
"""获取指定备忘录"""
returnself._items.get(item_id)
deflist_all(self) -> List[MemoItem]:
"""列出所有未删除的备忘录"""
returnlist(self._items.values())
deffilter_by_priority(self, priority: str) -> List[MemoItem]:
"""按优先级筛选"""
return [item for item inself._items.values() if item.priority == priority]
defstats(self) -> dict:
"""返回统计信息"""
all_items = self.list_all()
done_items = [i for i in all_items if i.done]
undone_items = [i for i in all_items ifnot i.done]
by_priority = {}
for item in all_items:
by_priority[item.priority] = by_priority.get(item.priority, 0) + 1
return {
"total": len(all_items),
"done": len(done_items),
"undone": len(undone_items),
"by_priority": by_priority,
}
```
### Step 3:运行测试
```bash
pytesttest_memo.py-v
```
```
test_memo.py::test_create_memo PASSED
test_memo.py::test_complete_memo PASSED
test_memo.py::test_delete_memo PASSED
test_memo.py::test_filter_by_priority PASSED
test_memo.py::test_stats PASSED
========================= 5 passed in 0.01s =========================
```
全部通过!🎉 这就是 TDD 的基本流程:**红-绿-重构**。
-**红**:先写失败的测试
-**绿**:写出让测试通过的代码
-**重构**:优化代码结构,重新运行测试确认没破坏任何东西
---
## 六、练习题
### 练习 1:测试数学工具函数
写一个 `math_utils.py` 包含求阶乘和判断素数的函数,再写对应的测试。
```python
# math_utils.py
deffactorial(n: int) -> int:
"""计算 n 的阶乘"""
if n < 0:
raiseValueError("不允许负数")
if n in (0, 1):
return1
result = 1
for i inrange(2, n + 1):
result *= i
return result
defis_prime(n: int) -> bool:
"""判断是否为素数"""
if n < 2:
returnFalse
for i inrange(2, int(n ** 0.5) + 1):
if n % i == 0:
returnFalse
returnTrue
```
要求编写测试覆盖边界情况(0、1、负数、小数输入等)。
### 练习 2:用 pytest fixture 管理测试数据
```python
# 提示:使用 @pytest.fixture 创建共享的测试数据
import pytest
@pytest.fixture
defsample_users():
return [
{"id": 1, "name": "Alice", "age": 30},
{"id": 2, "name": "Bob", "age": 25},
{"id": 3, "name": "Charlie", "age": 35},
]
deftest_find_adults(sample_users):
"""找出年龄 >= 30 的用户"""
adults = [u for u in sample_users if u["age"] >= 30]
assertlen(adults) == 2
assert adults[0]["name"] == "Alice"
```
尝试扩展这个 fixture 并写更多测试。
### 练习 3:给之前的某个函数写完整测试
从 Episode 05 的爬虫代码中选一个函数,给它写全覆盖的测试(包括 Mock 网络请求的部分)。
### 练习 4:运行 ruff 和 black
在你的任意 Python 项目中安装 Black 和 Ruff,运行格式化检查和代码质量扫描,然后修复所有问题。
### 练习 5:实现带缓存的斐波那契数列(含测试)
```python
deffibonacci(n: int, _cache: dict = None) -> int:
"""带缓存优化的斐波那契"""
if _cache isNone:
_cache = {}
if n in _cache:
return _cache[n]
if n <= 0:
raiseValueError("n 必须是正整数")
if n == 1or n == 2:
return1
result = fibonacci(n - 1, _cache) + fibonacci(n - 2, _cache)
_cache[n] = result
return result
```
为这个函数编写测试,验证前 10 项的结果。
---
## 七、参考答案
### 练习 1 参考
```python
# test_math_utils.py
import pytest
from math_utils import factorial, is_prime
deftest_factorial_zero():
assert factorial(0) == 1
deftest_factorial_one():
assert factorial(1) == 1
deftest_factorial_five():
assert factorial(5) == 120
deftest_factorial_large():
assert factorial(10) == 3628800
deftest_factorial_negative():
with pytest.raises(ValueError, match="不允许负数"):
factorial(-1)
@pytest.mark.parametrize("n,expected", [
(2, True),
(3, True),
(4, False),
(17, True),
(1, False),
(0, False),
(-5, False),
(97, True),
])
deftest_is_prime(n, expected):
assert is_prime(n) == expected
```
### 练习 5 参考
```python
# test_fibonacci.py
import pytest
from math_utils_exercise import fibonacci
@pytest.mark.parametrize("n,expected", [
(1, 1),
(2, 1),
(3, 2),
(4, 3),
(5, 5),
(6, 8),
(7, 13),
(8, 21),
(9, 34),
(10, 55),
])
deftest_fibonacci_values(n, expected):
assert fibonacci(n) == expected
deftest_fibonacci_performance():
"""缓存优化应该让大数计算飞快"""
import time
start = time.time()
fibonacci(1000)
elapsed = time.time() - start
# 1000 项应该在 1ms 内算完
assert elapsed < 0.01
deftest_fibonacci_invalid():
with pytest.raises(ValueError):
fibonacci(0)
with pytest.raises(ValueError):
fibonacci(-10)
```
---
## 八、总结
这一期我们学了很多实用的工程技能:
-**pytest 基础**:`assert`、`@pytest.fixture`、`@pytest.mark.parametrize`、异常测试
-**Mock 技术**:用 `unittest.mock` 模拟外部依赖(API、数据库)
-**测试覆盖率**:用 `pytest-cov` 量化测试质量
-**代码质量工具**:Black 格式化、Ruff 检查、mypy 类型验证
-**TDD 实践**:测试驱动开发的完整流程
这些工具不是"可选的加分项",而是专业开发的标配。当你养成写测试的习惯,你会发现:
1.**改代码不再忐忑了**——测试替你盯着
2.**bug 变少了**——因为你会想"我怎么测试这个"才去写代码
3.**文档更清晰了**——测试就是最准确的文档
---
## 下期预告
📢 **第十七期:Python 设计模式实战——让代码更优雅的艺术**
我们已经学了如何写能用的代码、如何保证代码正确。接下来,我们要学如何让代码写得**更好**。
第十七期将介绍最常用的几种设计模式:
-**单例模式**:确保一个类只有一个实例
-**工厂模式**:隐藏创建逻辑,统一接口
-**装饰器模式**:动态给对象添加功能(没错,Episode 4 的装饰器升级版)
-**观察者模式**:事件驱动架构的基础
-**策略模式**:算法可以随时切换
- 配合 pytest 测试每种模式的实现
敬请期待!
---
**📚 系列回顾**
到目前为止我们学过的内容:
| Episode | 主题 |
|---------|------|
| 01 | 环境搭建与基础语法 |
| 02 | 数据结构与字符串处理 |
| 03 | 面向对象编程 |
| 04 | 装饰器、生成器与文件 IO |
| 05 | 爬虫入门 |
| 06 | 数据分析入门 |
| 07 | Web 后端开发 |
| 08 | AI 实战入门 |
| 09 | 前端入门 |
| 10 | 项目实战——Dashboard |
| 11 | Docker 容器化部署 |
| 12 | 数据库进阶 |
| 13 | 异步编程与并发 |
| 14 | 日志系统与调试技巧 |
| 15 | 自动化运维与脚本实战 |
| 16 | 单元测试与代码质量 ← **本期** |
继续加油,你已经在成为 Python 全栈工程师的路上了!🚀