2. Stateflow建模基础回顾
要对Stateflow控制逻辑做测试与模型验证,第一步不是写测试用例,而是把Stateflow的语义边界重新对齐。测试用例的覆盖率、断言和故障注入,最终都要落到“状态是否被激活”“转移是否被触发”“动作是否按序执行”这些可观测事实上。如果对语义理解有偏差,就会出现“模型看起来对、测试却判错”的情况。本节把后续验证所依赖的语义要素逐一梳理清楚。
状态:逻辑的驻留点与激活语义
Stateflow中的状态(State)表示系统在某一时刻所处的逻辑驻留位置。状态有激活与非激活两种基本状态,任一时刻一个状态要么被激活,要么不被激活。状态内部可以包含入口动作(entry)、during动作(during)和退出动作(exit),它们分别在进入状态、状态保持激活、离开状态时执行。测试时最需要关注的是:状态是否被正确激活、激活持续时间是否符合预期、entry/exit是否只执行一次。例如一个“加热”状态,entry中置 heaterOn = true,exit中置 heaterOn = false,若测试发现heaterOn在状态切换后仍为true,问题往往出在exit动作未被执行或转移路径绕过了该状态。
转移、条件与动作:状态切换的触发链
转移(Transition)连接源状态与目标状态,是状态切换的通道。一条转移由三部分组成:转移标签中的事件(event)、条件(condition)和转移动作(action)。其执行语义是:当源状态处于激活状态,且事件发生、条件为真时,转移被触发;触发时先执行转移动作,再退出源状态(执行exit),然后进入目标状态(执行entry)。这个顺序是测试断言的关键依据。例如转移标签写作 E_OverTemp [temp > 85] / faultFlag = 1;,表示事件E_OverTemp到来且温度超过85时,先执行 faultFlag = 1,再退出当前状态、进入目标状态。若测试只检查faultFlag而忽略状态是否真正切换,就可能漏掉“动作执行了但转移未完成”的异常。
层次结构与并行状态:父状态与子状态的组合语义
Stateflow支持层次结构:一个父状态内部可以包含多个子状态,子状态之间通过默认转移确定初始激活对象。父状态被激活时,其内部默认转移指向的子状态随之激活;父状态退出时,其内部所有激活的子状态一并退出。这种“父激活带动子激活、父退出带动子退出”的语义,是测试层次化逻辑时必须验证的。并行状态(Parallel State)则用虚线边框表示,其内部各区域同时激活、并行执行。并行区域之间没有默认转移的先后关系,但共享同一父状态的生命周期。测试并行逻辑时,要特别关注各区域是否同时被激活、区域间是否存在意外的相互干扰。
事件驱动机制:广播、限定与执行顺序
Stateflow是事件驱动的。事件(Event)分为输入事件、本地事件和输出事件。输入事件由外部(如Simulink信号)触发,本地事件在Stateflow内部广播,输出事件向外部发送。事件广播后,所有处于激活状态且监听该事件的转移都会参与条件判断;若同一时刻多个转移条件为真,则按状态层次和转移优先级决定执行顺序。这一点在测试中极易被忽略:当两个并行区域同时响应同一事件时,执行顺序可能影响共享变量的最终值。因此,测试用例设计必须明确事件的产生时刻、广播范围以及各转移的优先级,才能对结果做出可复现的判断。
把语义要素映射为测试观测点
理解上述语义后,测试设计就有了明确的落点。状态层面,观测状态激活标志和entry/exit执行次数;转移层面,观测转移触发记录和条件真值序列;动作层面,观测动作副作用对输出变量的影响;层次与并行层面,观测父子状态的激活联动和并行区域的一致性;事件层面,观测事件广播时刻与各转移的响应顺序。这些观测点共同构成后续覆盖率统计和断言设计的基础。
常见语义陷阱与验证注意事项
第一,默认转移只在父状态首次激活时生效,父状态重新进入时仍会走默认转移,但若父状态未退出而子状态间发生切换,默认转移不再参与。第二,并行区域内的本地事件广播可能被多个区域同时接收,若两个区域都修改同一变量,最终值取决于执行顺序,测试必须固定顺序或显式声明优先级。第三,转移动作在exit之前执行,若转移动作修改了源状态exit所依赖的变量,可能导致exit行为异常,这类问题在测试中应通过变量快照对比来发现。第四,条件表达式中使用浮点数比较时,应明确容差,避免因仿真步长导致条件抖动。