遵循TDD小步迭代原则,如何开发API响应解析函数?
问题背景
刚读完Kent Beck的《Test Driven Development: By Example》,正在项目里实践TDD。需求是从如下JSON格式的API响应中提取最近的事件:
[ {"1789484079": "event1", "1531059415": "event2"}, {}, {}, {"1234256612": "event3"} ]
数组里的每个对象可能包含多个时间戳键,也可能为空。规则是:给定一个时间戳,返回小于等于该时间戳的最近时间戳对应的事件,比如:
- 给定
1889484079,返回"event1" - 给定
1689484079,返回"event2"
当前测试与实现困境
按照TDD步骤,我先写了测试和返回常量的简易实现:
def parse_response(response, timestamp): return "event1" def test_api_response_parsing_returns_correct_events(): response = [{1789484079: "event1", 1531059415: "event2"}] timestamp = 1889484079 assert parse_response(response, timestamp) == "event1"
接着更新测试让它失败(添加第二个断言):
def test_api_response_parsing_returns_correct_events(): response = [{1789484079: "event1", 1531059415: "event2"}] timestamp = 1889484079 assert parse_response(response, timestamp) == "event1" timestamp = 1689484079 assert parse_response(response, timestamp) == "event2"
然后写了硬编码的实现让测试通过,但存在明显重复:
def parse_response(response, timestamp): if timestamp == 1889484079: return "event1" else: return "event2"
我想改成遍历响应、按规则检查时间戳的实现,但担心步子跨太大不符合小步迭代原则,想知道遵循TDD该怎么推进。
小步迭代优化步骤
第一步:从硬编码转向依赖输入数据
先不直接做全量遍历,先把硬编码的时间戳和事件名替换成从测试传入的response中读取。修改实现,让它能从第一个非空对象里筛选符合条件的时间戳:
def parse_response(response, timestamp): # 先处理测试中的第一个非空对象 event_dict = response[0] # 把字符串时间戳转成整数 timestamps = [int(ts) for ts in event_dict.keys()] # 筛选出小于等于给定时间戳的所有值 valid_timestamps = [ts for ts in timestamps if ts <= timestamp] # 取最近(最大)的时间戳 closest_ts = max(valid_timestamps) return event_dict[str(closest_ts)]
这时候跑测试,两个断言都会通过,同时摆脱了硬编码的具体值,开始依赖输入数据。
第二步:添加空对象场景的测试并优化实现
现在测试里只有一个非空对象,接下来覆盖响应包含空对象的场景。新增测试:
def test_api_response_with_empty_objects(): response = [{}, {1789484079: "event1"}, {}] timestamp = 1889484079 assert parse_response(response, timestamp) == "event1"
这个测试会失败,因为当前实现只取response[0](是空对象)。此时修改实现,遍历所有响应对象,收集非空对象的时间戳:
def parse_response(response, timestamp): all_ts = [] event_map = {} # 遍历所有响应对象,跳过空对象 for obj in response: if not obj: continue # 合并所有时间戳和对应事件 for ts_str, event in obj.items(): ts = int(ts_str) all_ts.append(ts) event_map[ts] = event # 筛选有效时间戳并取最近的 valid_ts = [ts for ts in all_ts if ts <= timestamp] closest_ts = max(valid_ts) return event_map[closest_ts]
现在这个新测试和原有测试都能通过,实现也覆盖了空对象场景。
第三步:添加边界场景测试并完善逻辑
比如测试给定时间戳早于所有事件的情况,先写测试(假设需求是返回最早的事件):
def test_timestamp_earlier_than_all_events(): response = [{1789484079: "event1", 1531059415: "event2"}] timestamp = 1000000000 assert parse_response(response, timestamp) == "event2"
如果当前实现逻辑正确,这个测试会直接通过;如果有问题,再针对性调整实现。
核心TDD原则落地
Kent Beck的小步迭代核心是红-绿-重构循环:
- 红:编写一个会失败的测试(覆盖新场景)
- 绿:写最少的代码让测试通过(哪怕是临时方案)
- 重构:在测试全过的前提下,优化代码消除重复、提升可读性
你之前的硬编码是绿阶段的合理选择,后续重构要拆成小步:先从依赖硬编码转向输入数据,再逐步覆盖空对象、边界值等场景,每一步都保证测试通过,就不会出现步子过大的问题。
内容的提问来源于stack exchange,提问作者hardhypochondria

