第4章 Python基础快速回顾:变量、数据类型、控制流、函数
这一章咱们不讲心法,直接上干货。
我知道在座的各位多半已经写过代码,但ATE测试脚本开发有其特殊性。我在带项目时发现,很多人不是不会Python,而是把Python写成了「C语言」或者「Java」,结果在自动化测试框架里跑得磕磕绊绊。咱们趁热打铁,把几个关键基础捋一遍。
4.1 变量与数据类型
Python是动态类型语言。说白了,你不需要像C语言那样先声明 `int a`,直接 `a = 5` 就行。但我建议你养成一个习惯:变量名见名知意。
我个人习惯:测试项名称用全大写,比如 TEST_NAME = "VCC_SHORT";局部循环变量用小写,比如 for pin in pin_list。这样别人读你的脚本时,一眼就能分清常量和临时变量。
常用的数据类型就那么几种:
| 类型 | 示例 | 在ATE中的使用场景 |
|---|---|---|
| int | cycle_count = 100 |
测试周期计数、通道编号 |
| float | vcc = 3.3 |
电压设置、时间精度 |
| str | pattern_name = "func_scan" |
测试文件路径、Pattern名称 |
| bool | is_pass = True |
测试结果判读、开关控制 |
| list | pins = ["A1","B2","C3"] |
管脚列表、测试序列 |
| dict | limits = {"min":1.8, "max":2.0} |
参数配置、极限值管理 |
避坑指南:我曾经在解析测试机台日志时,直接用 == 比较两个浮点数,结果出了大bug。ATE里的电压值经常是测量结果,浮点数比较要用 abs(a - b) < epsilon,千万别图省事。
变量赋值其实比你想象的灵活。比如 a, b, c = 1, 2, 3 这种解包赋值,在切换测试通道配置时特别好用。还有 a += 1 这种增量赋值,写循环计数时比 a = a + 1 利索多了。
4.2 控制流——别写成意大利面条
控制流无非是 if、for、while。但ATE测试脚本里,控制流的写法直接决定你后续调试的效率。
先看条件判断。我见过有人嵌套了七八层 if,读起来就像一盘意大利面——理不清头绪。我的建议是:能用 elif 就别层层嵌套。
# 推荐写法
if result == "PASS":
handle_pass()
elif result == "FAIL":
handle_fail()
elif result == "SKIP":
handle_skip()
else:
log_error(f"未知结果: {result}")
# 不推荐:多层嵌套,且每个分支里只有两三行代码
if result == "PASS":
handle_pass()
else:
if result == "FAIL":
handle_fail()
else:
if result == "SKIP":
handle_skip()
else:
log_error(...)
再看循环。ATE测试里最常用的是 for 循环遍历管脚、通道、测试项。range() 函数要熟练掌握,尤其是它的三个参数用法:range(start, stop, step)。我在写scan链测试时,经常需要跳过一些空位通道,用 range(0, 1024, 4) 就非常方便。
警告:别在循环体里修改正在遍历的列表。比如 for pin in pin_list: pin_list.remove(pin) 这种写法,跑着跑着你就会发现索引乱了。正确做法是创建一个副本,或者用列表推导式。
说到 while,我其实不太建议在ATE测试脚本里用它做主要循环。为什么?一旦条件写错,就是死循环。测试机台跑着跑着卡死了,Locker里的芯片全都提前停机,这损失可不小。我一般只在需要「等待硬件就绪」时才用 while,而且一定加超时保护。
# 超时保护的标准写法
timeout = 5.0 # 秒
start_time = time.time()
while not hardware_ready():
if time.time() - start_time > timeout:
raise TimeoutError("硬件未就绪,超出等待时间")
time.sleep(0.01)
4.3 函数——把测试逻辑封装好
函数的本质是「把一段逻辑装进一个盒子里,给它取个名字」。在ATE开发中,函数就是你测试脚本的积木块。
首先是参数传递。Python的默认参数值很实用,但你想想看,如果默认参数用可变对象(比如列表、字典),会发生什么?
def add_pin(pin_name, pin_list=[]): # 这是坑
pin_list.append(pin_name)
return pin_list
# 多次调用后,pin_list会越来越大
list1 = add_pin("A1")
list2 = add_pin("B2")
print(list1) # ['A1', 'B2'],注意:list1也被修改了
这里的原因是默认参数只在函数定义时创建一次。我习惯用 None 作为默认值,在函数体内再创建可变对象,这样就安全了。
def add_pin(pin_name, pin_list=None):
if pin_list is None:
pin_list = []
pin_list.append(pin_name)
return pin_list
然后是返回值。函数要有明确的返回值。我曾经接手过一个测试脚本,里面的函数有的返回 True/False,有的返回 0/1,有的甚至不返回值直接打印,那叫一个乱。你自己写脚本时,一个函数只做一件事,返回值类型要固定。
小技巧:可以用类型注解(Type Hints)让代码更清晰。比如 def measure_voltage(pin: str) -> float:,IDE会给你自动补全提示,别人读代码也一看就懂。
说起变量作用域,局部变量和全局变量不要混用。我见过有人把测试参数写成全局变量,然后在各个函数里乱改,最后查bug查到崩溃。能用局部就用局部,实在要传数据,用参数传进去。
4.4 数据结构——列表、元组、字典、集合
这四种数据结构在ATE测试脚本里几乎天天见。我整理了一张对比表:
| 数据结构 | 可变性 | 有序性 | 适用场景 |
|---|---|---|---|
| list | 可变 | 有序 | 管脚列表、测试序列、日志 |
| tuple | 不可变 | 有序 | 固定配置、坐标点、多返回值 |
| dict | 可变 | 无序 | 参数配置、查表映射、缓存 |
| set | 可变 | 无序 | 去重、集合运算、快速成员检查 |
举个例子:你在ATE测试中需要检查某几根管脚是否在某个集合里。用列表的话,每次查找都是O(n);用集合就是O(1),跑大数据量测试时差距非常明显。
# 集合的成员检查效率远高于列表
target_pins = {"A1", "B2", "C3", "D4"} # 集合
all_test_pins = get_all_pins() # 假设有几万个pin
for pin in all_test_pins:
if pin in target_pins: # 集合查找,速度快
do_special_test(pin)
字典的灵活程度超乎想象。我在自动化测试配置里经常用嵌套字典:
test_config = {
"VDD_MIN": {
"voltage": 1.8,
"current_limit": 0.010,
"measure_pin": "VDD_PIN"
},
"VDD_MAX": {
"voltage": 2.0,
"current_limit": 0.015,
"measure_pin": "VDD_PIN"
}
}
# 循环执行不同的测试条件
for test_name, params in test_config.items():
set_voltage(params["voltage"])
current = measure_current(params["measure_pin"])
check_limit(current, params["current_limit"])
最后说一下列表推导式。这东西很Pythonic,但你用它的时候要控制好复杂度。一句话能写清楚的可以用,超过三层条件嵌套的,老老实实写普通循环,别为了装酷牺牲可读性。
# 可读的推导式
valid_pins = [pin for pin in all_pins if pin.startswith("D")]
# 过于复杂的推导式,不如写循环
# result = [x for y in z if a(y) for x in b(y) if c(x)] # 不推荐
坦白讲,Python基础说来说去就是这些东西,但怎么在ATE测试场景里用好它们,才是关键。下一节我们直接上实战——用标准库操作文件、写日志、处理异常,这些才是你写自动化测试脚本时每天都要面对的硬仗。