LoginSignup
216
214

More than 3 years have passed since last update.

VBAにはユニットテストやリファクタリング機能がない・・・そんなふうに考えていた時期が俺にもありました

Last updated at Posted at 2020-07-13

はじめに

ワルいけどVBAは開発環境としてあまりにも不完全すぎる。

VBAにはユニットテストがない
リファクタリング機能がない
静的解析がない
以上の理由でVBAは開発環境として不完全だッ

image.png

※実際のところ、VBAUnitとかコードをエクスポートして静的解析とかできますが、今回はパス。

殺伐とした開発環境に救世主が!!!

image.png

RubberduckはVBA6,VBA7 x86/x64,VB6の開発環境であるVBEを拡張するものです。

オープンソースで開発されており、以下のページからダウンロードが可能です。
https://github.com/rubberduck-vba/Rubberduck

上記からインストールすることで、VBEはRubberduckの機能を含んだものに拡張されます。

image.png

※メニューとツールバーが追加されている

Rubberduckの機能

コードメトリックスを表示する

Rubberduck->Tools->Code Metricesを選択することで行数、ネスト数、循環的複雑度を表示できます。

image.png

もし表示されている情報が古い場合は以下の方法で更新がおこなえます。
・Rubberduck->Refresh を選択する
・ツールバーのimage.pngを押下する。

Code Inspections

Rubberduck->Code Inspectionsでコードの静的解析の結果が表示されます。

image.png

たとえば「Option Explicit」を宣言していない場合や、使っていない変数があるとその旨を表示してくれます。

Fixメニューを使用すると修正または警告を無視できます。
image.png

参照箇所

エディタ上で関数を選択すると、Rubberduckのツールバーにその関数を参照している箇所の数が表示されます。それをクリックすることで参照している場所を確認できます。

vba002.gif

リファクタリング機能

コンテキストメニューまたは、メニューバーのRubberduckメニューからRefactorを実行することでリファクタリングが行えます。
下記の例では関数名を変更しています。

vba003.gif

他にも引数の削除や順番が行えます。

ユニットテスト

テスト用のモジュールとメソッドの追加方法

Rubberduck→Unit Tests→Test Moduleでテストモジュールを追加、Rubberduck→Unit Tests→Test Methodでテストメソッドを追加します。

vba004.gif

テストモジュール中の「'@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からテストを実行します。もし、テストコードが反映されていない場合はimage.pngで更新ができます。

vba005.gif

現時点で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で開発する場合は入れてみると便利ではないでしょうか。

216
214
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
216
214