
はじめまして。りきおです。
本記事ではPLC(Programmable Logic Controller)に使用されている「ラダー」と呼ばれるプログラム言語の基礎知識と実際の動作例、テストについて記載していきます。
はじめに
シーケンス制御について
はじめに、「シーケンス制御」という言葉をご存じでしょうか。
あまり聞き慣れないかとは思いますが、実は私たちの身の回りのありとあらゆるもので使われているのです。
日本産業規格のJIS Z 8116では、以下のように定義されています。
あらかじめ定められた順序又は手続きに従って制御の各段階を逐次進めていく制御
自動制御用語 ー 一般 4-24 シーケンス制御
日本産業規格 抜粋
つまり、ある操作を行ったとき(A)、事前に定義した命令や処理(B)に応じて、様々な制御が段階的に行われる(C)ということになります。
洗濯機を例に考えてみましょう。
スタートボタンを押下したとき(A)、事前に設定した洗濯コース(B)に従って、様々な洗浄動作が段階的に行われていきます(C)。

この一連の動作は、まさに「シーケンス制御」と言えます。
他にも、エレベーターや遊園地のアトラクションなど、様々な場面で利用されています。
そして、この「シーケンス制御」を記述するプログラミング言語こそが「ラダー」なのです。
ラダーとは|ラダー言語の基礎知識
PLCについて
そんなラダーですが、一体どこでどのように使われているのかと言うと、「PLC(Programmable Logic Controller)」という組み込み機器(CPU)のプログラミング言語として使用されています。
PLCとは、主に製造業で使用されている制御装置です。PCからPLCにラダープログラムを書き込み、機械や設備に動作指示を与えることによって、シーケンス制御を実現させるのです。

ラダーについて
いよいよ本題です。そもそも「ラダー」とは何でしょうか?
「ラダー(Ladder)」とは、「はしご」を意味します。プログラミング図がはしごのように見えることから、この名前がつけられたそうです。C言語やJavaといったテキスト形式のプログラミング言語とは違ってより視覚的に記述することができます。
では、実際にどんな形か見てみましょう。

PLCを発売している各メーカーによって見え方は微妙に異なりますが、基本的には上図のような見た目になります。両端に縦線(母線)、行ごとに横線(罫線)が引かれており、これが「ラダー(はしご)」と呼ばれる由縁です。
原則として左から右、上から下に向かって処理が進み、1周するまでの処理を「スキャン」と言います。1スキャン目が終わってもPLCが運転中であればラダーは停止することなく、2スキャン目、3スキャン目と連続で実行されていきます。
ラダー中に記述されている図形のようなものは「命令語」です。それぞれ独自の機能があるため、制御動作が異なります。
命令語に対して、「X000」や「Y000」と書かれているものは「デバイス」です。メーカーによって異なりますが、一般的には「英語(デバイス種別)+数字(デバイス番号)」で表記され、デバイス毎にデータ型が固定されているため、用途に応じて使い分けます。
「hoge」や「fuga」と書かれたものは「変数」です。変数はデバイスとは異なり、ユーザーが任意に名前やデータ型を定義して使用できます。
これらの「命令語」に「デバイス(変数)」を組み合わせてプログラミングすることによって、様々な処理や制御を行うのです。
命令語について
ラダーにおける命令語は、基本的に左端に書かれた「条件」が満たされたとき、右端に書かれた「命令」が実行されます。メーカーによって呼称は異なるため、本記事ではこの「条件」を「条件命令」、「命令」を「制御命令」と定義します。
実際にラダーを書いて動かすには、各メーカーのPLCと付属のラダーソフトが必要になります。
今回は無料ラダーソフトである「Codesys」を使って、一部の命令語を紹介していきます。
■条件命令
・A接点

設定したビット型やBOOL型のデバイス(変数)が「TRUE」のとき、A接点以降の処理に進みます。
反対にFALSE のときは、A接点以降の処理は実行されません。
・B接点

設定したビット型やBOOL型のデバイス(変数)が「FALSE」のとき、B接点以降の処理に進みます。
反対にTRUE のときは、B接点以降の処理は実行されません。
・接点比較

機能的にはA接点やB接点と同様ですが、ワード型のデバイス(変数)に対して記述されます。
「=」であれば、設定した値または設定デバイス(変数)が同値のとき、接点比較以降の処理に進みます。
「=」以外にも、「<」、「>」、「≤」、「≧」、「<>」などもあります。
■制御命令
・コイル命令

実行時、設定したデバイス(変数) が「TRUE」になります。
類似の命令語で「セット命令(強制TRUE )」や「リセット命令((強制FALSE )」などもあります。
コイル命令は、実行中のみ設定したデバイス(変数)「TRUE」になりますが、セット命令やリセット命令は、一度実行されると実行条件がOFFになっても値は保持されます。
・MOV命令

実行時、設定した値を指定したワード型のデバイス(変数) に転送します。
一度実行されると、実行条件がOFFになっても値は保持されます。
・数学演算子:ADD(加算)命令

実行時、設定した値(A)に設定した値(B)を加算し、結果をデバイス(変数)に格納します。
値(AまたはB)は、デバイス(変数)以外に直値も指定できます。
他にも「SUB(減算)」、「MUL(乗算)」、「DIV(除算)」などがあります。
などなどあります。挙げるとキリがないのでここまでの紹介に止めます。
ラダーを書いて動かしてみる
PLCには動作モードとして「停止モード」と「運転モード」の2種類あり、状況に応じてこれらを使い分けます。基本的に「ラダーを書く」プロセスは「停止モード」で行い、「ラダーを動かす」プロセスは「運転モード」で行います。
上記「命令語について」で紹介した「A接点」に「X000」、「コイル命令」に「Y000」をそれぞれ設定し、実際に動かしてみましょう。
※今回はPLC実機がないため、Codesysのシミュレーションモード(オンライン上の運転モード)で確認します。
まずは、「停止モード」でラダーを書きます。

データを転送して「運転(シミュレーション)モード」にすると、ラダーの状態が変わりました。青い箇所が実行中の処理になってそうです。
開始時点では、「X000」は「FALSE」なのでまだ処理は走ってません。
※上部「Device.Application.Test」と書かれた箇所で、ラダーで使用している全てのデバイス・変数の値がモニタリングできます。

ここから「X000」に「TRUE」を設定して値を書き込むと、、

「Y000」も「TRUE」になりました!
罫線や命令語も青くなっていて、想定通り動いてくれました◎

このように、記述した命令語の動き方やデバイス・変数にどうやって値が格納されるかを視覚的・直感的に確認しながらプログラミングできることは、ラダーの特徴でもあります。
参考:ラダー図とは・見方、書き方
こちらのサイトの「PLC(シーケンサ)講座」にて詳しく解説されています。
ラダーテスト
「境界値分析」で変数をテストする
以上を踏まえ、ラダーをテストするケースを考えてみます。
ラダーでのテストは、新機能が実装されたときに実施するケースが多いため、対象の機能がラダー上で仕様通りに動作するかを確認するためのプログラミングが必要になります。
今回は、「新しく実装された変数の値の範囲が仕様通りか」を確認するケースを想定します。(昔はデバイスが主流でしたが、近年は各メーカーで変数が実装されるようになったそうで、まだまだ新しい機能と言えます。)
データ型の有効範囲を確認するために、テスト技法として「境界値分析※」を用いてテストします。
※1 境界値分析とは?
JSTQB FLのシラバスでは境界値分析は以下のように定義されています。
境界値分析(BVA)は同値分割法の拡張であるが、パーティションが数値または順序付け可能な値で構成される場合だけ使用できる。パーティションの最小値および最大値(または最初の値と最後の値)が、境界値である。
JSTQB FLシラバス
Foundation Level シラバス 日本語版 Version 2018V3.1.J03 (p.57)
※2 境界値分析については、別記事にも掲載しています。
詳しくは「境界値分析」をテスト設計に取り入れるをご参照ください。
テスト要件
★「INT型」の変数の境界値を確認する
※INT型には何種類かありますが、今回はCodesysに準拠して「short int型」を「INT型」と呼びます。
◆テスト要件
操作 :INT型変数に最小値と最大値を書き込む
期待値 :「-32768」と「+32767」が書き込みできること
※境界値の無効値(「-32769」と「+32768」)は確認しません。
データ型に対する無効値の扱われ方は各メーカーによって異なり、ラダーのエラーとして転送不可になる場合や、転送できても別の値に置換される(暗黙の型変換)場合があります。
ちなみにCodesysでは、コンパイルエラーが発生して転送できませんでした(ラダーのエラー)。
手動テスト
まずは、手動テストから始めましょう。
上記「命令語について」で紹介した命令語を使って、作成&実行してみます。
はじめに、テスト対象の変数を定義(設定)します。

→ 「TEST_OBJECT」という名前のINT型変数を定義しました。
「TEST_OBJECT」に対して、最小値と最大値を書き込む命令語を記述します。

(想定動作)
1行目:「X000」が「TRUE」のとき、「TEST_OBJECT」に「-32768」を書き込む
2行目:「X001」が「TRUE」のとき、「TEST_OBJECT」に「32767」を書き込む
データを転送して「運転(シミュレーション)モード」に切り替え、「X000」に「TRUE」を書き込みます。

→ 「TEST_OBJECT」が「-32768」になりました。(最小値書き込み成功)
「X000」に「FALSE」、「X001」に「TRUE」を書き込みます。
※誤爆を防ぐために「X000」は「FALSE」に戻しておきます。

→ 「TEST_OBJECT」が「32767」になりました。(最大値書き込み成功)
以上で手動テストは終わりです。
ラダーの記述方法は色々ありますが、いずれにしても比較的シンプルなプログラミングでテストできます。
自動テストを作成&実行する
続いて、自動テストも作成&実行してみましょう。
自動テストでも、上記「命令語について」で紹介した命令語を使って作成していきます。
「自動」テストなので運転モードに切り替えた時に、「自動で各テストプロセスを制御して結果を判定すること」を考慮して作成しなければなりません。少しだけ難易度は上がりますが、そもそも「シーケンス制御」は自動制御のため、ラダーで自動テストすること自体は本来の使用目的に近く、プログラミングもしやすいのではないかと思います。
手始めに、要件として以下を定義しました。
◆テスト開始時
・運転モードまでは手動で行い、それ以降の処理は自動で行う
・テスト回数をカウントする
・対象の変数を初期化させる
◆テスト実行時
・最小値書き込み → 最大値書き込みの順にテストし、両方クリアすればテストOKとする
・結果判定は、変数に格納された値と事前に設定した期待値を照合することで行う
◆テスト終了時
・規定した回数に連続してテストOKとなったとき、テスト成功としてラダー処理を停止させる
規定回数:100回
・規定回数中、1回でもテストに失敗したとき、テスト失敗としてラダー処理を停止させる
※その際、NG発生時の情報を記録する
今回はこの要件に沿ったフローチャートも作ってみました。

以上を踏まえプログラミングしていきます。
はじめに、必要な変数を定義します。

各変数には、コメントや初期値(運転開始時にあらかじめ格納される値)も設定できるため、上図の通り設定しました。ここで定義した変数を使い、要件やフローチャートを基にプログラミングしていきます。
※「変数」のテストをするとき、テスト対象以外の変数は定義しないのが望ましいですが、Codesysはデバイスがないため変数のみを使用しています。データ型は、テスト対象以外に「INT型」を使わないようにしています。
1行目 状態:1(テスト初期化)

①「接点比較命令」で「1」と「STATE」を比較する
→ 「STATE」の初期値に「1」を設定しているため、運転開始直後に自動的に処理に進む
②「ADD命令」で「COUNT+1=COUNT」となるように設定する
→ 状態:1になるたび、テスト回数がインクリメントでカウントされる
③「MOV命令」で「TEST_OBJECT」に「0」を書き込む
→ テスト実行毎にテスト対象をキレイな状態にしておきたいため、ここで初期化しておく
④「MOV命令」で「STATE」に「2」を書き込む
→ 状態:2に変化するため次の処理に進む
※本来は、PLCの運転直後はラダー処理が不安定になる場合があるため、意図的にテスト開始タイミングを遅らせたりしますが、今回は実機もなくシミュレーションモードのためプロセスを省略しています。
2行目 状態:2(テスト実行:最小値書込)

①「接点比較命令」で「2」と「STATE」を比較する
→ 状態:2であれば以降の処理に進む
②「MOV命令」で「TEST_OBJECT」に「-32768」を書き込む
→ テスト実行(手動テスト相当)
③条件分岐を記述して、テスト結果を判定する
→ 「接点比較命令」で「TEST_OBJECT」と「期待値:-32768」を照合する
期待値照合に成功する場合
④「MOV命令」で「STATE」に「3」を書き込む
→ 状態:3に変化するため次の処理に進む
期待値照合に失敗する場合
④「MOV命令」で「NG_TYPE」に「1(最小値書き込み失敗)」を書き込む
→ NG種別の情報を残しておく
⑤「MOV命令」で「STATE」に「200」を書き込む
→ 状態:200に変化するためテスト失敗処理に進む
3行目 状態:3(テスト実行:最大値書込)

①「接点比較命令」で「3」と「STATE」を比較する
→ 状態:3であれば以降の処理に進む
②「MOV命令」で「TEST_OBJECT」に「32767」を書き込む
→ テスト実行(手動テスト相当)
③条件分岐を記述して、テスト結果を判定する
→ 「接点比較命令」で「TEST_OBJECT」と「期待値:32767」を照合する
期待値照合に成功する場合
④「MOV命令」で「STATE」に「100」を書き込む
→ 状態:100に変化するためテスト成功処理に進む
期待値照合に失敗する場合
④「MOV命令」で「NG_TYPE」に「2(最大値書き込み失敗)」を書き込む
→ NG種別の情報を残しておく
⑤「MOV命令」で「NG_TYPE」に「1」を書き込む
→ 状態:200に変化するためテスト失敗処理に進む
4行目 状態:100(テスト成功)

①「接点比較命令」で「100」と「STATE」を比較する
→ 状態:100であれば以降の処理に進む
②条件分岐を記述して、テスト結果を判定する
→ 「接点比較命令」で「COUNT」とテスト回数を比較する
テスト回数が100回未満の場合
③「MOV命令」で「STATE」に「2」を書き込む
→ 状態:1に戻し、テストを繰り返し実行する
テスト回数が100回以上の場合
④「コイル命令」に「OK_FLAG」を設定する
→ テスト成功フラグを立てる
※状態:100のままなので、ラダーはここで停止する
5行目 状態:200(テスト失敗)

①「接点比較命令」で「200」と「STATE」を比較する
→ 状態:200であれば以降の処理に進む
②「コイル命令」に「NG_FLAG」を設定する
→ テスト失敗フラグを立てる
※状態:200のままなので、ラダーはここで停止する
以上で作成は完了です。全体で見るとこんな感じになりました。

手動テスト同様、ラダーの記述方法は色々ありますが、なるべくシンプルかつスマートに記述することを心掛けましょう。第三者への可読性の高さを意識したり、仕様変更が入る事態に備えてメンテナンスしやすいような書き方にしたりすることも大切です。
また、類似機能を自動化するようなケースでは、流用しやすいようなラダー構築も重要です。例えば、今回のラダーを流用してINT型以外の変数を自動化する場合、「テスト対象のデータ型」、「書き込み値」、「期待値」を変えるだけで流用できるため、工数削減にも繋がります。
テスト結果
自動テストを実行して結果を確認してみましょう。
テスト結果は、OK / NGに関わらず各変数に格納された値で確認することができます。
■テストOKパターン
各変数に格納された値は以下の通りです。

COUNT = 100 、OK_FLAG = TRUE なので、
テスト回数が規定通りかつ最後まで処理が走っていることから、問題なくテストが終了したことが分かります。
■テストNGパターン
通常運転ではテストNGにならないので、「特定のテスト回数のときだけテスト対象にNG値を書き込む処理」を作為的に記述してラダーを動かしてみると、以下のような結果となりました。

COUNT = 50、NG_TYPE = 2、TEST_OBJECT = 32766、NG_FLAG = TRUE なので、
テスト回数が「50」回目のとき、「最大値書き込み」のテストにて、テスト対象に「32766」が書き込まれて「テスト失敗」している ⇒ 制御ステート「3」で何か問題がありそう?
というところまで推測できます。
実際に、制御ステート「3」でテスト回数が「50」回目のときだけテスト対象に「32766」を書き込む処理を追記していました。情報の確度も高そうです。

上記のように、テストに成功したときだけではなく、テストに失敗したときに「必要な情報を退避させて原因が追えるか」という点は、非常に重要なポイントになります。なぜなら、テスト実行時は人の目を介さず「自動」でテストされるため、発生した問題に際して何が起こったのかを識別する手段が必要となるからです。したがって、自動テストを作成する際は、意図的にNGを発生させてそのときの挙動がどうなるかを確認する作業も忘れてはいけません。
メリット・デメリット
最後に、手動 / 自動テストにおけるメリット・デメリットをそれぞれ下記にまとめました。

- 標準化(テストが属人的にならないか)
- 手動テスト:簡単なデータでテストできるためテスターに依存しない
- 自動テスト:複雑なプログラミングスキルが求められるのでテスター(作成者)に依存する
- 費用(テスト時にコストがかかるか)
- 手動テスト:簡単なデータでテストできるためコストはかからない
- 自動テスト:データの作成及び運用にコストがかかる
※他テストに流用する場合や長期間テストする場合であれば、費用対効果は高められる
- 汎用性(別機能に流用して効率化できるか)
- 手動テスト:機能毎にプログラミングしなければならない
※同一プログラムに対して各テストのラダーを積み重ねて記述していくことで汎用的なデータ資産となり、コーナーケースの不具合が発見できる場合がある - 自動テスト:ラダー中の設定を変えるだけで異なるテストに流用できる場合が多い
- 手動テスト:機能毎にプログラミングしなければならない
- 反復性(繰り返しテストすることで品質が高められるか)
- 手動テスト:テスト期間で少なくて1回、多くても数回程度しかテストできない
- 自動テスト:テスト期間・テスト回数に制限なくテストできる
- 回帰性(システムの修正や変更に強いか)
- 手動テスト:変更毎に確認しなければならない
- 自動テスト:プログラムを流用することで、リグレッションテスト等が容易にできる
※機能そのものに仕様変更が入った場合はその都度メンテンナンスしなければならない
まとめ
今回は、特殊なプログラミング言語である「ラダー」とそのテストについて記載させていただきました。
「シーケンス制御」や「ラダープログラミング」について考えることは、テスト自動化をはじめ様々な方面で活用できるような思考法なのではないのかと思っています。
また、手動 / 自動テストにおけるメリット・デメリットを上記に掲載しましたが、これはラダーテストに限ったことではありません。どのようなテストであっても、対象の機能や実施期間、優先度などをしっかりと考慮したうえで、適切に運用していきましょう。