この記事ではTcUnitというUnit Test FrameWorkの導入方法だけではなく、Unit TestingやTest Drive Deveopmentのコンセプトも簡単に紹介します。Bechoff TwinCAT3はVisual Studioに統合されており、Communityから様々なライブラリも無償で公開されています。
TcUnitもその1つです。どうぞよろしくお願いします。
Unit testing ?
Unit testing(ユニットテスト)は、ソフトウェア開発におけるテスト手法の一つで、単一のコンポーネント(ユニット)が期待通りに動作するかどうかを検証することを目的としています。ユニットテストでは、関数やメソッドなどの個々の部品を分離してテストし、その部品が正しく機能するかどうかを確認します。ユニットテストを行うことにより、コードの品質を向上させ、バグの発見を早め、修正にかかる時間を短縮することができます。また、テスト駆動開発(TDD)などの手法により、開発者はコードの品質を意識した設計や実装を行うことができます。
Image From:
単体テストの概念アイコンソフトウェア開発段階のアイディア細線イラストアプリケーションのパーフォマンス検証java ソース コードit プロジェクトベクトル分離アウトライン描
Working Flow?
Unit Testの一般的な流れは以下の通りです。
このように、Unit Testは、継続的にテストを行い、品質を確保するために重要な手法の1つとなっています。
Step1 テスト対象のユニットを決定する
ユニットとは、ソフトウェアの中で最小のテスト対象となる機能やメソッドなどの単位です。Beckhoff TwinCAT やCodesysだとFunctionやFunction Block内のMethodに該当します。
Step2 テストケースを作成する
テストケースとは、テスト対象のユニットが期待する動作を確認するための入力データと期待する出力データの組み合わせを定義したものです。
Step3 てテストを実行する
テストケースで定義された入力データを使って、テスト対象のユニットを呼び出し、期待する出力データを検証します。
Step4 テスト結果を評価する
テスト結果を判定し、期待する結果と実際の結果が一致しているかを確認します。
Step5 テストの修正と再実行
テスト結果が期待通りでない場合は、テストケースやテスト対象のユニットの修正を行い、再度テストを実行します。
Step6 テスト報告書の作成
テスト実行の結果、成功したテストケースと失敗したテストケースを報告し、開発者やチームメンバーに共有します。
Test driven development?
Test Driven Development(TDD)は、ソフトウェア開発の手法の一つで、開発者がコードを書く前にテストを書き、そのテストを通過するようにコードを実装することを繰り返し行うプロセスです。TDDは、以下の3つのステップで構成されます。
- テストの作成: 開発者は、まだ実装されていないコードの振る舞いを表すテストを作成します。
- テストの実行: 開発者は、作成したテストを実行し、テストが失敗することを確認します。この段階では、まだコードが存在しないため、テストは必ず失敗します。
- コードの実装: 開発者は、テストが成功するようにコードを実装します。コードを実装する際には、単一責任原則などのソフトウェア開発のベストプラクティスに則って実装します。
このサイクルを繰り返すことにより、開発者は、コードが期待通りに動作し、テストを通過することを保証することができます。TDDは、品質の高いコードを迅速に開発するための有用な手法であり、開発者がコードの品質を意識した設計や実装を行うことを促進することができます。
Working Flow?
Test driven development (TDD) の一般的な流れは以下の通りです。
このように、TDDは、開発者がテストに集中し、コードが期待通りに動作することを確実にするために重要な手法の1つとなっています。
Step1 テストを書く
TDDでは、最初にテストを書くことから始めます。テストは、コードの要件や仕様を定義するものです。コードが正しく動作するかどうかを確認するための入力値や出力値を含むテストケースを作成します。
Step2 テストを実行する
テストを実行し、そのテストが失敗することを確認します。この時点では、テスト対象のコードはまだ存在しません。
Step3 コードを書く
テストが失敗したら、次にそのテストをパスするための最小限のコードを書きます。このコードは、最低限の要件を満たすだけで良いです。
Step4 テストを再実行する
テストを再実行する
コードが書かれたら、再びテストを実行します。テストがパスすることを確認します。
Step5 コードをリファクタリングする
テストがパスするようになったら、コードをリファクタリングし、重複したコードを除去したり、可読性を高めたりします。
テストを再実行する
コードをリファクタリングしたら、再びテストを実行して、依然としてテストがパスすることを確認します。
xUnit?
xUnitは、ソフトウェア開発におけるユニットテストを行うためのフレームワークの一種です。JUnitが最初に開発され、その後、xUnitという名前で多数の言語に移植され、現在では多くのプログラミング言語で利用されています。
xUnitの基本的な概念は、以下の通りです。
- テストケース(Test case): テストするための最小単位となる処理をまとめたものです。
- アサーション(Assertion): テスト結果が期待したものであるかどうかを判定するために使用されます。
- テストスイート(Test suite): 複数のテストケースをまとめたもので、複数のテストケースを一度に実行できます。
xUnitは、テストコードを書く際に構造化されたアプローチを提供し、テストコードの読みやすさや保守性の向上に役立ちます。xUnitフレームワークを使用することにより、テストの自動化が容易になり、開発者はバグの早期発見や品質の向上などのメリットを得ることができます。
TDDは必ず必要なの?
Test Driven Development(TDD)は、ソフトウェア開発の手法の一つであり、品質の高いコードを迅速に開発するための有用な手法です。しかし、すべてのプロジェクトでTDDが必要なわけではありません。
TDDを実施する主なメリットは、品質の高いコードを保証することができることです。テストを先に書くことによって、コードが予期せぬエラーを引き起こす可能性を低減でき、修正にかかる時間を短縮することができます。また、テストを自動化することで、繰り返し行うテストの手作業にかかる時間を削減できます。
TDD向かないとき?
実はプログラムの規模や要件、テスト環境、開発者のスキルなどによっては、Test Driven Developmentを運用することが開発効率を下げる可能性があります。つまり、TDDは有用な手法であるといえますが、必ずしもすべてのプロジェクトでTDDを実施する必要はありません。例えば:
- プログラムの規模が小さい場合
プログラムの規模が小さい場合、テストケースを記述するための時間や労力がプログラムの実装に比べて多くなる可能性があります。このような場合は、Test Driven Developmentを運用することで開発効率が下がってしまうことがあります。 - プログラムの要件が定まっていない場合
プログラムの要件が定まっていない場合、テストケースを記述することが難しくなります。このような場合は、テストケースを記述することができないため、Test Driven Developmentを運用することが難しくなります。 - テスト環境が整っていない場合
Test Driven Developmentを運用するためには、テスト環境が整っていることが必要です。しかし、テスト環境が整っていない場合、テストケースを実行することができず、Test Driven Developmentを運用することが難しくなります。 - 開発者のスキルが不足している場合
Test Driven Developmentを運用するためには、開発者がテストケースを記述し、テストを実行するスキルが必要です。しかし、開発者のスキルが不足している場合、テストケースの記述や実行に時間がかかり、開発効率が下がってしまうことがあります。 - プロジェクトのスケジュールが厳しい場合
TDDは、テストコードを書くことに時間がかかるため、コードを書くだけでスケジュールに追われるような場合は、TDDを行うことができない場合があります。 - 既存のコードに対してTDDを実施する場合
この場合は、テストの作成が容易ではない場合があります。TDDを実施するよりも、手動のテストやコードリファクタリングなどの手法を検討することが必要かもしれません。
じゃ、PLCとTDDは?
PLCプログラムの場合、Test Driven Development(TDD)を使用するかどうかはプロジェクトの性質や要件によって異なります。ただし、TDDを実施することによって、PLCプログラムの品質や保守性を向上させることができます。
PLCプログラムは、産業用の制御システムなどで使用されることが多く、安全性や信頼性が重要視されます。このため、品質の高いコードを作成することが求められます。TDDは、品質の高いコードを作成するための有用な手法であり、PLCプログラムのテストにも適用できます。例えば、プログラムが正しく動作することを確認するためのテストや、プログラムの保守性を高めるためのリファクタリングなどがあります。
また、PLCプログラムの場合は、品質を確保するために手動のテストが必要な場合があります。TDDを実施することによって、手動テストに必要な時間を削減することができます。
ただし、PLCプログラムを開発する場合には、TDDの利用に際していくつかの課題があることもあります。例えば:
- PLCプログラムのテスト環境が実際のシステムと異なる場合、テストの精度が低下することがあります。
- TDDを実施するためには、PLCプログラムの機能や要件について十分に理解する必要があります。
総じて、PLCプログラムの開発においてTDDを使用するかどうかは、プロジェクトの要件や開発者のスキル、開発環境によって異なります。しかし、TDDを実施することによって、品質の高いPLCプログラムを開発することができます。
前提条件は?
PLCプログラムでTest Driven Development(TDD)を使用する場合、以下の前提条件が必要です。
- プログラムの機能や要件が明確であること
- テスト環境が構築されていること
- テストフレームワークが選定されていること
- テストのためのユーティリティやヘルパーが開発されていること
開発言語は?
STだけでなく、ラダー、FBD、CFCなどのプログラム言語でもTDDを運用することは可能です。ただし、それぞれの言語の特性に合わせたテストフレームワークを選定する(もしくは自分で作成する)必要があります。
Example
以下に、PLCプログラムの開発において、Test Driven Developmentを使用する具体的な例を示します。
例えば、ある機械の制御を行うPLCプログラムを開発する場合を考えます。この場合、まずは以下のような要件を定義します。
モーターを制御するための出力信号を生成するために、入力信号を読み取って、プログラムの状態を制御すると、モーターの回転数を調整するためのパラメーターを設定できることです。次に、これらの要件に基づいて、以下のようなテストを記述します。
- モーターの正常出力信号が正常に生成されるかをテストする
- モーター正常信号
- 入力値:TRUE
- 期待される出力値:モーターの出力信号がONになること
- 動作:入力信号を読み取って、プログラムの状態が正常に制御されるかをテストする
- スイッチのON信号
- 入力値:ON
- モーターの出力信号がONになること
- スイッチのOFF信号
- 入力値:ON
- モーターの出力信号がOFFになること
- モーター正常信号
- モーターの回転数を調整するためのパラメーターが正常に設定されるかをテストする
- 入力値:パラメーターの値
- 期待される出力値:モーターの回転数が設定された値に合わせて調整されること
これらのテストを記述した後は、実際のプログラムの実装を行います。実装にあたっては、まずはテスト1に対するコードを記述し、その後テスト2、テスト3に対するコードを順次実装していきます。
実装が完了したら、記述したテストを実行して、全てのテストが正常に動作することを確認します。テストがパスしない場合は、プログラムの実装に問題がある可能性があるため、プログラムの修正を行います。
このように、Test Driven Developmentを使用することで、プログラムの品質を向上させ、バグを事前に発見することができます。また、プログラムの保守性や再利用性も高まるため、開発効率の向上にもつながります。
Unit TestとTDDの違いは?
Unit TestとTest Driven Development (TDD) は、ソフトウェア開発におけるテストの手法ですが、異なるアプローチを取ります。
Unit Testは、既存のソースコードのユニット(最小単位)に対して、テストを実行することで、そのユニットの振る舞いを検証する手法です。開発者が実装したユニットに対して、事前にテストケースを作成し、テストを実行することで、そのユニットが期待通りの振る舞いをするかを確認します。Unit Testは、既に実装が終わったソースコードに対して実行されるため、実装に関係なく実行できます。
一方、TDDは、テストを先に記述してから実装する手法です。開発者は、まずテストケースを作成し、そのテストが失敗することを確認します。そして、そのテストを通過するようにコードを実装し、再度テストを実行します。このサイクルを繰り返すことで、開発者は要件に合致したコードを実装することができます。TDDは、テストを先に作成するため、プログラムの仕様に即したテストが作成されることが多く、コード品質の向上につながります。
したがって、Unit Testは、既存のコードをテストする手法であり、TDDは、先にテストを作成してからコードを実装する手法であるという違いがあります。また、TDDは、Unit Testの一種として考えることもできます。
Tc Unitとは?
今回の記事で紹介するのはTcUnitというOpen SourceのUnit Test Frameworkです。TcUnitはxUnitタイプのFrameworkでBeckhoff TwinCAT3の開発環境のために作られもので、TwinCAT3 UserはTc UnitをプロジェクトのライブラリとしてImportすればそのまま使用いただける、便利なライブラリです。
こちらはTcUnitの更新サイドです。
Unit testing with TcUnit
こちらはTcUnitの構造図です。
PRG_Test
PRG_TestはxUnitのコンセプトの一部ではなく、単にこれから実行するテストケースに必要な実施環境を用意します。PRG_Testは必ず初期化が必要で、なおかつ実際に運用する環境で使用することができない、そしてテスト目的のために存在するだけです。
Test case
テスト ケースはTDDの中で最も基本的な構成要素です。これは、テストの意図と期待される結果を表現できる名前によって定義されます。
Test fixture
Test fixtureはテストを実行するために必要な一連の前提条件または状態です。 TcUnit では、これらは通常、テスト対象の関数ブロックの入力として使用される定数や変数です。 そして、これらを複数のテストで共有するかどうかに応じて、 test suiteまたはtest caseで宣言できます。
Test suite
Test suiteは相互に関連するテストの集まりです。 TcUnit では、同じtest fixtureを共有できますし、Test suite内のテストごとに個別のtest fixtureを使用することもできます。 TcUnit では、 test suiteはFunction Blockとして定義されます。
Assertions
アサーションは、テスト対象のユニットの状態を検証するメソッドです。たとえば、Function BlockのBoolo Outputが特定の状態に従っていることを確認したい場合、出力がTrueであることをアサートできます。TcUnit にはさまざまな assert メソッドがあり、例えばAssertEquals_INT、AssertEquals_STRING、AssertEquals_BOOL などです。
Test result formatter
Test result formatterはテストの最終結果を生成する部分です。
Test runner
TcUnit Test runnerは定義されたすべてのテストを確実に実行し結果を収集します。
特徴?
使用簡単!
TcUnitは使いやすいFrameworkです。ライブラリをダウンロードしてインストールし、プロジェクトで TcUnit ライブラリを参照するだけで、テストコードを開始できます。
ライブラリはたった1つ!
Unit Testに関するすべての機能は、1 つのライブラリによって完結されます。ライブラリをプロジェクトに追加すると、準備完了です。そのライブラリのプリコンパイル済み (すぐにインストールできる) バージョンをダウンロードするか、またはソース コードをダウンロードできます。
MIT-License
ライブラリとすべてのソース コードは MIT ライセンスに従ってライセンスされます。このソフトウェアは完全に無料で、ソフトウェアに MIT ライセンス条項を含める限り、個人利用でも商用利用でも、ソフトウェアを使用できます。
Automated test runs
TcUnitは追加の TcUnit-Runner ソフトウェアを使用すると、すべての TcUnit テストを CI/CD ソフトウェア ツールチェーンに統合できます。 Jenkins や Azure DevOps などの自動化ソフトウェアを使用すると、ソフトウェア バージョン管理 (Git や Subversion など) で何かが変更されるたびに、テストを自動的に実行し、テスト統計を収集できます。
API
FB_TestSuite
このFunction BlockはTest suiteの内部状態を保持します。すべてのTest suiteは 1 つ以上のTestを持つことができ、 1 つ以上のアサートを行うことができます。また、さまざまなデータ型をアサートするためのすべての assert-methodsが提供されます。注意するのはFailedしたアサーションのみが記録されます。
Implementation
Download Library
下記のLinkをアクセスし、TcUnit.LibraryをDownloadします。
https://github.com/tcunit/TcUnit/releases
Install library
まずライブラリをTwinCATプロジェクトに追加します。
References>右クリック>Add Libraryします。
Advanced..をクリックします。
Library Repository..をクリックします。
Installボタンをクリックし、外部ライブラリをインストールします。
先程Downloadしたライブラリを選びます。
Done!TcUnitライブラリが追加されました。
Add Library
次はライブラリを追加します。References>右クリック>Add libraryします。
TcUnitを選び>OKします。
TcUnitが追加されてるときは時計マークに変わります。
Done!LibraryがImportされました。
FB_SumCal
なにごとも第1歩は簡単で、誰でもできることが大事です。今回Unit TestするのはそのFB_SumCal Function Blockです。2つの整数を入力するパラメータで、出力はそのパラメータの和になります。
FUNCTION_BLOCK FB_SumCal VAR_INPUT i1,i2 :UINT; END_VAR VAR_OUTPUT Result :uint; END_VAR VAR END_VAR Result:=i1+i2; |
つまり、いまあなたのプロジェクトはこんな構造です。
FB_SumCal_Test
次はFB_TestSuiteを拡張するFunction Block FB_SumCal_Testを追加します。FB_TestSuiteは先程も説明しましたが、そのFunction BlockはTcUnitのAPIで(Test suiteの部分に該当する)、中に様々なAssertionsが実装されています。
FUNCTION_BLOCK FB_SumCal_Test EXTENDS FB_TestSuite VAR END_VAR ThreePlusThreeEqSix(); |
METHOD ThreePlusThreeEqSix
ThreePlusThreeEqSixは実際Test Case・検証・Test Result Formatterの部分が含まれています。
TEST(TestName:=’ThreePlusThreeEqSix’); FB_SumCal(i1:=3,i2:=3,Result=>Result); AssertEquals( Expected:=ExceptResult ,Actual:=Result ,Message:=’Test Case1:Result is not the same’); FB_SumCal(i1:=5,i2:=3,Result=>Result); AssertEquals( Expected:=ExceptResultCase2 ,Actual:=Result ,Message:=’Test Case2:Result is not the same’); TEST_FINISHED(); |
プログラムの流れは以下になります。
テストする部品名を設定します。今回の記事はThreePlusThreeEqSixです。
TEST(TestName:=’ThreePlusThreeEqSix’); |
テストするコードを実行します。今回の記事ではFB_SumCalで、i1=3,i2=3なので結果は6になりますね。
FB_SumCal(i1:=3,i2:=3,Result=>Result); |
AssertEqualsを使用し結果を検証します。期待する出力と現在の出力結果を検証し、もし一致ではなければTest Case1:Result is not the sameメッセージが出力されます。
AssertEquals( Expected:=ExceptResult ,Actual:=Result ,Message:=’Test Case1:Result is not the same’); |
テストが完了します。
TEST_FINISHED(); |
いまのプロジェクト構造は下図のように変わります。
POU_Test
次はPOUを作成、TcUnitを実行しテスト開始します。
PROGRAM POU_Test VAR FB_SumCal_Test :FB_SumCal_Test; END_VAR TcUnit.RUN(); |
いまのプロジェクト構造は下図のように変わります。
MAIN
最後はMAIN POUで、POU_Test()を呼び出せばOKです。
PROGRAM MAIN VAR END_VAR POU_Test(); |
最後、プロジェクト構造は下図のように変わります。
Result
2つのテストの中の1つがFailの場合、最初に説明した通り、Failしたテストのみ出力されます。
もし2つのテストがFailであれば、Successful Test:0が表示され、各Test Resultのメッセージも出力されます。