同一段脚本,在编辑器里运行成功,从终端运行就找不到数据。接手的人还需要猜解释器、猜工作目录,才看得到作者说的那个结果。这种练习即使只有十几行代码,也已经出现了可复现性问题。
今天做一个最小项目:输入四条订单,输出订单数量。重点是把执行方式、路径策略和验收结果写明白,让下一步业务逻辑有一个稳定起点。
朋友们好,我是楠风。这是「楠风测开 · 测试工程师复习日记」Day19。本文用标准库完成实现,核验环境是 Windows 和 Python 3.12.10;不把环境版本写成"最新",也不假设其他操作系统已经跑过。
从验收条件倒推第一份代码
先定义三个行为:读取四条订单时输出四;读到合法空列表时输出零;文件打不开、JSON 不合法或者外层结构错误时失败。
第三个行为尤其容易漏。起步阶段为了减少报错,有人会把所有异常都变成 []。这样程序确实能走到末尾,但"没有订单"和"没有读到数据"就合并成了同一种结果。后面把它接入自动化任务时,很难再判断成功意味着什么。
所以第一版不增加网络、数据库或测试框架,只把本地输入到终端输出这段路径做好。短程序也要有范围:计数成功只说明订单列表被读取,不说明每笔订单金额正确。
选择一:把解释器写进运行方式
创建项目目录 python-lab,在其中运行:
powershell
python --version
python -m venv .venv
.\.venv\Scripts\python.exe -c "import sys; print(sys.prefix != sys.base_prefix)"
最后一条预期是 True。后续命令都明确使用 .venv 的解释器,不依赖当前终端是否激活过环境。
这使运行说明本身就能表达环境选择。若编辑器使用另一套 Python,用 import sys; print(sys.executable) 对照解释器位置,再调整编辑器设置。不要看到包导入失败就立刻全局安装。
虚拟环境并不要求先执行激活脚本,直接调用其中的解释器就是官方支持的方式。venv 文档也说明,环境一般应在目标位置重新创建,而不是把整个目录当作可移植安装包。
本文没有第三方依赖。以后需要安装时,用对应解释器的 -m pip,保持"由谁安装、由谁运行"一致。macOS/Linux 将解释器路径换为 .venv/bin/python,创建环境通常使用 python3 -m venv .venv;这部分是对应命令说明,非本文实测结论。
选择二:样例输入跟随脚本位置
项目只需要以下内容,自己的文件不要放进虚拟环境目录:
text
python-lab/
├── .venv/
├── hello_orders.py
└── data/
└── orders.json
保存 data/orders.json,编码为 UTF-8:
json
{
"orders": [
{"order_id": "1001", "status": "PAID", "amount": "199.80"},
{"order_id": "1002", "status": "CREATED", "amount": "59.90"},
{"order_id": "1003", "status": "CANCELLED", "amount": "99.90"},
{"order_id": "1004", "status": "PAID", "amount": "159.80"}
]
}
这份数据是自有教学样例,不需要生产账号。金额用字符串保留两位小数表示,今天只数订单,后续再讨论数值计算。
现在有两种路径方案。Path("data/orders.json") 根据调用命令时的工作目录寻找文件,适合调用方明确指定运行位置的脚本;从 __file__ 出发,则适合与代码一起分发的固定样例。本篇选择后者。
选择的依据是输入角色,不是谁更"高级"。到后续开发命令行工具时,用户显式传入的文件参数可能应该按工作目录解析;把所有路径一律改成脚本目录,反而会违背用户预期。
选择三:读取、检查与输出依次发生
将以下完整代码保存为 hello_orders.py:
python
import json
from pathlib import Path
def main():
source = Path(__file__).resolve().parent / "data" / "orders.json"
with source.open(encoding="utf-8") as stream:
document = json.load(stream)
if not isinstance(document, dict) or not isinstance(document.get("orders"), list):
raise ValueError("JSON 顶层必须是含 orders 列表的对象")
print(f"输入:{source.name}")
print(f"订单总数:{len(document['orders'])}")
if __name__ == "__main__":
main()
Path 表达路径,open 时明确编码;具体接口可查 pathlib 文档。固定样例按 UTF-8 保存、按 UTF-8 读取,让编码约定成为代码的一部分。
json.load 完成的是语法解析。解析成功以后,再检查顶层字典与 orders 列表。字符串 JSON 使用 json.loads,文件对象使用 json.load,两者职责可从 json 官方接口说明核对。
把输出放在检查之后,能避免结构错误时先打印一条看起来成功的统计信息。main 让入口集中,底部判断使导入文件时不自动执行。这里不引入类和项目框架,因为目前只有一条清晰的执行路径。
还有一个"能跑但不对"的细节:len(document) 得到的是顶层字段数;len(document["orders"]) 才是订单数。代码评审不能只看是否调用了 len,还要看它作用于哪一层数据。
核验的是行为,不是截图
在项目目录执行:
powershell
.\.venv\Scripts\python.exe hello_orders.py
这份样例的实测输出:
text
输入:orders.json
订单总数:4
随后在隔离临时目录中,使用同一份脚本做了四个变化:合法空列表输出零;空文件产生 JSONDecodeError;顶层数组产生 ValueError;文件缺失产生 FileNotFoundError。正常与空列表成功退出,其余失败退出。脚本也从另一个工作目录调用过,依然按自己的位置寻找样例。
这些结果证明了当前的输入约定与路径选择。它们没有证明金额汇总正确,更没有证明程序适用于任意体量、任意结构的数据。把证据的范围说清楚,后续添加功能时才知道哪些条件需要重新验证。
调试时也按层次走:先看进程有没有启动,再看脚本能否找到,再看输入能否打开,然后看 JSON 和结构,最后核对数量。每次只改变一个条件,保留原始样例,能够减少"改完好了,但不知道为什么"的情况。
读者复现异常分支时,可以另建副本目录,只复制脚本与 data 子目录,进入副本重新创建 .venv。之后改的是副本里的固定输入,执行的也是副本脚本。仅把数据另存成另一个文件名,原脚本并不会自动切换去读取它。
提交这份练习时,也要把虚拟环境与项目材料区分开。代码、输入和说明能表达任务,环境目录只是本机的一次构建结果。接手的人按照说明重建环境后,应当看到相同样例结果;如果仍依赖你机器上的路径设置,运行说明就还缺一部分。
把学习路线写成后续提交
这个项目接下来可以自然长出几个小提交:先把订单字段转成校验契约,再修复错误的金额计算,然后增加命令行输入和报告输出;有了测试后,再练习评审 AI 提供的实现。工具选择应服务这些任务,不需要在第一天就把常见库全部装齐。
每个提交都要回答三个问题:输入增加了什么约束,输出增加了什么信息,失败能否被调用方识别。这些问题也能帮助判断一篇教程是否真的推进了项目,而不只是把相同代码换一种语法重写。
建议保留一段简短运行说明,写清 Python 版本、创建环境的命令、执行命令、样例预期输出。不要把机器上的绝对目录写死进源文件,也不要依赖只有自己配置过的编辑器按钮。
今天可以交付:
- 一份只依赖标准库的入口脚本和四条订单样例。
- 明确的解释器调用与脚本相对路径约定。
- 正常、空集合、无效输入的核验记录。
- 一条后续边界:业务字段还未验证,下一次提交再加入。
你会如何区分固定样例路径和用户传入路径?欢迎分享自己的判断方式。下一篇是 Day20《Python核心语法:读懂并处理JSON数据》,将加入可复用的数据校验与金额汇总。关注楠风测开,我们沿这个小项目继续做能复现、能评审的改动。