当前位置:首页>python>Python教程 - 单元测试与代码质量

Python教程 - 单元测试与代码质量

  • 2026-08-20 15:49:07
Python教程 - 单元测试与代码质量

欢迎来到"Python教程从零基础到实战"系列的第十六期!

前面我们从环境搭建一路学到了自动化运维,掌握了 Python 的方方面面。但一个重要的问题我们还没有深入讨论:**你怎么确定自己的代码是对的?**

这一期,我们要聊的是软件工程中不可或缺的两个话题:**单元测试****代码质量**。学会写测试,你的代码就不再是"能跑就行"的草稿,而是可靠的生产级代码。学会维护代码质量,你的项目才能在大���上保持整洁。

准备好了吗?让我们开始吧!

---

## 一、为什么需要单元测试?

想象一下这个场景:你花了一周时间写了一个数据处理模块,功能完美运行。同事帮你改了两行代码后,你的模块突然报错了——但他不知道自己的改动破坏了你原来的逻辑。

这就是单元测试要解决的问题:**当你(或别人)修改代码时,能立刻知道哪些功能被破坏了。**

单元测试的核心思想很简单:**把程序拆成小块,每一小块单独测试。** 比如一个函数应该接受正确的输入并返回期望的输出。

### 1.1 pytest —— Python 最流行的测试框架

Python 自带 unittest 模块,但社区更推荐用 **pytest**,因为它写起来简单得多。

```bash

pipinstallpytest

```

先看一个基本示例。假设你有一个计算薪水的函数:

```python

# salary_calculator.py

defcalculate_salary(base_salaryfloatovertime_hoursfloat = 0bonusfloat = 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(10000overtime_hours=20)

assert result == 11875

deftest_with_bonus():

"""测试加上奖金的情况"""

    result = calculate_salary(8000overtime_hours=10bonus=2000)

    expected = 8000 + (10 * (8000 / 160) * 1.5) + 2000

assert result == expected

deftest_negative_salary_raises_error():

"""测试负数工资应抛出异常"""

with pytest.raises(ValueErrormatch="基本工资不能为负数"):

        calculate_salary(-5000)

deftest_zero_overtime():

"""测试零加班的情况"""

assert calculate_salary(5000overtime_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",

    [

        (100000010000),

        (5000005000),

        (80001008000 + 10 * (8000/160) * 1.5),

        (100000300013000),

        (0000),

    ],

)

deftest_salary_parametrized(baseovertimebonusexpected):

"""用参数化测试覆盖多种场景"""

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(citystr) -> 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__(selfdb_connection):

self.db = db_connection

defget_user_by_email(selfemailstr) -> 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(selfnamestremailstr) -> 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(selfuser_idstritemslisttotal_amountfloat) -> 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(selforder_idint) -> bool:

"""取消订单"""

if order_id notinself.orders:

returnFalse

        order = self.orders[order_id]

if order["status"] == "cancelled":

returnFalse

        order["status"] = "cancelled"

returnTrue

defget_order_status(selforder_idint) -> 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(ValueErrormatch="订单不能为空"):

        manager.create_order("user_001", [], 0)

deftest_create_order_negative_amount(manager):

"""负数金额应抛出异常"""

with pytest.raises(ValueErrormatch="金额必须为正数"):

        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(999isFalse

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(nameage=25city="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(afloatbfloat) -> float:

return a / b

defmultiply(aintbint) -> int:

return a * b

# 类型检查示例

result1 = divide(10.03.0)      # OK

result2 = multiply("5""3")      # mypy 会报错:参数类型错误

result3: str = divide(10.03.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:

"""备忘录条目"""

idint

    content: str

    priority: str

    done: bool = False

classMemoApp:

"""简单的备忘录应用"""

def__init__(self):

self._items: Dict[int, MemoItem] = {}

self._next_id: int = 1

defcreate(selfcontentstrprioritystr = "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(selfitem_idint) -> bool:

"""标记备忘录为已完成"""

if item_id notinself._items:

returnFalse

self._items[item_id].done = True

returnTrue

defdelete(selfitem_idint) -> bool:

"""删除备忘录"""

if item_id inself._items:

delself._items[item_id]

returnTrue

returnFalse

defget(selfitem_idint) -> Optional[MemoItem]:

"""获取指定备忘录"""

returnself._items.get(item_id)

deflist_all(self) -> List[MemoItem]:

"""列出所有未删除的备忘录"""

returnlist(self._items.values())

deffilter_by_priority(selfprioritystr) -> 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(nint) -> int:

"""计算 n 的阶乘"""

if n < 0:

raiseValueError("不允许负数")

if n in (01):

return1

    result = 1

for i inrange(2, n + 1):

        result *= i

return result

defis_prime(nint) -> bool:

"""判断是否为素数"""

if n < 2:

returnFalse

for i inrange(2int(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(nint_cachedict = 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(ValueErrormatch="不允许负数"):

        factorial(-1)

@pytest.mark.parametrize("n,expected", [

    (2True),

    (3True),

    (4False),

    (17True),

    (1False),

    (0False),

    (-5False),

    (97True),

])

deftest_is_prime(nexpected):

assert is_prime(n) == expected

```

### 练习 5 参考

```python

# test_fibonacci.py

import pytest

from math_utils_exercise import fibonacci

@pytest.mark.parametrize("n,expected", [

    (11),

    (21),

    (32),

    (43),

    (55),

    (68),

    (713),

    (821),

    (934),

    (1055),

])

deftest_fibonacci_values(nexpected):

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 全栈工程师的路上了!🚀

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-08-21 14:39:50 HTTP/2.0 GET : https://f.mffb.com.cn/a/505510.html
  2. 运行时间 : 0.238725s [ 吞吐率:4.19req/s ] 内存消耗:4,898.30kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=96d6120dd5b67e124c3d2b5d316f5a0c
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.001119s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001432s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000809s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000693s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001363s ]
  6. SELECT * FROM `set` [ RunTime:0.003998s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001662s ]
  8. SELECT * FROM `article` WHERE `id` = 505510 LIMIT 1 [ RunTime:0.001447s ]
  9. UPDATE `article` SET `lasttime` = 1787294390 WHERE `id` = 505510 [ RunTime:0.003895s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 66 LIMIT 1 [ RunTime:0.000720s ]
  11. SELECT * FROM `article` WHERE `id` < 505510 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.001311s ]
  12. SELECT * FROM `article` WHERE `id` > 505510 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.001131s ]
  13. SELECT * FROM `article` WHERE `id` < 505510 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.023367s ]
  14. SELECT * FROM `article` WHERE `id` < 505510 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.002366s ]
  15. SELECT * FROM `article` WHERE `id` < 505510 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.010764s ]
0.242339s