3. 第一个自动化脚本
好,咱们正式开始写第一个自动化脚本。
说实话,很多新手一上来就想搞个「万能框架」。我个人习惯是——先跑通一条命令,再谈批量。你想想看,连单个仿真都抓不住输出,批量跑一百个又有什么意义?
3.1 为什么是subprocess?
Python里执行系统命令,主流就三个选择:os.system、os.popen、subprocess。
我直接说结论:用subprocess。为什么?
os.system只能拿到退出码,拿不到输出内容。你连仿真报了什么错都不知道。os.popen虽然能读输出,但控制能力弱,容易卡死。subprocess是亲儿子,功能全,坑少。
我在项目中遇到过有人用 os.system 跑仿真,结果仿真挂了三天三夜没人发现——因为脚本只看退出码是否为0,根本没捕获stderr里的「Fatal Error」。
3.2 最简版本:跑一条命令
先看一个最基础的例子。假设我们要跑一条VCS仿真命令:
import subprocess
cmd = "vcs -R -f test.f +v2k"
result = subprocess.run(cmd, shell=True)
print("返回码:", result.returncode)
嗯,这里要注意:shell=True 表示通过系统shell执行。好处是你可以直接用管道、通配符。坏处是——有安全风险。如果你拼接用户输入的命令,小心注入攻击。
我个人习惯:测试脚本里用 shell=True 没问题,因为命令是写死的。但如果是Web服务调仿真,必须用 shell=False + 列表传参。
3.3 捕获输出:stdout和stderr
光看返回码不够。仿真失败了,你得知道哪里失败。捕获输出才是关键。
import subprocess
cmd = "vcs -R -f test.f +v2k"
result = subprocess.run(
cmd,
shell=True,
capture_output=True, # 捕获 stdout 和 stderr
text=True # 以文本形式返回,而不是字节
)
print("标准输出:")
print(result.stdout)
print("标准错误:")
print(result.stderr)
print("返回码:", result.returncode)
这里 capture_output=True 是Python 3.7+ 的写法。老版本你得写 stdout=subprocess.PIPE, stderr=subprocess.PIPE。效果一样。
text=True,输出就是字符串,不用手动 .decode()。少写一行是一行。
3.4 实时打印 vs 事后查看
仿真跑起来很慢,你肯定想实时看到日志,而不是等跑完了再看。这时候 subprocess.run 就不够用了——它要等进程结束才返回。
换成 subprocess.Popen:
import subprocess
cmd = "vcs -R -f test.f +v2k"
proc = subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
# 实时读取每一行
for line in proc.stdout:
print(line, end='')
# 等进程结束
proc.wait()
print("返回码:", proc.returncode)
我曾经踩过一个坑:只读stdout,不读stderr。结果进程因为stderr缓冲区满了,直接卡死。你想想看,仿真跑了两个小时,最后卡在最后一行——那种感觉,啧。
stderr=subprocess.STDOUT。
3.5 超时处理:别让脚本死等
仿真有时候会卡住。比如某个testcase进入了死循环。脚本不能无限等下去。
import subprocess
cmd = "vcs -R -f test.f +v2k"
try:
result = subprocess.run(
cmd,
shell=True,
capture_output=True,
text=True,
timeout=3600 # 1小时超时
)
except subprocess.TimeoutExpired:
print("仿真超时!请检查testcase")
超时时间怎么设?我一般这样:
- 单个testcase:取平均运行时间的2倍
- 回归测试:取最慢testcase的1.5倍
- 实在没数据:先设30分钟,跑一轮再调
3.6 完整示例:一个可复用的仿真函数
把上面的知识点串起来,写一个函数。以后每个脚本都能直接调用:
import subprocess
import sys
def run_simulation(cmd, timeout=3600):
"""
运行仿真命令,返回 (返回码, 标准输出, 标准错误)
"""
print(f"[INFO] 执行命令: {cmd}")
try:
result = subprocess.run(
cmd,
shell=True,
capture_output=True,
text=True,
timeout=timeout
)
# 打印关键信息
if result.returncode != 0:
print(f"[WARN] 返回码非0: {result.returncode}")
# 只打印最后20行错误,避免刷屏
err_lines = result.stderr.strip().split('\n')
for line in err_lines[-20:]:
print(f"[STDERR] {line}")
return result.returncode, result.stdout, result.stderr
except subprocess.TimeoutExpired:
print(f"[ERROR] 超时!命令执行超过 {timeout} 秒")
return -1, "", "Timeout"
except Exception as e:
print(f"[ERROR] 未知异常: {e}")
return -2, "", str(e)
# 使用示例
if __name__ == "__main__":
ret, stdout, stderr = run_simulation("echo 'hello world'")
print(f"返回码: {ret}")
3.7 本章知识体系
下面这张图帮你理清思路:
3.8 几个容易忽略的细节
| 参数 | 作用 | 我踩过的坑 |
|---|---|---|
shell=True |
通过shell执行 | 路径有空格时记得加引号 |
capture_output |
捕获stdout/stderr | 不设这个,输出直接打印到终端,脚本拿不到 |
text=True |
返回字符串而非字节 | 忘了设的话,输出是 b'...' 格式,还得 decode |
timeout |
超时保护 | 不设的话,仿真卡死脚本也卡死 |
好了,第一个自动化脚本就这些内容。说白了就是三件事:跑命令、抓输出、防卡死。你把这个函数写好,后面批量跑测试用例就是套个循环的事。
run_simulation 函数敲一遍。别复制粘贴——亲手敲一遍,印象深十倍。
可可精品资料库