Skip to content

slow-life-pg/VBAUnit

Repository files navigation

VBAUnit

VBAをTDDしよう。

何がしたいのか

Excel VBAのテスト自動化。

VBAをテストしたければ、VBAでテストを書くしかない。 しかし、それはExcelを開いてマクロを手動やボタンで実行することになる。 そこには以下の問題がある

  • Excelをいちいち開く必要

  • テスト対象とテストの分離が困難(同時に開くことが必須)

  • Excel自体がバージョン管理できない(vbacはあるが)

テスト対象はどうしようもないが、テストの独立性確保とバージョン管理はしておきたい。 それならPythonでテストを書ければいいのではないか。

Excel VBAのユニットテストをPythonで書いて、自動実行する。

構想

Pythonでテストを記述

Pythonなら使えるライブラリが多く、Excelと組み合わせて紹介されることも多い。 Excel自動化をキッカケにPythonを覚えた人も多いはず。

Pythonにはxlwingsという便利な物があるので、COMを直接操作するより遙かに楽で、ツール自体の品質管理もやりやすい。

configでテストを制御

テストスイートの定義と選択をコマンドで行いたい。 予めテストスイートを定義しておいて、その名前を与えることで選択できるようにする。

ツールにはテストケースの定義を指定する必要があり、テストケースの集合はテストシナリオファイルにまとめる。 テストシナリオとテストスイートは密接に関わっているため、テスト設計者は両方の作成を担当しているはず。 それなら同じconfigファイルに両方書いてもらうのが一番間違いない。

Bridgeブック

PythonからExcelを操作できると言っても、VBAを自由に使えるわけではない。 Application.Run()で実行できるとはいえ、以下の問題がある。

  • Pythonで生成できないオブジェクトが引数にある

  • ByRef引数が戻ってこない

  • フォームモジュールに触れない

VBA上でしかできないことなら、VBAでさせればいいというわけで、Bridgeブックを用意した。 テスト対象は動的にBrigeの参照設定に追加して、Bridge経由で全ての公開メンバにアクセス可能にする。 CallByName()を使えばByRefの値も取れるので、任意の型の値をPythonでチェックできる。

Bridgeの機能

VBA内部で使うCOM系のオブジェクトはBridgeで生成する。 xlwingsではCOMオブジェクトでそのまま通信できるので、Bridgeで生成したCollectionをPythonで取り回すこともできる。

VBA関数の呼び出しはCallByName()を使うが、PythonとBridgeの間では関数のシグネチャが分からないため、予め引数の数を決められない。 Bridgeでは常に可変長引数を受け取り、内部で展開してCallByNameする。
ただし、展開時にはシグネチャに合わせて展開する必要があるため、対応できる引数の数は制限される。

個別対応

Bridge経由の汎用的な通信では実現し得ない、フォームオブジェクトの操作やクラスモジュールのインスタンス化などもテストとしては必要になる。 これはCOMでどうこうできないので、VBAのコードを動かすしかない。 テストコードを配布物に含めたくないから、テスト時に動的に追加したい。 ツールとしてはこの操作を実現する。

テスト対象の保存

VBAは外から見えないので、Excelブックのリビジョンが分かったところで簡単には特定できない。 テスト実行時のテスト対象のコードを抜き取って、毎回テキストファイルとして保存する。

実行結果の保存

テストの実行には毎回IDを発行する。テストのリビジョンのようなもの。 実行したテストスイート名、その定義情報、テストケース、テストケースの合否を持っておく。 テストコード自体は保存しない。

構成管理が目的ではなく、前回の失敗だけ実行、のような小回りの利くツールを目指しているため。

テスト対象のコードの保存はこのテストリビジョンで刻む。

シナリオの表現

シナリオの定義書

Excelにまつわるテストなのだから、分かりやすくExcelで作ってもらう。 このテストシナリオファイルをconfigで指定する。

テスト実行時、特に指定しなければこのファイルがある場所をカレントディレクトリにする。 テストコードのパスはカレントディレクトリからの相対パスだから、基本構成はこうなる。

  • root

    • シナリオ.xlsx

    • config.json

    • GroupA

      • a1_test.py

      • a2_test.py

    • GroupB

      • b1_test.py

      • b2_test.py

開発も考えるとこうなるはず。

  • root

    • .git

    • .env

    • .gitignore

    • product-test.code-workspace

    • シナリオ.xlsx

    • その他

テストグループ

beforeEachとafterEachみたいなものを持たせない。 これはテストスイートに対して定義されるはずで、上記のconfigの話になる。 しかし厳密に言えばテストケース毎に定義されるものであり、同じ準備をして同じ始末をするテストケース群が一個のグループなのだと思う。

テストスイート

一度のコマンド実行で流したい一連のテストケースの集合がテストスイートだと思う。 順序性は無いが、各々に対してbeforeとafterが存在しうる。

テストケースとテストスイートの対応について。 1つのテストスイートに1つのグループの全てを含む必要は無い。 1つのテストスイートに複数のグループが混在していてもいい。 グループはテストケースを整理するための分類で、テストスイートはテスト実施の物理的な単位。

普通なら非同期でコアを活用して、という発想もあり得るが、相手はExcelなので一度に一インスタンスしか開けない。 直列に実行するしか無い。

テストレポート

シナリオとの対応

テストスイートレポート

テストグループレポート

テストケースレポート

About

VBAをTDDしよう。

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published