让惰性导出行为可被观察
目标
验证行的消费次数、换行转义、公开字段和响应格式。
为什么重要
用小列表测试过的导出代码看上去一切正常,却在把大输入全部转成 list 时耗尽了内存。结果相同,并不意味着运行方式的契约也相同。必须把惰性消费变成肉眼可见的状态。
步骤
- 在
/root/work/test-lazy-export-lab/test_service.py中测试所提供 service.py 的下列公开契约:validate_row(row) 在 row 是 dict、id 是除 bool 之外的正 int、name 是非空 str 时,返回 row。其余抛出 ValueError。允许存在额外的内部字段。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
先做一次准备。已有的文件不会被覆盖。
mkdir -p /root/work/test-lazy-export-lab
test -e /root/work/test-lazy-export-lab/service.py || cp /opt/fixtures/ten_labs/test-lazy-export-lab/service.py /root/work/test-lazy-export-lab/service.py
test -e /root/work/test-lazy-export-lab/test_service.py || cp /opt/fixtures/ten_labs/test-lazy-export-lab/test_service.py /root/work/test-lazy-export-lab/test_service.py
cd /root/work/test-lazy-export-lab
-
在
/root/work/test-lazy-export-lab/test_service.py中测试所提供 service.py 的下列公开契约:project(row) 在 validate_row 之后,返回只含 id 和 name 的新 dict。原始的内部字段保持不变。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。 -
在
/root/work/test-lazy-export-lab/test_service.py中测试所提供 service.py 的下列公开契约:encode_line(row) 把 project 的结果用 ensure_ascii=False、separators=(',',':')、sort_keys=True 编码为 JSON,并在末尾追加一个 '\n',返回 str。name 中的换行必须是 JSON 转义。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。 -
在
/root/work/test-lazy-export-lab/test_service.py中测试所提供 service.py 的下列公开契约:validate_max(value) 只在 bool 之外的 int 处于 1–1000 范围内时原样返回,其余抛出 ValueError。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。 -
在
/root/work/test-lazy-export-lab/test_service.py中测试所提供 service.py 的下列公开契约:take_rows(rows, maximum) 是一个 iterator,用 islice 等方式最多惰性地返回 maximum 个。调用时会验证 maximum,每调用一次 next,只消费一次输入。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。 -
在
/root/work/test-lazy-export-lab/test_service.py中测试所提供 service.py 的下列公开契约:json_lines(rows, maximum=100) 对从 take_rows 得到的每一行 yield 出 encode_line。不返回把全部拼起来的字符串或列表。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。 -
在
/root/work/test-lazy-export-lab/test_service.py中测试所提供 service.py 的下列公开契约:decode_lines(text) 对 splitlines 得到的每个非空行先 json.loads,再 validate_row,并以列表返回。空字符串返回 [],中间出现空行则抛出 ValueError。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。 -
在
/root/work/test-lazy-export-lab/test_service.py中测试所提供 service.py 的下列公开契约:create_app(rows) 在 GET /export 上,把 json_lines(rows, 100) 作为 application/x-ndjson 的 StreamingResponse 返回。rows 是可再次遍历的列表。不能含有内部字段,并且必须保留每一行的内容和顺序。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
参考
- 无需联网,也无需安装软件包,在现有的 lab-dev 环境中进行。
- 每一步都在 45 秒的评分预算内运行。不要添加真实的 sleep 或网络调用。
- 提交的测试会在另外的临时文件夹中,分别在正确实现和有缺陷的实现上运行。在正确实现上,实际运行的测试必须全部通过;在有缺陷的实现上,测试本体必须失败。收集错误、运行 0 个、全部跳过、被强制终止都不算通过。只使用 pytest 的基本功能和提供的库。
- FastAPI 官方文档 · pytest 官方文档 · Python sqlite3
- 局限:TestClient 会缓冲响应,所以它无法证明网络首字节的延迟,也无法证明内存上限的全部。是否做到惰性求值,要另用计数生成器来检查。流开始之后如果遇到错误的行,就很难再把状态改成正常的错误 JSON。真实服务必须决定在预先验证、逐行错误格式、中止策略之中采用哪一种。提供的实现可以阅读,但评分使用的是另外的副本。不要通过检查源码措辞或修改文件来绕过缺陷,而要检查公开接口的实际运行结果。
验证行契约——测试
在 /root/work/test-lazy-export-lab/test_service.py 中测试所提供 service.py 的下列公开契约:validate_row(row) 在 row 是 dict、id 是除 bool 之外的正 int、name 是非空 str 时,返回 row。其余抛出 ValueError。允许存在额外的内部字段。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
先做一次准备。已有的文件不会被覆盖。
mkdir -p /root/work/test-lazy-export-lab
test -e /root/work/test-lazy-export-lab/service.py || cp /opt/fixtures/ten_labs/test-lazy-export-lab/service.py /root/work/test-lazy-export-lab/service.py
test -e /root/work/test-lazy-export-lab/test_service.py || cp /opt/fixtures/ten_labs/test-lazy-export-lab/test_service.py /root/work/test-lazy-export-lab/test_service.py
cd /root/work/test-lazy-export-lab
区分 bool 与数字,并把空名称当作错误处理。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。
保存后用 bash /opt/lab/checks/test-lazy-export-lab/01-contract.sh 确认。
只生成公开行——测试
在 /root/work/test-lazy-export-lab/test_service.py 中测试所提供 service.py 的下列公开契约:project(row) 在 validate_row 之后,返回只含 id 和 name 的新 dict。原始的内部字段保持不变。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
导出路径也必须与普通 API 采用相同的公开字段策略。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。
保存后用 bash /opt/lab/checks/test-lazy-export-lab/02-contract.sh 确认。
保留行边界地编码——测试
在 /root/work/test-lazy-export-lab/test_service.py 中测试所提供 service.py 的下列公开契约:encode_line(row) 把 project 的结果用 ensure_ascii=False、separators=(',',':')、sort_keys=True 编码为 JSON,并在末尾追加一个 '\n',返回 str。name 中的换行必须是 JSON 转义。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
如果用字符串拼接来构造 JSON,遇到引号和换行时格式就会被破坏。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。
保存后用 bash /opt/lab/checks/test-lazy-export-lab/03-contract.sh 确认。
验证输出数量上限——测试
在 /root/work/test-lazy-export-lab/test_service.py 中测试所提供 service.py 的下列公开契约:validate_max(value) 只在 bool 之外的 int 处于 1–1000 范围内时原样返回,其余抛出 ValueError。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
要求调用方给出上限,避免不小心把无限输入一直读到底。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。
保存后用 bash /opt/lab/checks/test-lazy-export-lab/04-contract.sh 确认。
只消费需要的行——测试
在 /root/work/test-lazy-export-lab/test_service.py 中测试所提供 service.py 的下列公开契约:take_rows(rows, maximum) 是一个 iterator,用 islice 等方式最多惰性地返回 maximum 个。调用时会验证 maximum,每调用一次 next,只消费一次输入。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
一旦转换成 list(rows),就无法处理无限输入和大容量输入了。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。
保存后用 bash /opt/lab/checks/test-lazy-export-lab/05-contract.sh 确认。
惰性序列化行——测试
在 /root/work/test-lazy-export-lab/test_service.py 中测试所提供 service.py 的下列公开契约:json_lines(rows, maximum=100) 对从 take_rows 得到的每一行 yield 出 encode_line。不返回把全部拼起来的字符串或列表。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
让对象的选取与表示形式的转换,各自都保持为惰性阶段。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。
保存后用 bash /opt/lab/checks/test-lazy-export-lab/06-contract.sh 确认。
重新验证下载的行——测试
在 /root/work/test-lazy-export-lab/test_service.py 中测试所提供 service.py 的下列公开契约:decode_lines(text) 对 splitlines 得到的每个非空行先 json.loads,再 validate_row,并以列表返回。空字符串返回 [],中间出现空行则抛出 ValueError。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
区分空文件与格式被破坏的空记录。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。
保存后用 bash /opt/lab/checks/test-lazy-export-lab/07-contract.sh 确认。
完成 HTTP 下载——测试
在 /root/work/test-lazy-export-lab/test_service.py 中测试所提供 service.py 的下列公开契约:create_app(rows) 在 GET /export 上,把 json_lines(rows, 100) 作为 application/x-ndjson 的 StreamingResponse 返回。rows 是可再次遍历的列表。不能含有内部字段,并且必须保留每一行的内容和顺序。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
要同时配合生成器测试,确认没有只在 Content-Type 上写流式、内部却把全部内容收集起来。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。
保存后用 bash /opt/lab/checks/test-lazy-export-lab/08-contract.sh 确认。