跳转到内容

搜索仅适用于生产版本。 尝试构建并预览网站以在本地测试。

自动化测试框架

https://github.com/youngyangyang04/Test-Automation-Framework

https://www.yuque.com/chengxuyuancarl/fzian3? 《自动化测试》 密码:fbdg(密码定期修改)

自动化测试框架可以抽象出几种角色,任何测试框架都是

  • test_data

  • data_extractor

  • test_executer

先假设被测系统可以抽象成一个带上下文的函数:

SUT : (Input, Context) -> Output

在这个层面,我们完全不关心 HTTP 还是 gRPC,甚至不关心它是不是代码,只把它当作一个黑盒函数

1)test_data:测试意图的抽象表示

Section titled “1)test_data:测试意图的抽象表示”

在抽象层面,test_data 不是文件,不是表格,而是测试意图的一个元素。在某个前置条件下,对 SUT 做某种操作,应该满足某种性质。也就是说,抽象的 test_data 至少携带三类语义(不是字段,是语义):

  1. 前置世界(上下文)
  • 系统处于怎样的业务状态(用户有多少余额、订单在什么阶段…)
  1. 操作/刺激
  • 我要对 SUT 施加什么操作(调用哪个能力,传入什么意图)
  1. 期望性质(不一定是具体值,可以是性质):
  • 例如:余额减少但不为负数、列表长度增加 1、满足某个不变量

现在的test_data,本质就是对 SUT 行为的一条约束 / 场景描述,而不是任何具体的数据结构。


2)data_extractor:从抽象测试用例到可执行测试的映射

Section titled “2)data_extractor:从抽象测试用例到可执行测试的映射”

在这个高度,data_extractor 可以看成是一个纯函数

data_extractor : TestSpecSpace -> ExecutableTestSpace

也就是说,它负责把人类视角 / 业务视角的测试意图,翻译成执行引擎能直接执行的测试程序。

在抽象层,它做的是语义层的编译,而不是单纯的解析文件。

你可以把它类比成:

  • 编译器:从高级语言代码 → 中间表示(IR)

  • 模型到模型的转换器:从业务场景模型 → 测试执行模型

它需要完成的抽象责任,比如:

  1. 把业务世界的概念映射到调用世界的概念
  • 如:用户=黄金会员 → 某个 userId + 获取 token 的方式

  • 下单成功 → 一系列对 SUT 的调用序列(准备环境 + 下单)

  1. 把性质翻译成可执行断言或检查步骤
  • 如: 

余额减少但不为负数 →

  • Step A: 调前查询余额

  • Step B: 调用支付接口

  • Step C: 再查余额 + 计算差值 + 判断结果

  1. 生成一个结构化的可执行测试脚本(仍可非常抽象,不等于具体语言),例如:
ExecutableTest =
{ pre_steps, invoke_steps, post_checks, expected_relations, … }

此时我们完全不关心这个 ExecutableTest 是 Python 对象、XML 文件还是别的什么,只关心它在语义上是一种对 SUT 的操作与检查序列


3)test_executer:解释 / 运行可执行测试的语义

Section titled “3)test_executer:解释 / 运行可执行测试的语义”

在这个高度上,test_executer 是一个解释器

test_executer : ExecutableTestSpace × SUT -> TestResult

它的职责是:

给我一个可执行测试描述(ExecutableTest)和一套被测系统实现,我根据这个描述去驱动系统,并产生一个带有判定的结果。

这里的关键点:

  • test_executer 只懂 ExecutableTest 这个中间表示的语义
  • 例如:pre_steps 代表前置操作序列

  • invoke_steps 代表主要调用

  • post_checks 代表检查

  • 它不需要也不应该理解业务含义,只负责:
  • 按顺序/规则执行这些步骤

  • 收集观察到的 Output / 中间状态

  • 交给检查逻辑(可以包含在 ExecutableTest 里,也可以进一步抽象成 oracle)来判断

换句话说,这个 test_executer 已经抽象成:

一种对测试中间语言的解释器 / 虚拟机。

这在概念上非常像:

  • Programming Language:
  • Source Code(test_data)

  • Compiler(data_extractor)

  • VM / Runtime(test_executer)


2. 把三层抽象到你完全不关心具体实现的样子

Section titled “2. 把三层抽象到你完全不关心具体实现的样子”

如果把刚才的东西压缩成纯概念图,就是:

+------------------+
| TestSpecSpace | (抽象测试规格 / 测试意图)
+---------+--------+
|
| data_extractor : TestSpec -> ExecutableTest
v
+-----------------------+
| ExecutableTestSpace | (测试中间表示 / 测试脚本)
+-----------+-----------+
|
| test_executer : ExecutableTest × SUT -> TestResult
v
+------------------+
| TestResult | (通过 / 未通过 / 不确定 + 观测信息)
+------------------+

这个图里,你关心的只有三个集合/空间两个映射

  • 集合(空间):
  • TestSpecSpace:抽象 test_data 所在的世界

  • ExecutableTestSpace:data_extractor 输出的可执行测试世界

  • TestResult:执行结果空间

  • 映射:
  • data_extractor:规格 → 可执行测试

  • test_executer:可执行测试 + SUT → 结果


假设被测系统(SUT)里有三类 HTTP 操作(仍然是抽象的):

  • Login: 

输入:(username, password) 

输出:token

  • CreateOrder: 

输入:(token, sku_list) 

输出:order_id

  • PayOrder: 

输入:(token, order_id) 

输出:payment_result

在模型层,你可以把每个接口抽象成一个操作符

Login : Cred -> Token
CreateOrder : Token × SkuList -> OrderId
PayOrder : Token × OrderId -> PayResult

这些只是函数签名式的类型,没有任何协议细节。


我们要做的测试场景是:

一个黄金会员成功登录,创建订单并支付成功。

这个时候,test_data 不是任何文件,而是一个场景规格(ScenarioSpec) 。 

可以把它理解成一个带依赖的步骤图 + 性质约束

2.1 把 test_data 看成一个有向图(抽象)

Section titled “2.1 把 test_data 看成一个有向图(抽象)”

定义一个抽象的 TestSpec 类型:

TestSpec = (Steps, Edges, Properties)
  • Steps:一组抽象步骤节点,每个节点只是说明调用哪个操作符、扮演什么角色,不带具体值

  • Edges:步骤之间的依赖关系(谁的输出喂给谁)

  • Properties:对整个场景的期望性质(不是具体字段值,而是判断规则)

对我们这个 HTTP 场景,一个可能的 TestSpec 是:

Steps =
L: Login( user_role = GOLD_MEMBER )
C: CreateOrder( actor = L.user, items = {skuA, skuB} )
P: PayOrder( actor = L.user, target_order = C.order )
Edges =
L.token -> C.token
L.token -> P.token
C.order_id -> P.order_id
Properties =
P.payment_result.status == SUCCESS
balance_after(L.user) = balance_before(L.user) - order_amount(C.order)

注意这里:

  • user_role = GOLD_MEMBER 这种东西是业务语义,还不是具体 userId

  • skuAskuB 也可以只是抽象商品标签,不是具体 sku 编号

  • L.tokenC.order_id符号变量,只是说P 这个步骤依赖 L、C 的输出

这就是抽象 test_data: 

一个图 + 约束的对象,描述了要怎么走场景、场景里数据是怎么流动的、以及期待满足什么性质。 

完全不涉及实现细节。


3. 抽象层面的 data_extractor:spec → 可执行测试

Section titled “3. 抽象层面的 data_extractor:spec → 可执行测试”

现在看 data_extractor

在模型层,它就是一个函数

data_extractor : TestSpec -> ExecutableTest

我们再给 ExecutableTest 一个抽象定义,比如:

ExecutableTest = (ConcreteSteps, Order, Checkers)
  • ConcreteSteps:带已绑定参数的步骤

  • Order:执行顺序 / 拓扑排序结果

  • Checkers:可执行的检查规则(可能还是抽象的表达式树)

对刚才的那个 TestSpecdata_extractor 在抽象层大致做三件事:

3.1 语义绑定(从角色/标签到具体值空间的投影)

Section titled “3.1 语义绑定(从角色/标签到具体值空间的投影)”

例如:

bind_user(GOLD_MEMBER) → some_user_id ∈ UserIdSpace
bind_sku(skuA) → some_sku_code_A ∈ SkuSpace
bind_sku(skuB) → some_sku_code_B ∈ SkuSpace

这一步的抽象含义是:

从业务标签空间映射到系统可接受的值空间。

我们完全不说这个映射怎么查,是表还是服务,只说它在数学上是一个映射函数。

3.2 把依赖图转成可执行步骤 + 变量绑定

Section titled “3.2 把依赖图转成可执行步骤 + 变量绑定”

得到的 ConcreteSteps 可能长这样(仍然抽象):

ConcreteSteps =
L: Login( username = some_user_id, password = some_pwd )
C: CreateOrder( token = L.token, sku_list = [sku_code_A, sku_code_B] )
P: PayOrder( token = L.token, order_id = C.order_id )

此时你还是只知道:

  • 每个步骤要调用哪个操作符(Login / CreateOrder / PayOrder)

  • 每个实参从哪里来(常量、绑定结果、前序步骤输出)

3.3 把性质表达式翻译成可执行检查器

Section titled “3.3 把性质表达式翻译成可执行检查器”

比如 Properties 中的性质:

P.payment_result.status == SUCCESS
balance_after(L.user) = balance_before(L.user) - order_amount(C.order)

data_extractor 翻译成某种可执行检查表达式树

Checkers =
checker1: (P.payment_result.status) == SUCCESS
checker2: (balance_after) == (balance_before - order_amount)

最终,data_extractor 输出一个纯抽象的 ExecutableTest

ExecutableTest =
ConcreteSteps = {L, C, P}
Order = [L, C, P] (来自 Edges 的拓扑排序)
Checkers = {checker1, checker2}

这里的关键点:

  • 依然没落到文件、框架,只是在说有一个中间表示(ExecutableTest)

  • ExecutableTesttest_executer 唯一需要理解的东西


4. 抽象层面的 test_executer:解释/执行 ExecutableTest

Section titled “4. 抽象层面的 test_executer:解释/执行 ExecutableTest”

在模型层,test_executer 是一个解释器/运行时:

test_executer : ExecutableTest × SUT -> TestResult

我们给 TestResult 一个很抽象的定义:

TestResult = (Verdict, Observations)
  • Verdict ∈ { PASS, FAIL, INCONCLUSIVE }

  • Observations:执行过程中产生的所有可观测信息(响应、状态快照、时间等)

面对 ExecutableTest 中的 Order + 引用(L.tokenC.order_id),test_executer 的抽象语义可以描述为:

  1. Order 拓扑顺序执行每个 Step

  2. 每执行完一个 Step,就把它的输出放入一个环境 Env

  3. 后续 Step 在执行前,从 Env 里按引用去取值:

Env₀ = ∅
(L_out, Env₁) = eval_step(L, Env₀, SUT)
(C_out, Env₂) = eval_step(C, Env₁, SUT)
(P_out, Env₃) = eval_step(P, Env₂, SUT)

这里的 eval_step 只是一个抽象函数:

eval_step : Step × Env × SUT -> (StepOutput, Env')

它的语义是:

在给定环境下解释这个步骤描述,去驱动 SUT,并把新产生的输出写回环境。

执行完所有 Step 后,test_executer 拿着 Env₃Checkers 去做判断:

verdict = run_all_checkers(Checkers, Env₃)

其中每个 Checker 是一个布尔表达式

Checker : Env -> Bool

所有 Checker 都为真 → PASS 

有 Checker 为假 → FAIL 

其他错误/不确定情况 → INCONCLUSIVE(比如依赖步骤执行失败)

最终得到一个抽象的 TestResult

TestResult =
Verdict = PASS
Observations = {
(L, L_out),
(C, C_out),
(P, P_out),
logs, metrics, ...
}

5. 把这一切再对回最开始提到的的三层角色

Section titled “5. 把这一切再对回最开始提到的的三层角色”
  1. test_data (抽象测试数据)
  • TestSpec 空间里的一个元素:
  • 一个描述场景步骤 + 依赖 + 期望性质的抽象规格
  • 和文件/格式无关,只是一个逻辑对象
  1. data_extractor
  • 是一个映射:
TestSpec -> ExecutableTest
  • 负责:
  • 把业务标签/角色绑定到具体值空间

  • 把依赖图转为具体的执行步骤和参数绑定

  • 把性质表达式转成可执行的 Checker

  1. test_executer
  • 是一个解释器:
ExecutableTest × SUT -> TestResult
  • 负责:
  • 按依赖顺序执行步骤

  • 在环境里传播步骤之间的依赖数据

  • 执行 Checker 得出最终 Verdict


4. 这套框架的通用性边界在哪里?

Section titled “4. 这套框架的通用性边界在哪里?”

从理论上说,这样设计的通用框架是可行的,而且足够抽象。但也有几个边界和取舍要提前想清楚:

4.1 TestSpec 的抽象度:抽太高 vs 抽太低

Section titled “4.1 TestSpec 的抽象度:抽太高 vs 抽太低”
  • 抽太高
  • 所有东西都是抽象操作 + 标签,

  • Binder 会变得非常复杂,几乎相当于重写一套业务编排逻辑。

  • 抽太低
  • TestSpec 变成HTTP 请求描述清单,又容易退化成普通接口测试框架,失去抽象价值。

比较现实的做法

  • 按业务领域定义一套中等粒度的 DomainOperation
  • 比如电商:
  • Login

  • AddToCart

  • CreateOrder

  • PayOrder

  • CancelOrder

  • 而不是细到POST /api/order/create这种协议级别

这样:

  • TestSpec 足够贴近业务(可读、可复用)

  • Binder 负担可控

  • Executor 依然通用

从这种思路做测试框架有人做过吗?有人做过,而且不少。比如基于模型驱动测试(Model‑Based Testing, MBT)的 Spec Explorer、Robot Framework、测分布式系统一致性的Jepsen,mysql 的测试框架 mysql test runner(MTR)

自动化测试框架可以抽象出几种角色,任何测试框架都是

  • test_data

  • data_extractor

  • test_executer

先假设被测系统可以抽象成一个带上下文的函数:

SUT : (Input, Context) -> Output

在这个层面,我们完全不关心 HTTP 还是 gRPC,甚至不关心它是不是代码,只把它当作一个黑盒函数

1)test_data:测试意图的抽象表示

Section titled “1)test_data:测试意图的抽象表示”

在抽象层面,test_data 不是文件,不是表格,而是测试意图的一个元素。在某个前置条件下,对 SUT 做某种操作,应该满足某种性质。也就是说,抽象的 test_data 至少携带三类语义(不是字段,是语义):

  1. 前置世界(上下文)
  • 系统处于怎样的业务状态(用户有多少余额、订单在什么阶段…)
  1. 操作/刺激
  • 我要对 SUT 施加什么操作(调用哪个能力,传入什么意图)
  1. 期望性质(不一定是具体值,可以是性质):
  • 例如:余额减少但不为负数、列表长度增加 1、满足某个不变量

现在的test_data,本质就是对 SUT 行为的一条约束 / 场景描述,而不是任何具体的数据结构。


2)data_extractor:从抽象测试用例到可执行测试的映射

Section titled “2)data_extractor:从抽象测试用例到可执行测试的映射”

在这个高度,data_extractor 可以看成是一个纯函数

data_extractor : TestSpecSpace -> ExecutableTestSpace

也就是说,它负责把人类视角 / 业务视角的测试意图,翻译成执行引擎能直接执行的测试程序。

在抽象层,它做的是语义层的编译,而不是单纯的解析文件。

你可以把它类比成:

  • 编译器:从高级语言代码 → 中间表示(IR)

  • 模型到模型的转换器:从业务场景模型 → 测试执行模型

它需要完成的抽象责任,比如:

  1. 把业务世界的概念映射到调用世界的概念
  • 如:用户=黄金会员 → 某个 userId + 获取 token 的方式

  • 下单成功 → 一系列对 SUT 的调用序列(准备环境 + 下单)

  1. 把性质翻译成可执行断言或检查步骤
  • 如: 

余额减少但不为负数 →

  • Step A: 调前查询余额

  • Step B: 调用支付接口

  • Step C: 再查余额 + 计算差值 + 判断结果

  1. 生成一个结构化的可执行测试脚本(仍可非常抽象,不等于具体语言),例如:
ExecutableTest =
{ pre_steps, invoke_steps, post_checks, expected_relations, … }

此时我们完全不关心这个 ExecutableTest 是 Python 对象、XML 文件还是别的什么,只关心它在语义上是一种对 SUT 的操作与检查序列


3)test_executer:解释 / 运行可执行测试的语义

Section titled “3)test_executer:解释 / 运行可执行测试的语义”

在这个高度上,test_executer 是一个解释器

test_executer : ExecutableTestSpace × SUT -> TestResult

它的职责是:

给我一个可执行测试描述(ExecutableTest)和一套被测系统实现,我根据这个描述去驱动系统,并产生一个带有判定的结果。

这里的关键点:

  • test_executer 只懂 ExecutableTest 这个中间表示的语义
  • 例如:pre_steps 代表前置操作序列

  • invoke_steps 代表主要调用

  • post_checks 代表检查

  • 它不需要也不应该理解业务含义,只负责:
  • 按顺序/规则执行这些步骤

  • 收集观察到的 Output / 中间状态

  • 交给检查逻辑(可以包含在 ExecutableTest 里,也可以进一步抽象成 oracle)来判断

换句话说,这个 test_executer 已经抽象成:

一种对测试中间语言的解释器 / 虚拟机。

这在概念上非常像:

  • Programming Language:
  • Source Code(test_data)

  • Compiler(data_extractor)

  • VM / Runtime(test_executer)


2. 把三层抽象到你完全不关心具体实现的样子

Section titled “2. 把三层抽象到你完全不关心具体实现的样子”

如果把刚才的东西压缩成纯概念图,就是:

+------------------+
| TestSpecSpace | (抽象测试规格 / 测试意图)
+---------+--------+
|
| data_extractor : TestSpec -> ExecutableTest
v
+-----------------------+
| ExecutableTestSpace | (测试中间表示 / 测试脚本)
+-----------+-----------+
|
| test_executer : ExecutableTest × SUT -> TestResult
v
+------------------+
| TestResult | (通过 / 未通过 / 不确定 + 观测信息)
+------------------+

这个图里,你关心的只有三个集合/空间两个映射

  • 集合(空间):
  • TestSpecSpace:抽象 test_data 所在的世界

  • ExecutableTestSpace:data_extractor 输出的可执行测试世界

  • TestResult:执行结果空间

  • 映射:
  • data_extractor:规格 → 可执行测试

  • test_executer:可执行测试 + SUT → 结果


假设被测系统(SUT)里有三类 HTTP 操作(仍然是抽象的):

  • Login: 

输入:(username, password) 

输出:token

  • CreateOrder: 

输入:(token, sku_list) 

输出:order_id

  • PayOrder: 

输入:(token, order_id) 

输出:payment_result

在模型层,你可以把每个接口抽象成一个操作符

Login : Cred -> Token
CreateOrder : Token × SkuList -> OrderId
PayOrder : Token × OrderId -> PayResult

这些只是函数签名式的类型,没有任何协议细节。


我们要做的测试场景是:

一个黄金会员成功登录,创建订单并支付成功。

这个时候,test_data 不是任何文件,而是一个场景规格(ScenarioSpec) 。 

可以把它理解成一个带依赖的步骤图 + 性质约束

2.1 把 test_data 看成一个有向图(抽象)

Section titled “2.1 把 test_data 看成一个有向图(抽象)”

定义一个抽象的 TestSpec 类型:

TestSpec = (Steps, Edges, Properties)
  • Steps:一组抽象步骤节点,每个节点只是说明调用哪个操作符、扮演什么角色,不带具体值

  • Edges:步骤之间的依赖关系(谁的输出喂给谁)

  • Properties:对整个场景的期望性质(不是具体字段值,而是判断规则)

对我们这个 HTTP 场景,一个可能的 TestSpec 是:

Steps =
L: Login( user_role = GOLD_MEMBER )
C: CreateOrder( actor = L.user, items = {skuA, skuB} )
P: PayOrder( actor = L.user, target_order = C.order )
Edges =
L.token -> C.token
L.token -> P.token
C.order_id -> P.order_id
Properties =
P.payment_result.status == SUCCESS
balance_after(L.user) = balance_before(L.user) - order_amount(C.order)

注意这里:

  • user_role = GOLD_MEMBER 这种东西是业务语义,还不是具体 userId

  • skuAskuB 也可以只是抽象商品标签,不是具体 sku 编号

  • L.tokenC.order_id符号变量,只是说P 这个步骤依赖 L、C 的输出

这就是抽象 test_data: 

一个图 + 约束的对象,描述了要怎么走场景、场景里数据是怎么流动的、以及期待满足什么性质。 

完全不涉及实现细节。


3. 抽象层面的 data_extractor:spec → 可执行测试

Section titled “3. 抽象层面的 data_extractor:spec → 可执行测试”

现在看 data_extractor

在模型层,它就是一个函数

data_extractor : TestSpec -> ExecutableTest

我们再给 ExecutableTest 一个抽象定义,比如:

ExecutableTest = (ConcreteSteps, Order, Checkers)
  • ConcreteSteps:带已绑定参数的步骤

  • Order:执行顺序 / 拓扑排序结果

  • Checkers:可执行的检查规则(可能还是抽象的表达式树)

对刚才的那个 TestSpecdata_extractor 在抽象层大致做三件事:

3.1 语义绑定(从角色/标签到具体值空间的投影)

Section titled “3.1 语义绑定(从角色/标签到具体值空间的投影)”

例如:

bind_user(GOLD_MEMBER) → some_user_id ∈ UserIdSpace
bind_sku(skuA) → some_sku_code_A ∈ SkuSpace
bind_sku(skuB) → some_sku_code_B ∈ SkuSpace

这一步的抽象含义是:

从业务标签空间映射到系统可接受的值空间。

我们完全不说这个映射怎么查,是表还是服务,只说它在数学上是一个映射函数。

3.2 把依赖图转成可执行步骤 + 变量绑定

Section titled “3.2 把依赖图转成可执行步骤 + 变量绑定”

得到的 ConcreteSteps 可能长这样(仍然抽象):

ConcreteSteps =
L: Login( username = some_user_id, password = some_pwd )
C: CreateOrder( token = L.token, sku_list = [sku_code_A, sku_code_B] )
P: PayOrder( token = L.token, order_id = C.order_id )

此时你还是只知道:

  • 每个步骤要调用哪个操作符(Login / CreateOrder / PayOrder)

  • 每个实参从哪里来(常量、绑定结果、前序步骤输出)

3.3 把性质表达式翻译成可执行检查器

Section titled “3.3 把性质表达式翻译成可执行检查器”

比如 Properties 中的性质:

P.payment_result.status == SUCCESS
balance_after(L.user) = balance_before(L.user) - order_amount(C.order)

data_extractor 翻译成某种可执行检查表达式树

Checkers =
checker1: (P.payment_result.status) == SUCCESS
checker2: (balance_after) == (balance_before - order_amount)

最终,data_extractor 输出一个纯抽象的 ExecutableTest

ExecutableTest =
ConcreteSteps = {L, C, P}
Order = [L, C, P] (来自 Edges 的拓扑排序)
Checkers = {checker1, checker2}

这里的关键点:

  • 依然没落到文件、框架,只是在说有一个中间表示(ExecutableTest)

  • ExecutableTesttest_executer 唯一需要理解的东西


4. 抽象层面的 test_executer:解释/执行 ExecutableTest

Section titled “4. 抽象层面的 test_executer:解释/执行 ExecutableTest”

在模型层,test_executer 是一个解释器/运行时:

test_executer : ExecutableTest × SUT -> TestResult

我们给 TestResult 一个很抽象的定义:

TestResult = (Verdict, Observations)
  • Verdict ∈ { PASS, FAIL, INCONCLUSIVE }

  • Observations:执行过程中产生的所有可观测信息(响应、状态快照、时间等)

面对 ExecutableTest 中的 Order + 引用(L.tokenC.order_id),test_executer 的抽象语义可以描述为:

  1. Order 拓扑顺序执行每个 Step

  2. 每执行完一个 Step,就把它的输出放入一个环境 Env

  3. 后续 Step 在执行前,从 Env 里按引用去取值:

Env₀ = ∅
(L_out, Env₁) = eval_step(L, Env₀, SUT)
(C_out, Env₂) = eval_step(C, Env₁, SUT)
(P_out, Env₃) = eval_step(P, Env₂, SUT)

这里的 eval_step 只是一个抽象函数:

eval_step : Step × Env × SUT -> (StepOutput, Env')

它的语义是:

在给定环境下解释这个步骤描述,去驱动 SUT,并把新产生的输出写回环境。

执行完所有 Step 后,test_executer 拿着 Env₃Checkers 去做判断:

verdict = run_all_checkers(Checkers, Env₃)

其中每个 Checker 是一个布尔表达式

Checker : Env -> Bool

所有 Checker 都为真 → PASS 

有 Checker 为假 → FAIL 

其他错误/不确定情况 → INCONCLUSIVE(比如依赖步骤执行失败)

最终得到一个抽象的 TestResult

TestResult =
Verdict = PASS
Observations = {
(L, L_out),
(C, C_out),
(P, P_out),
logs, metrics, ...
}

5. 把这一切再对回最开始提到的的三层角色

Section titled “5. 把这一切再对回最开始提到的的三层角色”
  1. test_data (抽象测试数据)
  • TestSpec 空间里的一个元素:
  • 一个描述场景步骤 + 依赖 + 期望性质的抽象规格
  • 和文件/格式无关,只是一个逻辑对象
  1. data_extractor
  • 是一个映射:
TestSpec -> ExecutableTest
  • 负责:
  • 把业务标签/角色绑定到具体值空间

  • 把依赖图转为具体的执行步骤和参数绑定

  • 把性质表达式转成可执行的 Checker

  1. test_executer
  • 是一个解释器:
ExecutableTest × SUT -> TestResult
  • 负责:
  • 按依赖顺序执行步骤

  • 在环境里传播步骤之间的依赖数据

  • 执行 Checker 得出最终 Verdict


4. 这套框架的通用性边界在哪里?

Section titled “4. 这套框架的通用性边界在哪里?”

从理论上说,这样设计的通用框架是可行的,而且足够抽象。但也有几个边界和取舍要提前想清楚:

4.1 TestSpec 的抽象度:抽太高 vs 抽太低

Section titled “4.1 TestSpec 的抽象度:抽太高 vs 抽太低”
  • 抽太高
  • 所有东西都是抽象操作 + 标签,

  • Binder 会变得非常复杂,几乎相当于重写一套业务编排逻辑。

  • 抽太低
  • TestSpec 变成HTTP 请求描述清单,又容易退化成普通接口测试框架,失去抽象价值。

比较现实的做法

  • 按业务领域定义一套中等粒度的 DomainOperation
  • 比如电商:
  • Login

  • AddToCart

  • CreateOrder

  • PayOrder

  • CancelOrder

  • 而不是细到POST /api/order/create这种协议级别

这样:

  • TestSpec 足够贴近业务(可读、可复用)

  • Binder 负担可控

  • Executor 依然通用

从这种思路做测试框架有人做过吗?有人做过,而且不少。比如基于模型驱动测试(Model‑Based Testing, MBT)的 Spec Explorer、Robot Framework、测分布式系统一致性的Jepsen,mysql 的测试框架 mysql test runner(MTR)