はじめに
ワルいけどVBAは開発環境としてあまりにも不完全すぎる。
VBAにはユニットテストがない
リファクタリング機能がない
静的解析がない
以上の理由でVBAは開発環境として不完全だッ
※実際のところ、VBAUnitとかコードをエクスポートして静的解析とかできますが、今回はパス。
殺伐とした開発環境に救世主が!!!
RubberduckはVBA6,VBA7 x86/x64,VB6の開発環境であるVBEを拡張するものです。
オープンソースで開発されており、以下のページからダウンロードが可能です。
https://github.com/rubberduck-vba/Rubberduck
上記からインストールすることで、VBEはRubberduckの機能を含んだものに拡張されます。
※メニューとツールバーが追加されている
Rubberduckの機能
コードメトリックスを表示する
Rubberduck->Tools->Code Metricesを選択することで行数、ネスト数、循環的複雑度を表示できます。
もし表示されている情報が古い場合は以下の方法で更新がおこなえます。
・Rubberduck->Refresh を選択する
・ツールバーのを押下する。
Code Inspections
Rubberduck->Code Inspectionsでコードの静的解析の結果が表示されます。
たとえば「Option Explicit」を宣言していない場合や、使っていない変数があるとその旨を表示してくれます。
参照箇所
エディタ上で関数を選択すると、Rubberduckのツールバーにその関数を参照している箇所の数が表示されます。それをクリックすることで参照している場所を確認できます。
リファクタリング機能
コンテキストメニューまたは、メニューバーのRubberduckメニューからRefactorを実行することでリファクタリングが行えます。
下記の例では関数名を変更しています。
他にも引数の削除や順番が行えます。
ユニットテスト
テスト用のモジュールとメソッドの追加方法
Rubberduck→Unit Tests→Test Moduleでテストモジュールを追加、Rubberduck→Unit Tests→Test Methodでテストメソッドを追加します。
テストモジュール中の「'@TestMethod」というコメントが記載された関数がテストメソッドとなります。
テスト実行例
まず以下のようなテストモジュールを用意します。
Option Explicit
Option Private Module
'@TestModule
'@Folder("Tests")
Private Assert As Object
Private Fakes As Object
'@ModuleInitialize
Private Sub ModuleInitialize()
'this method runs once per module.
Set Assert = CreateObject("Rubberduck.AssertClass")
Set Fakes = CreateObject("Rubberduck.FakesProvider")
End Sub
'@ModuleCleanup
Private Sub ModuleCleanup()
'this method runs once per module.
Set Assert = Nothing
Set Fakes = Nothing
End Sub
'@TestInitialize
Private Sub TestInitialize()
'this method runs before every test in the module.
End Sub
'@TestCleanup
Private Sub TestCleanup()
'this method runs after every test in the module.
End Sub
'@TestMethod("足し算の検証")
Private Sub TestMethodOK()
On Error GoTo TestFail
'Arrange:
'Act:
Dim act As Long
act = 5 + 3
'Assert:
Assert.AreEqual 8&, act
TestExit:
Exit Sub
TestFail:
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
End Sub
'@TestMethod("足し算の検証")
Private Sub TestMethodNG()
On Error GoTo TestFail
'Arrange:
'Act:
Dim act As Long
act = 5 + 3
'Assert:
Assert.AreEqual 7&, act
TestExit:
Exit Sub
TestFail:
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
End Sub
なおAssertクラスがサポートしている機能は以下の通りです。
https://github.com/rubberduck-vba/Rubberduck/wiki/Unit-Testing#the-assert-class
テストコードを記載したらTestExplorerからテストを実行します。もし、テストコードが反映されていない場合はで更新ができます。
現時点でUIなしで実行はできないようです。
VB6 should offer a command line option for UI-less operation. #4099
https://github.com/rubberduck-vba/Rubberduck/issues/4099
Fakeの利用
現在時刻などをFakesで偽装してテストに都合のいい値を返すことができます。
たとえば以下のようなテストしずらいコードがあったとします。
Option Explicit
Public Function hoge1() As Long
Dim ret As String
ret = InputBox("Input Number")
hoge1 = Val(ret) * 2
Debug.Print hoge1
End Function
Public Function hoge2() As String
hoge2 = Format(Now(), "yyyy/mm/ddです")
End Function
Fakesを利用することでInputBoxやNowをテスト用に偽装することができます。
Option Explicit
Option Private Module
'@TestModule
'@Folder("Tests")
Private Assert As Object
Private Fakes As Object
'@ModuleInitialize
Private Sub ModuleInitialize()
'this method runs once per module.
Set Assert = CreateObject("Rubberduck.AssertClass")
Set Fakes = CreateObject("Rubberduck.FakesProvider")
End Sub
'@ModuleCleanup
Private Sub ModuleCleanup()
'this method runs once per module.
Set Assert = Nothing
Set Fakes = Nothing
End Sub
'@TestInitialize
Private Sub TestInitialize()
'this method runs before every test in the module.
End Sub
'@TestCleanup
Private Sub TestCleanup()
'this method runs after every test in the module.
End Sub
'@TestMethod
Public Sub InputBoxFakeWorks()
' InputBoxを偽装して常に23という文字列を返す
On Error GoTo TestFail
Dim lRet As Long
Fakes.InputBox.Returns "23"
lRet = Module1.hoge1()
Assert.AreEqual 46&, lRet
Fakes.InputBox.Verify.Once
Fakes.InputBox.Verify.Parameter "Prompt", "Input Number"
TestExit:
Exit Sub
TestFail:
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
End Sub
'@TestMethod
Public Sub NowFakeWorks()
' Now()を偽造して現在時刻を偽物を返却
On Error GoTo TestFail
Dim sRet As String
Fakes.Now.Returns #2/3/1999 5:32:32 AM#
sRet = Module1.hoge2()
Assert.AreEqual "1999/02/03です", sRet
TestExit:
Exit Sub
TestFail:
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
End Sub
ロジックを見る限りFakeできる関数は以下のものだけのようです。
- MsgBox
- InputBox
- Beep
- Environ
- Timer
- DoEvents
- Shell
- SendKeys
- Kill
- MkDir
- RmDir
- ChDir
- ChDrive
- CurDir
- Now
- Time
- Date
こんなかんじでVBAの関数のDLLインジェクションしているっぽい。
モックの利用
以下のプルリクエストでモックフレームワークの導入されるようになるらしい。
https://github.com/rubberduck-vba/Rubberduck/pull/4681
まとめ
このように想像以上に色々できます。
また、ソースコードが公開されているので、自分でなんとかすることもできます。
VBAで開発する場合は入れてみると便利ではないでしょうか。