TDD开发中如何测试动作执行顺序?求通用解决方案
测试有执行顺序要求的TDD代码通用方案
在TDD开发需要按特定顺序执行动作的代码时,常遇到测试无法区分正确顺序与错误顺序实现的问题。比如发送AT命令的exchange函数,要求先发送命令,再读取响应,但最初的测试无法阻止“先读响应再发命令”的错误实现通过。
问题示例
最初的测试代码:
def test_exchange_sends_a_command_and_returns_the_received_response(): stdout = io.StringIO() stdin = io.StringIO("CONNECT\r\n") response = exchange(stdout, stdin, "ATD") assert stdout.getvalue() == "ATD\r\n" assert response == "CONNECT"
正确实现:
def exchange(stdout, stdin, command): stdout.write(command + "\r\n") return stdin.readline().strip()
错误实现(仍能通过测试):
def exchange(stdout, stdin, command): response = stdin.readline().strip() stdout.write(command + "\r\n") return response
通用测试模式
1. 带序列验证的自定义测试替身
实现能记录调用顺序的标准输入输出替身,测试时不仅验证最终状态,还检查动作的执行顺序:
class OrderedStdoutStub: def __init__(self): self.written_content = [] self.interactions = [] def write(self, buf): self.written_content.append(buf) self.interactions.append(("write", buf)) class OrderedStdinStub: def __init__(self, response): self.response = response self.interactions = [] def readline(self): self.interactions.append(("readline",)) return self.response def test_exchange_correct_order(): stdout = OrderedStdoutStub() stdin = OrderedStdinStub("CONNECT\r\n") response = exchange(stdout, stdin, "ATD") # 验证最终结果 assert stdout.written_content == ["ATD\r\n"] assert response == "CONNECT" # 验证执行顺序:先发送命令,再读取响应 assert stdout.interactions[0] == ("write", "ATD\r\n") assert stdin.interactions[0] == ("readline",) # 确保write动作早于readline all_interactions = stdout.interactions + stdin.interactions assert all_interactions.index(("write", "ATD\r\n")) < all_interactions.index(("readline",))
2. 利用Mock框架做行为验证
借助语言自带的Mock工具(如Python的unittest.mock),直接验证方法的调用顺序:
from unittest.mock import Mock, call def test_exchange_with_mock_validation(): stdout = Mock() stdin = Mock() stdin.readline.return_value = "CONNECT\r\n" response = exchange(stdout, stdin, "ATD") # 验证调用内容和顺序 stdout.assert_has_calls([call.write("ATD\r\n")]) stdin.assert_has_calls([call.readline()]) # 严格验证全局调用顺序:write先于readline all_calls = stdout.method_calls + stdin.method_calls assert all_calls == [call.write("ATD\r\n"), call.readline()] assert response == "CONNECT"
3. 基于“间谍”模式的交互记录
这种模式下,测试替身会完整记录所有交互行为,测试时直接检查交互的时间线,比状态验证更精准。上面的Mock方案本质就是间谍模式的实现。
核心思路
之前的测试仅验证最终状态(输出内容、返回值),未验证执行顺序。上述方案通过记录动作序列,确保代码严格按照业务要求的顺序执行,彻底排除错误顺序的实现。
内容的提问来源于stack exchange,提问作者Bart
相关产品推荐
相关产品推荐

