※ 2015年1月に執筆したものを転載
scilabとboost:statechartの連携 後編です。
C言語化ですが、自動化ツールがあるわけでもないので手作業になります。
とはいえ、あまりにも設計と離れすぎると結局実装ミスが入り込みそうなので、
boost::statechartに一番性質が近いと思われる、
イベント拡張型、遷移規定型のハイブリッドタイプで実装します。
これなら、今回のレベルであれば大半はコピペでいけそうです。仕組みの方の準備が面倒ですが。
ちょっと話が戻りますが、
前回、状態機に対してユニットテストを実施しています。
「一発物なので・・・」みたいな書き方をしたかと思いますが、
一部の人はこれに対して、
「一発物でユニットテストフレームワークを使用するのはイマイチなんじゃね?」と思ったかもしれません。
ユニットテストフレームワークを利用する最大の理由はリグレッション(回帰)テストの効率化であり、一発物ではイマイチ効果が薄いです。
なのですが、このユニットテスト、今回も使用します。
boost:statechartだろうが、C言語だろうが、中身が違っても同一の状態機であるためインターフェース及び振る舞いが完全一致です。
というわけでユニットテストをフェーズ横断で使いまわしてしまいます。
こういうのをBack to Backテストと呼びます。
まぁ本当はモデルとコードの一致性確認のことをBack to Backテストというのですが、
今回は上流側でboost:statechartを使用してるので「コード」と「コード」になってます。
boost:statechartは確かに「コード」ですが、
ひっろ~~~い意味(と心)で見ればモデルのようなものなので、Back to Backテストってことにします。
さらに言うと、モデルの段階でテスト網羅率を高めた状態でコードに対して同一のテストすることがBack to Backテストなのですが、今回はテスト網羅率は特に考えてません。
これもちょっと理由があります。
最大の理由は
「めんどくさい」
身も蓋もないこと言うようですが、本当で面倒くさいのです。
Back to Backテストを自動で行ってれるツールってそこそこあるのですが、
今回のケースに於いてはほぼ有効に機能しません。
別にツールの性能、品質が悪いからってわけではありません。
何が悪いかというと
「運が悪い」
この手のツールはC1かC2かは知りませんが、カバレッジ計測を可能な限り100%に近づけるためのテストパターンを導出してくれます。
今回のテスト対象は状態機と制御器です。
この状態機と制御器って、カバレッジ計測との相性が最悪なのです。
状態機に対して実行/分岐網羅率を計測した場合、
すべての遷移パスを通すだけで100%になります。
「遷移パスを通すだけ」では全く不十分で、連続イベント、遅延遷移なども加味する必要があり、通常のカバレッジでは見えない部分は私の感覚では70%くらいあると見ています。
そして大問題の制御器。
これに至っては、入力データが何であれ、一回実行するだけで100%に到達します。
これはもともとが数式であったものをソフトウェアで表現しただけなので、
コード上にも命令展開後にも分岐が全く発生しません。
つまり、カバレッジ計測そのものが無意味な物となります。
状態機の方は、状態機用のカバレッジ計測がぼちぼち確立してきてるっぽいので、かなり近い将来なんとかなりそうな気はしていますが、
制御器の方は・・・ソフトウェア的なアプローチでは限界があるかもしれません。
現状では制御対象の振る舞いを模したプラントモデル対して代表的な指令値パターン(ランプ、ステップ、インパルス等)を入れ込んでみたり、プラントモデル側や制御器側の定数を振らして、可能な限りの状況を作りだすといったことを頑張ってやるのですが、最終的には勘と経験と計算に基づいた割と匠的な世界がかなり残っています。(積分時間をリセットしない限り、少なからず過去の入出力を延々と引きずりますので。)
つまり「どういう状況になっても耐えられる制御式を考える」が主流で具体的なテスト方式は後手に回ってます。
理屈の上では問題ないからテスト不要って考え方も有りなのかもしれません。
一応、要求との一致性という観点でのテストパターン導出方法は当然ありますが、
Back to backの話と混ぜてよいかはわかりません。
話を戻します。
さっとと実装してBack to Backテストもどきも実施して、きっと設計と同一であろうと思われる実装状態にしました。
一応比較。↓
当たり前なのですが、完全に一致です。
ちょっとでも差があったら、それはバグです。
見るポイントは出力している値もそうなのですが、
今回の場合はタイミングですね。
状態機の駆動タイミング、イベントの処理のタイミングが1周期ズレてたり、処理順が入れ替わっていたりすると波形のタイミングで現れます。
ここらへんはユニットテストでも見ていた部分なので、波形確認は「念の為の確認」に近い行為です。
端的に言えば不要なのですが、波形の方が説明する上で説得感あるし、事情を知らない人が見てもいろいろ推測できることも多いわけで、エビデンスとしては優秀です。
ウソ(?)でもいいから出力してみるといろいろ発見があるかもしれません。
ちなみに・・・。
今回の作業↓
・状態機設計
・状態機振る舞いモデル作成
・コード生成
Matlab/Simulinkを使用すると一撃です。
StateFlowブロックというものがあるのですが、それを使用すると状態機設計した段階で振る舞いモデルも完成します。
さらにRTW-ECを使用するとコード生成もできます。
やはり金か、金なのか。
差としてはちょっと状態機の表現がUMLと異なる点があるってところでしょうか。
状態機のダイアグラム的表現方法は主に以下3つ
・ムーア型
・ミーリ型
・ハレル型
ムーアは遷移するときに処理、
ミーリは状態に入った時に処理、
ハレルはムーア、ミーリの両方の特性を持ち、さらに階層を表現できます。
(状態からの退場動作規定できるのもハレルの特徴・・・かな?)
というわけでハレル最強ってことになるのですが、
UML、StateFlow共にハレル型をベースにしています。
ベースにしているってことはハレルそのものでは無いということで、
共に独自の拡張が入っています。
・共通の拡張
UMLのdoとかStateFlowのduring。
その状態に入ったらXXし続けるという処理です。
・UML独自拡張
シグナルイベント、defer、同一状態内での非同期処理
・StateFlow独自拡張
afterイベント、VisualFunction、開始状態を同一階層に複数設置可(であるが故に開始状態から初期状態への遷移イベントまたはガード規定が可能)、複数の状態の非同期処理
というわけで、完全互換ではないです。
UMLで設計して、さぁStateFlowで書こう!っと思った矢先に「いや書けねーよ!」なんてこともあるのでちょっと気を付ける必要はあります。
逆も然り。
あとStateFlowの難点としては・・・
状態機のコードがかなりアレなのです。
フローチャート的な記述をした場合は割と期待通りというかしっかりしたコードをはいてくれるのですが、状態機として記述してコードを出力するとかなり摩訶不思議コードが完成します。
でも、ちゃんと設計通り動くもんだからMathWorks社の意地の根性がうかがえます。
とはいえ、規模が大きくなると人間が追うにはちょっと厳しそうです。
メンテはモデルを起点ってことに徹底するれば問題無いのかもしれませんが、ちょっと怖いのもの事実。
そのうち読みやすくなるかもしれませんが。
ただ、1関数で状態機を実現するコードを出してるので、命令生成効率はコンパイラの最大スペックを狙える可能性はあります。
(もしかしてそれを狙ってるのか??)
かわりにメモリリソース(スタック)が読めなくなるという罠も仕掛けられる感じがちょっとヤダ。
まとめ
・scilab/xcosで状態機を実現するのは難しい
・CBlock側に状態管理を任せる方が楽
・さらにBoost:statechartのようなフレームワークを利用すると設計上の構造を維持できる。
・C言語でも近いフレームワークを用意していると楽ができる。
・モデル→実装の工程でミスが混入しないようにBack to Backテストを意識していると安心
コメント