can't go to sleep

組み込み開発の多くをRubyで自動化することに興味があります

unity開始の第一歩(超入門)

思い返せば、unityを初めて1年近く経過しました。
結局こういうツールは使い始めるまでのハードルが最も高いように思います。
ということで、unityの簡単な始め方を、改めて書き出したいと思います。

目標

unityを開始する際には色々と気を付けることがありますが、ここではTDDにこだわらず、無料でC言語単体テストをすることを目標にしたいと思います。そのため、既存の開発プロジェクトのフォルダの近くにunityのプロジェクトを作成し、製品コードには手を加えずに単体テストを実施できる環境を作ることを目標とします。
また、色々なところで触れられていますが、CMockはfff(fake function framework)と比べると使い勝手、動作速度などで劣っているため、今回は導入しません(CMockの使い方は以前1度記事にしましたが、rakefileの設定などが変わるため色々と面倒です)。更に、ceedlingはCIを行う際には強い味方となりますが、いまいち使い勝手が悪いので、そちらも無視です。

事前準備

rubyおよびrakeをPCにインストールします。
rubyは、「ruby installer」で検索すると、インストーラーが見つかるので、適当にインストールしてください。rakeはrubyをインストールすると同時にインストールされると思います。
http://rubyinstaller.org/
次に、unityの本体を入手します。GitHubからcloneしても手に入りますが、作者達のサイト「Throw The Switch」からでもzip形式で入手できます。今回は、現在の作者サイトから手に入る、unity-master.zipを使うことを前提としています。
http://throwtheswitch.org/

unityをとりあえず動かす

まず、サンプルテストを動作させるところまでを行いまs。

フォルダ構成について

ダウンロードしたunity-master.zipを、製品コードの傍に展開します。展開すると、unity-masterフォルダができます。フォルダ構成ですが、今回は、以下のようなフォルダ構成を仮定します(A.c, A.hは製品コードの例)。

-/project
    |-/src
        |-A.c
        |-A.h
-/unity-master

こういったフォルダ構成にすることで、プロジェクトフォルダの外でテストファイルを作成することができますので、製品コードとテストコードを完全に分離することができます。ただし、テストファイルはソースファイルやプロジェクトと一緒にバージョン管理することを強くお勧めします。

デフォルトのテストを動作させる

コマンドプロンプトを立ち上げて、unity-masterフォルダへ移動します(rakefile.rbのある場所)。そこで、「rake」とコマンドを入力して実行してみてください。すると、

rake aborted!
cannot load such file -- C:/utest/Unity-master/auto/unity_test_summary

と表示され、うまくいきません。そこでrakefile.rbをエディタで開き、2行目を

#UNITY_ROOT = File.expand_path(File.dirname(__FILE__)) + '/../..'
UNITY_ROOT = File.expand_path(File.dirname(__FILE__))

と修正します。再びrakeをすると、次は

rake aborted!
No such file or directory - target_gcc_32.yml

と表示されるので、次はtargetフォルダ内にある「gcc_32.yml」を「target_gcc_32.yml」とリネームしたうえで、rakefile.rbと同じフォルダにコピーします(真面目に修正するならば、rakefile_helper.rb内のconfigure_toolchainなどを変更する必要があるのですが、面倒なので・・・)。再びrakeをすると、何やら変なエラーが出ます。これは、testフォルダ内にある「testparameterized.c」というファイルのテストがgccで動作しないためです(環境によっては動くかもしれませんが、私の環境ではエラーとなりました)。ここは素直にあきらめて、「testparameterized.c」を削除してしまいます。改めてrakeをすると、ようやくテストが実行できます。
unityでは、testフォルダ中にある、「test」で始まる.cファイルは全てテストファイルとみなします。そのため今後テストを追加する際には、testフォルダ内に追加していくことになります。

製品コードへのパスを追加する

次に、製品コードのソースへのパスを設定に追加します。target_gcc_32.ymlを開き、compiler:のincludes:のitems:に、相対パスを追加します。
具体的には

  includes:
    prefix: '-I'
    items:
      - 'src/'
      - '../project/src/'
      - *unit_tests_path

という具合に、デフォルトで存在する'../src'を変更すれば良いと思います。

製品コードをテストする

これでようやく製品コードをテストできます。今回は、製品コードの例として

// A.c
int funcA(int a, int b)
{
	return a + b;
}
// A.h
int funcA(int a, int b);

という形でコードが記述されており、上記のproject/src/フォルダに保存されているとします。
例として、testA.cという名前で、testフォルダに以下のようなテストコードを作成します。

// testA.c
#include "unity.h"
#include "A.h"

void setUp(void)
{
}

void tearDown(void)
{
}

void testA(void)
{
	
	TEST_ASSERT_EQUAL(3, funcA(1,2));
}

これでrakeを実施すると、

test/testA.c:12:testA:PASS
------------------------
1 Tests 0 Failures 0 Ignored
OK

と表示されます。テストが正常に実行され、パスしていることがわかります。
ここで、testunity.cは邪魔なので、_testunity.cなどとリネームしてやると、テスト対象から外れます。

終わりに

とりあえずこれでunityが使えるようになります。
ここからでも、色々とつまづくことがあるかと思います。具体的には、

  1. 同名のヘッダファイルがないCファイルのテスト
  2. 依存ファイルが多すぎるファイルのテスト方法
  3. モック関数の作り方
  4. 大量にできたテストファイルをいちいち毎回テストするのが面倒
  5. フォルダ分けしたソースファイルをテストしたい場合、またテストファイルをフォルダ分けしたい場合

これらは以前このブログで書いたように思いますので、ぜひ参考にしてみてください。

Unity全自動化(仕様)

以前、Unityを半自動化するという記事を書きました。
最近それを、更に改善する方法を考えたので、メモとして記載します(まだ作成していないので、あくまでメモです)。
半自動化の問題点は、変更したソースコードをいちいち自分で指定しないといけない点です。
しかし運用をしていると、Unityを実行したいファイルは

  1. ソースファイルを変更した場合
  2. テストケースを変更した場合

に限ることがわかってきました(常にグリーンを保つ開発をしているならば、それ以外のファイルは、テストが通るはずです)。
ということは、最近更新されたソースファイルとテストファイルを抽出し、以前作成したtestList.ymlに登録するスクリプトを作成し、rakeの最初に実施するようにすれば良いのです。
動作手順は以下の通りです。

  1. ファイルリストを作成する。このリストには、ソースファイルとテストファイルの全ての更新日付が記録されている。
  2. rake実施時に、上記ファイルリストと、ソースファイルとテストファイルの更新日付をそれぞれ比較し、最近更新されたファイルを抽出する。
  3. ファイルリストの更新タイミングは、gitなどのバージョン管理ツールでソースコードとテストファイルをコミットする時とする
  4. このようにすることで、gitでコミットするタイミングを元に、それ以降更新されたファイルは全てテストを実行するようになる
  5. gitの操作は、post-commitのフックを使えば良い

時間さえ取れれば、サクッと作成できる気がするので、作成したらサンプルコードを掲載しようと思います。


あと別の話題ですが、cmockの検索でこのブログに辿り着いて下さる方がいるようですが、cmockよりもfake function framework(fff)をおススメします。fffの問題点は、c99でないとコンパイルできないので、マイコンコンパイラがc99に対応していないと、実コンパイラでビルドができない点くらいだと思います。
cmockは、動きが重いし、ソースをフォルダ分けして管理している場合には、cmock自身にいくつか手を加える必要があり、結構面倒です。

HEWでunityを実施する方法

私は仕事でunityを使用しています。
unityではgccでコンパイルを行うため、組み込み機器の実際のコンパイラでコンパイルをした場合と結果が異なる可能性があり、単体テストとしての品質の担保ができません。
そのため極端な話、gccのみのunityテストは、プログラマの安心のためのテストにはなっても、テストのエビデンスとして使用するのは難しいです。
多くの場合、単体テストレベルで考えた場合、gccとマイコン用コンパイラは同様の動きになるはずです。一方で、コンパイラ特性やクセによっては、微妙に動作が異なる可能性があります。
その可能性を排除するために、ターゲットコンパイラにて、単体テストを実施する必要があります。
テスト駆動開発による組み込みプログラミング」に記載されている、組み込みTDDのサイクルにおける、ステージ3です。

私はルネサスのマイコンを用いて開発を行っているため、統合開発環境としてHEWを用いています。HEWは統合開発環境としては非常にプアですが、一方でコンパイラに付属するシミュレータを動かすことができる、というメリットがあります。
ここでは、unityで作成したテストケースをHEWにてシミュレータを通して、テストする方法を説明します。
手順は以下の通りです。

  1. rubyのrakeを用いた、unityでのテストを行う
  2. HEWにて開発プロジェクトを立ち上げ、シミュレータに接続する
  3. unityのrubyファイルにより自動的に生成されたテストランナー及びテストファイルの1つを、HEWプロジェクトに読み込む
  4. プロジェクトにて、電源投入後にmain関数を呼び出すように設定し、開発プロジェクトのmain関数を削除する
  5. HEWにてビルドし、シミュレータを実行する

上記手順で、1つのテストファイルに対して、テストを実行することができます。上記処理をrubyスクリプトやWindowsのwsh、AutoItなどを用いて自動化し、HEWにて連続的にテストを行うことで、unityのように次々と単体テストを実行することができます(上記動作のソースコードはいずれgithubに上げる予定です)。
unityの利点である、テストケースを適当に書くと、テストランナーをrubyにより自動生成してくれる、という機能を最大限に利用し、HEWでのプロジェクトにも使用する方法です。

ただし、ここでいくつかの注意点があります。

  • unityは結果表示にputcharを使用しており、マイコンによってはputcharは対応していない。その場合は、unity_internal.hのUNITY_OUTPUT_CHAR部分を書き換える必要がある。
  • モックオブジェクトをfffで作成している場合、C99対応のコンパイラである必要がある。
  • 実マイコン上ではROM容量が足りない場合がある。その場合は、シミュレータで指定できるもっともROM容量の大きいマイコンを選択することで問題を回避することができる。
  • HEWはキーボードショートカットに対応した動作が少ない。特にビルド対象のファイルの登録はショートカットがあるものの、削除はショートカットがない。これはhwpファイルを直接書き換えることで対応できるが、面倒。
  • HEWはビルド待ちなどの状態をウィンドウで表示しないため、AutoItで状態を取得するのもかなり面倒。

もしかしたら、HEWからCubesuiteに移行することで、上記のHEWの問題は解決できるかもしれません。

具体的な記載が無いですが、このようにして実コンパイラでの単体テストを行うことができます。
組み込み用途の実コンパイラでの単体テストに関してはGAIOのWinAMSが有名ですが、上記方法ではルネサスが公開しているシミュレータを使用するわけなので、GAIOのシミュレータよりも信頼性が高いかと考えています。
WinAMSとunityとでは、実際には目的が違うのですが、そこを混同する人には上記説明が有効だと考えます。
WinAMSにはC0レベルのテストケースを自動生成して実行させる機能があります。その機能を真似してunityにてテストできるツールを作成しようと思ったのですが、unityはあくまでシナリオベースのテストが目的なので意味がないと考えました(おそらく、そういう目的であればWinAMSを使用するのが正解)。

また、今後はThrow The Switchが作成しているcucumber-cを確認し、ATDDに挑戦したいです。
あるいは、AstahやEAにて描いたシーケンス図を元にテストケースを作成し、unityにて実行できるようなツールを作成したいと考えています。

Unityメモ2

  • CeedlingでxUnit形式のxmlを出力できるけれど、デフォルトだとJenkinsで読み込めない。

http://futurismo.biz/archives/1498 参照

  • Ceedlingだと、テストケースを指定したテストフォルダのサブフォルダ以下からも取得する。しかしUnity単品のrakefileではそれができないので、修正する必要がある(Rubyだから簡単だけど)。
  • UnityとCeedlingではファイル構成が異なるので、Unityで適用した修正をCeedlingに実施するのは面倒。特にCMock系。上記Futurismoさんで紹介されていたfffが最強すぎるので、CMockは不要かもしれない・・・。
  • UnityのRuby回りの完成度はやはり低いけれど、実マイコン上でのテストはCppUTestよりも簡単そうなので、回りは自分が作ると割り切って使うと便利な気がする。

Unityのテスト自動化を半手動化する方法

Unityの自動テストの欠点

Unityはrakeを使用した自動テストランナー作成機能が非常に便利です。
例えば、testというフォルダを作成し、そのフォルダをテストフォルダとしてrakefile用のymlに登録しておくと、そのフォルダ内にあるテストファイルはすべて自動でテストできる。
しかし、テストケースがどんどん増えてくると、全てのテストケースを毎回テストしていたら時間がかかってしまいます。
全てのテストはJenkinsに任せたり、1日に1回だけ実行すれば十分でしょう。
そこで、Unityのテストを半手動化する方法を説明します。

半手動化とは

今回紹介する、半手動化は、テストターゲットをymlに登録しておく方法です。

  • まず、rakefileと同一フォルダにtestList.ymlを作成します。yml内のコメントアウトは# で行います。
#testList.yml
- TestFuncA.c
#- TestFuncB.c
- TestFuncC.c
  • 次にrakefile.rbを変更します。
desc "Test unity with its own unit tests"
task :unit => [:prepare_for_tests] do
#  run_tests get_unit_test_files
  run_tests get_unit_test_files_from_testList
end
  • 次に、上記に書いた「get_unit_test_files_from_testList」メソッドを、rakefile_helper.rbの中に追加します。
# ここはデフォルトであるコードです
  def get_unit_test_files
    path = $cfg['compiler']['unit_tests_path'] + 'test*' + C_EXTENSION
    path.gsub!(/\\/, '/')
    FileList.new(path)
  end

# 以下、追加したコードです
  def get_unit_test_files_from_testList
    arry2 = Array.new
    f = open('testList.yml',"r")
    arry1 = YAML.load(f)
    f.close
    
    arry1.each{|a|
      arry2 << 'test/' + a
    }
    return arry2    
  end
  • これでrakeを実行すれば、testList.ymlに登録したテストコードに対してのみ、テストを実施できます。
自動テストと共生する

上記方法だと、rakeコマンド時に半手動テストしかできません。不便です。ということで、「rake all」コマンド時にはtestフォルダ内にあるすべてのテストコードのテストを実施するようにします。

  • まず、rakefile.rbのうち、先ほど修正した箇所を修正しなおします。
# こっちはrake all用
desc "Test unity with its own unit tests"
task :unit => [:prepare_for_tests] do
  run_tests get_unit_test_files
end

# こっちはrake 用
desc "Test unity with its own unit tests"
task :selected_unit => [:prepare_for_tests] do
  run_tests get_unit_test_files_from_testList
end
  • 次に、rakefile.rbの下の方を修正します。
desc "Build and test Unity"
task :all => [:clean, :prepare_for_tests, :scripts, :unit, :summary]
task :selected => [:clean, :prepare_for_tests, :scripts, :selected_unit, :summary]
task :default => [:clobber, :selected]
#task :default => [:clobber, :all]

これで出来上がりです。「rake」とコマンドすれば、testList.ymlに登録してあるテストファイルのみテストし、「rake all」とコマンドすればtestフォルダにあるすべてのテストファイルをテストします。

メモ

Unityはテストランナーを自動で作成してくれるので、testフォルダに決まった名前(例えば、Test_で始まるファイル名にする、など。ルールはrakefile_helperで設定できます)でテストケースを入れておくと、「rake」というコマンドを入力するとそれだけで、全部テストをしてくれます。
非常に便利な機能ですが、テストケースが増えてくると、いちいち全部のテストを実行してしまって時間がかかります。
だからといって、テストケースを別フォルダに入れて、必要な時にフォルダを移動させる、というのも面倒だし・・・。
本来ならば、ある程度unityに慣れてくるとテストランナーを自動生成ではなくて、自分である程度いじるようになるのが、正しい選択のような気がします。
しかし、私はそれが面倒だったので、rake実行時に、テストケース内のテストファイルをリネームしてUnityがテストファイルをテストファイルとして認識しないようにするスクリプトを書きました。
具体的には、rakefileにて、unity実行前にテスト対象のファイル以外のファイル名の先頭に適当な文字(notなど)を入れます。そしてunityを実施後、ファイル名を元に戻します。

他にも色々な方法があると思うので、これが一番良い方法だとは思いませんが、とりあえず、テストもサクサク進み、且つ、必要な時には「rake all」コマンドで全テストを実行できため、なかなか気に入っています。

rakefileの変更方法及び、スクリプトはあとで気が向いたら公開します。

個人的には、rubyで色々気軽に拡張できるのでCppUTestよりもUnityの方が好きなんだけど、なんかCppUTestの方が流行っている印象・・・。
unityと一緒にrubyの使い方も覚えてしまえば、仕事の自動化効率化に相当有利になると思うのだけれど。

Unityメモ

Unityの基本的な考え方として、プロトタイプ宣言をした関数の実体はヘッダファイルと同名のcファイルの中に存在するべき、というものがあります。
つまり、FuncA.hの中でプロトタイプ宣言をした、int FuncAB(void);の実体は、FuncA.cの中に存在する必要があります。
これが守られていない場合(FuncABの実態が、FuncB.cに記述されている場合など)は、testFuncA.cの中に、

#include "unity.h"
#include "FuncA.h"

void setUp( void )
{
}

void tearDown( void )
{
}


void testFunCABMustAlwaysReturn0(void)
{
	TEST_ASSERT_EQUAL(0, FuncAB());
}

としても、テストが実行できません。

当たり前かもしれませんが、要注意です。