当前位置:首页 > Python 工业工具 > 正文内容

调试与测试:Bug查得快,写得稳

调试与测试:Bug查得快,写得稳

一、问题背景:一个空字符串坑了我3天

去年有件让我印象特别深刻的事。

写了一个MES数据导入脚本,跑了半年没问题。突然有一天,某批次的厚度平均值算出来是负数。

查了3天,最后发现:有一行的厚度值被MES导成了空字符串,被我转成了0。

更坑的是,上个月也有一批数据少一个值,但因为数据量大,一个0对平均值影响小,没发现。这次恰好样本少,一个0直接拉偏了整个平均值。

为什么没发现? 因为写代码的时候只想了"正常情况",没想"数据不完美"的情况。

教训:代码能跑 ≠ 代码没问题。Bug藏得越深,排查越痛苦。

学完这一篇,你能做到:

1. 用 `print` + `breakpoint` 快速找Bug

2. 用 `assert` 在代码里自动检查数据

3. 用 `unittest` 写简单的测试,上线前就发现问题

---

二、技术原理:调试三板斧

2.1 第一斧:print调试

最简单但也最有效:

```python

def calc_avg(data):

print(f"[调试] 输入数据: {data}") # 看输入

result = sum(data) / len(data)

print(f"[调试] 计算结果: {result}") # 看输出

return result

```

哪步不对,一看print就知道。缺点是调试完要删掉这些print,不能忘。

2.2 第二斧:assert断言

更高级的"自检"——条件不满足就直接报错:

```python

def calc_avg(data):

assert len(data) > 0, "数据不能为空!"

assert all(isinstance(x, (int, float)) for x in data), "数据里混了非数字!"

return sum(data) / len(data)

正常调用——没问题

calc_avg([1250, 1248, 1251])

传空列表——assert弹出来

calc_avg([]) # AssertionError: 数据不能为空!

```

为什么值得学? `assert` 像代码里的"安检门"——数据进来先检查一遍,不对立刻停。这样不会出现"算了一个错的结果还在继续用"的情况。

2.3 第三斧:breakpoint断点

```python

def complex_calculation(data):

total = 0

breakpoint() # 程序跑到这里会暂停,进入交互式模式

for x in data:

total += x

return total

complex_calculation([1, 2, 3])

当你看到 >>> 提示符时,可以输入变量名看值:

>>> data -> [1, 2, 3]

>>> total -> 0

>>> quit() -> 退出继续跑

```

实用技巧:在 `>>>` 后输入变量名就能看值,输入 `quit()` 或按 `Ctrl+Z` 继续运行。

---

三、实战案例:给你的代码写测试

3.1 先写一个简单函数

```python

def process_lot(data, lot_id="Unknown"):

"""

处理一批Lot数据

返回: 均值和是否合格

"""

# 自检(assert)

assert len(data) > 0, f"Lot {lot_id} 数据为空"

assert all(isinstance(x, (int, float)) for x in data), "数据必须为数字"

avg = sum(data) / len(data)

std = (sum((x - avg)2 for x in data) / (len(data) - 1)) 0.5

passed = std < 3.0 # 标准差<3算合格

return {"lot_id": lot_id, "avg": round(avg, 2), "std": round(std, 2), "pass": passed}

```

3.2 写测试文件

新建 `test_process_lot.py`:

```python

import unittest

from process_lot import process_lot # 假设函数在上面的文件里

class TestProcessLot(unittest.TestCase):

"""测试 process_lot 函数"""

def test_normal_lot(self):

"""正常数据"""

result = process_lot([1250.5, 1248.3, 1251.2], "FAB-001")

self.assertEqual(result['lot_id'], "FAB-001")

self.assertTrue(result['pass']) # 应该合格

def test_bad_lot(self):

"""标准差大的数据"""

result = process_lot([1250, 1240, 1260], "FAB-002")

self.assertFalse(result['pass']) # 应该不合格

def test_single_value(self):

"""边界情况:只有一个值"""

result = process_lot([1250], "FAB-003")

self.assertEqual(result['std'], 0) # 标准差为0

self.assertTrue(result['pass']) # 标准差=0算合格

if __name__ == '__main__':

unittest.main()

```

3.3 运行测试

```bash

python test_process_lot.py

```

输出:

```

...

----------------------------------------------------------------------

Ran 3 tests in 0.001s

OK # 全部通过!

```

把test跑失败:在 `process_lot` 里改点什么(比如把 `avg = sum(data) / len(data)` 改成 `avg = sum(data) / 2`),再跑测试看看——你会看到 `FAILED (failures=1)`。

一个好的函数应该有对应的测试。每次改代码,跑一遍测试就知道有没有改坏别的。

---

四、效果对比

| 代码 | 之前 | 之后 |

|------|------|------|

| 排查Bug | 3天 | 5分钟 |

| 上线信心 | "但愿没问题" | "测试全过" |

| 改代码 | 改完忐忑 | 改完跑测试 |

---

五、自己动手

```python

练习:给你自己的函数写测试

第1步:写一个函数

def calc_cpk(data, usl=1253, lsl=1247):

"""计算Cpk"""

avg = sum(data) / len(data)

std = (sum((x - avg)2 for x in data) / (len(data) - 1)) 0.5

return min((usl - avg) / (3 std), (avg - lsl) / (3 std))

第2步:写测试

测试用例1:正常数据,Cpk应该 > 1.0

测试用例2:数据波动很大,Cpk应该 < 1.0

测试用例3:传空列表,应该怎么办?

✏️ 下面写你的代码

```

思考题

1. 测试文件 `test_xxx.py` 为什么以 `test` 开头?试试改成别的名字

2. 如果函数里用了 `print` 而不是 `return`,能测试吗?

3. 你猜 `self.assertEqual` 和 `assert a == b` 有什么区别?

---

六、常见误区

| 问题 | 表现 | 解决 |

|------|------|------|

| 没写测试就上线 | 线上出Bug才慌 | 写一行测试不费时间 |

| 只有正常情况测试 | 边界条件全漏了 | 想想"最奇怪的数据"也测 |

| assert不写错误信息 | 只看到AssertionError不知道为啥 | `assert x > 0, "x必须大于0"` |

| 测试和生产用不同的逻辑 | 测试过了,上线挂了 | 测试要测真实的函数 |

---

> 💬 你有"查Bug查到崩溃"的经历吗?评论区讲讲

> 📚 收藏+点赞,下篇学面向对象 👆

相关文章

FAB工程师学Python的正确路径(附学习地图)

FAB工程师学Python的正确路径(附学习地图)

FAB工程师学Python的正确路径(附学习地图) 我带过一个实习生,非科班出身,学了3个月Python,第一个月工资就涨了2000。 也有干了5年的工艺工程师,手动导数据画图画了5年,月薪还是那点钱...

SPC/MES/FDC工具全家桶:工程师必备Python脚本合集

SPC/MES/FDC工具全家桶:工程师必备Python脚本合集

SPC/MES/FDC工具全家桶:工程师必备Python脚本合集 我在FAB干了15年,最值钱的东西不是经验,是一个攒了多年的Python工具箱。 今天把这个工具箱的核心部分分享出来,从数据采集到SP...

Python日报自动化:MES数据一键生成Excel报告(附完整源码)

Python日报自动化:MES数据一键生成Excel报告(附完整源码)

Python日报自动化:MES数据一键生成Excel报告(附完整源码) 1. 我的血泪史:每天2小时的日报工作 2018年,我在FAB做整合工程师的时候,每天早上第一件事不是分析数据,而是做日报。从M...

Python设备故障预测:XGBoost让FAB的设备维护从被动到主动

Python设备故障预测:XGBoost让FAB的设备维护从被动到主动

Python设备故障预测:XGBoost让FAB的设备维护从被动到主动 1. 问题背景:被动维修的代价 FAB里最贵的不是设备,是设备宕机造成的产能损失。一台光刻机价值$100M+,停机1小时损失约$...

Python晶圆良率分析实战:从数据清洗到可视化(附完整代码)

Python晶圆良率分析实战:从数据清洗到可视化(附完整代码)

Python晶圆良率分析实战:从数据清洗到可视化(附完整代码) 1. 问题背景:我的第一次良率分析 2016年,我在FAB做工艺工程师的时候,第一次被要求分析一批良率异常。工程师把数据发给我——一个E...

工艺工程师学Python的6个正确姿势:别再走弯路了

工艺工程师学Python的6个正确姿势:别再走弯路了

工艺工程师学Python的6个正确姿势:别再走弯路了 1. 工艺工程师学Python的特殊性 工艺工程师学Python不是为了写程序,是为了解决工作中的问题。这个区别很重要:软件工程师追求代码漂亮,工...