Python 25:pyproject 如何生成可安装制品
在24 的隔离环境里,PYTHONPATH=src 能运行任务,但这并不测试安装。现在从项目 pyproject.toml 构建 wheel 与 sdist,再把 wheel 装进另一个没有 pip、也不把源码目录加入 PYTHONPATH 的 venv;从仓库外运行生成的 tasklab 命令,先提交,再通过另一个进程执行和查询同一 SQLite 文件,最后验证重复执行被拒绝。安装后 tasklab.__file__ 位于新环境的 site-packages,不是工作树的 src。 这次新增的是分发边界,不是新任务引擎。21 篇的数据库合同和取消逻辑仍由原模块执行。发布到 PyPI、跨平台兼容性和非信任网络输入不在本章验收范围。 分清三个文件和三种角色 pyproject.toml 的 [build-system] 选择 setuptools.build_meta,将后端 setuptools==80.9.0、构建 wheel 所需 wheel==0.45.1 固定;[project] 保留 name=tasklab、version=0.1.0、requires-python...
Go 11:错误包装后怎样保持可判断
10 确定了查询成功必须返回真正的 nil 接口;缺失任务会返回非 nil 错误。接下来调用方还需要区分空文本、查不到 ID 与文件打不开:如果只根据 err.Error() 的字符串做分支,包装上一层 lookup "task[missing]" 就会改变调用方判断,OS 路径也可能进入 CLI 输出。错误类别应能跨包装层识别,诊断细节则留在内部错误链里。 沿用同一累计工程,本章为当前 CLI 的内部调用方声明 ErrInvalidInput、ErrTaskNotFound、ErrExecutionFailed 三种可用 errors.Is 判断的类别。这里的 package main 不是可供其他模块导入的公共错误库;CLI 对用户显示的消息另行决定。本篇完整累计工程与证据在只包含网站源码的环境中也能下载,附有SHA256SUMS。 独立复跑步骤列出本章正反例、源码校验及运行命令。 哪一层知道错误类别 validateConfig(Config{}) 对非正的 MaxWords 包装非法输入类别;analyzeTask 对空白文本和超过字段上限的文本也返回...
Go 10:接口里的 nil 指针为何不是 nil
09 的 TaskLookup 用 Lookup(id) 返回记录和是否存在的布尔值。查询方往往还要把「无此任务」沿调用栈返回给 CLI:如果函数声明返回 error,在成功路径先留一个 *missingTaskError 类型的 nil 指针,直接把它作为 error 返回,会不会等价于 return nil?不会。此时 err != nil 为真,即使内部那个指针为 nil。 同一累计工程在查询路径加入 lookupRecord(source, id) (TaskRecord, error)。查到任务时返回记录和字面 nil;未查到时返回非 nil 的 *missingTaskError,describeTask 把错误交给 CLI。旧的 --lookup 'task[missing]' 仍输出 task not found: task[missing] 到 stderr 并退出 2,成功查询与其他入口输出不变。真实反例、测试和完整工程见本章素材包与SHA256SUMS。 独立复跑步骤列出本章正反例、源码校验及运行命令。 比较的是接口值,不只是指针 Go 规范允许接口值同时携...
Python 24:一个环境为什么能运行却不能重建
在本机直接运行 /usr/bin/python3.12 -m venv /tmp/... 失败:发行版解释器没有安装 ensurepip,提示虚拟环境无法完成创建。改用 venv --without-pip 能隔离解释器,但新的环境仍没有安装器;它能通过 PYTHONPATH=src 从源码导入 tasklab,却不是“已安装了 tasklab”。这三种状态分别是:解释器能启动、源码包能被找到、发行包已安装。只看最后一次 import 成功,会错过前两处依赖条件。 第19 篇的导入路径和21 篇的 SQLite 仓储为本篇提供了现成的全套回归。这里只操作仓库外的临时虚拟环境,不改项目根配置或依赖锁;wheel/sdist 的干净安装留到 25,不能把设置 PYTHONPATH 当成安装成功。 隔离的是解释器,还是依赖清单 sys.prefix 指向新建虚拟环境,sys.base_prefix 仍指向原始系统解释器。初次默认创建失败后,用 /usr/bin/python3.12 -m venv --without-pip 建立环境。为了能在该环境记录安装器与依赖结果,本次取得 P...
Go 09:查询接口由谁需要的方法决定
08 把任务的 ID、结果和状态放进 TaskRecord。查询当前批次时,调用方实际需要几种方法?只要凭 ID 取到一份记录并辨认是否存在;创建批次、列出所有 ID 和更新状态不必进入同一个接口。先把接口定义为「存储系统的全部能力」,测试替身反而需要伪造一套写入协议。 同一累计工程在查询方附近声明 TaskLookup,只有 Lookup(id string) (TaskRecord, bool)。原 CLI 的 --lookup 'task[0]' 现在通过该接口调用;当前批次实体 BatchTasks 仍用 07 的 map 作为索引,不变成新工程、后台任务或持久化数据库。本章完整工程与 00–09 证据可下载,SHA256SUMS验证其内容。 独立复跑步骤列出本章正反例、源码校验及运行命令。 从一次查找收紧接口 describeTask(source TaskLookup, id string) 只问一件事:调用 source.Lookup(id),不存在就返回 "", false,存在则返回 "task[0] words=..."...
Go 08:任务记录的复制与指针更新
07 用临时 map 把 task[0] 映射到结果 slice 的第 0 项。计数、ID 和状态要放在同一个任务记录里时,先复制记录留作快照,再更新原记录,快照会变吗?答案取决于记录里的字段怎样保存数据,也取决于更新方法的接收者。把 Go 的嵌入写成「类继承」会掩盖这个问题。 同一累计工程引入 TaskRecord:包含 ID、TaskResult 和 Status,后者当前只使用 QUEUED 和 SUCCEEDED。原 CLI 的文本、文件、批次与本次批次的查询仍保持原输出。新增的类型只组织顺序计算的结果,尚未引入 worker 或对外可查询的持久任务;本篇完整归档和SHA256SUMS用于离线复跑。 独立复跑步骤列出本章正反例、源码校验及运行命令。 更新原件,快照保持什么 newTaskRecord("task[0]") 返回值的 ID 与初始 QUEUED 状态。record := newTaskRecord(...) 得到可寻址的局部变量;snapshot := record 复制此时整个结构体值;然后 record.MarkSucceeded(c...
Python 23:如何替换时间而不替换真实行为
同一段 "One two one" 在两组可控时间读数下走出不同的可检查结果:开始前读到 5、deadline 为 5,任务仍为 queued;开始读到 1、计算后读到 6,任务记为 failed,result 保持 null,原因里保留 TimeoutError。没有在测试里 sleep 等待时间到来,也没有把 SQLite 换成一份声称“差不多”的字典。 第22 篇的测试分层强调的是断言真实状态,而非统计 mock 被调用的次数。本篇只替换时间读数,用真实任务函数和真实临时数据库检验超时后的结果;还保留 16 的实际文件句柄关闭路径。 让时钟可控,工作仍真实 app.run 增加可选的 clock 和绝对时间 deadline 参数,默认时钟是 time.monotonic。当前顺序执行在开始前与计算后各检查一次:若开始前已到 deadline,抛 TimeoutError 且保留排队状态;若计算后才超时,由原来捕获执行故障的路径设置 failed、记录错误并抛出带原因的 TaskExecutionError。SQLite 仓储把同一时钟与 deadli...
Go 07:map 的零值为什么不能表示任务不存在
06 的批次按顺序保存在 slice 中;删除第一项并添加新项之后,调用者可按 task[0]、task[1] 看到当前批次的编号。如果另存一张 map[string]int 记录 ID 到切片位置的映射,positions["task[0]"] 得到 0。查询不存在的 ID 也可能得到 0:仅靠一次索引无法说明第一项真的存在。 同一累计工程现在给 --batch 加入互斥的 --lookup ID 和 --list-ids。它只是一次 CLI 调用期间建立的索引,不保存跨进程的任务身份,也没有后台 worker。原 --text、--file、顺序 --batch 与 06 的增删用法仍有效。完整累计工程与原始结果和SHA256SUMS可独立下载复跑。 独立复跑步骤列出本章正反例、源码校验及运行命令。 第 0 项与缺失键的共同返回值 indexTasks 用 make(map[string]int, count) 初始化映射,遍历当前批次,为每个位置写入 task[0]、task[1] 等键;这里的 count 是容量提示,不是固定的最大可添加条数。查找时必...
Go 06:复制 slice 后为什么仍会改写任务输入
05 解决了单次文件输入的关闭责任;顺序批量入口则把多条文本放进 []string。现在需要从待处理批次中删除第一项,再追加一项。如果调用者也保留着原切片,处理函数返回后它的内容会不会变?一个看似无害的 input = input[1:],配合随后的 append,足以让仍由调用者持有的数组发生变化。 同一个累计工程为 --batch 增加 --drop-first 和 --append text,其他 --text、--file 输入不变。两项编辑选项只对批量输入有效;原有 stdout 暂存逻辑继续保证:即使编辑后某一项不合法,本次调用也不输出半份成功结果。源码、失败前后的原始测试与当前累计工程在06 素材包,下载后用SHA256SUMS核验。 独立复跑步骤列出本章正反例、源码校验及运行命令。 失败的断言针对谁的数组 TestPrepareBatchDoesNotChangeInput 构造 backing := []string{"one", "two", "guard", "spare"},再...
Python 22:哪些测试能够证明业务不变量
把 SQLite 的 cancel 暂时替换为“直接返回 True、实际不改任务状态”。调用方拿到“取消已接受”,重新打开数据库却仍看见 queued。新加的回归测试在 state == "cancelled" 处失败;恢复真实方法后,累计 68 项测试通过。这个故障不是抽象的“测试覆盖率提高”,而是把一条承诺——取消排队任务后查询能看见 cancelled、不能再执行——交给能反驳它的断言。 第21 篇的 SQLite 事务已经留下了真实数据库和失败注入。本篇不另造一套 tasklab,只整理测试的责任边界,并用一次受控变异确定测试确实会对业务规则的破坏报警。 一个不变量要在哪些地方观察 进程内单元测试用 create_store → submit → cancel → query,检查返回 True、状态 cancelled、结果 None、之后执行被拒绝;它证明当前 Python 对象之间的状态流转。数据库集成测试用临时文件运行相同四个动作,另建 SQLiteTaskGateway 再查状态和结果;它额外检查了真实 SQLite 写入与重开连接可见性。...










