Excelマクロ(VBA)の単体テストを書いてみました。

Excelインクリメンタルサーチ を作るにあたって、できるだけ単体テストを書くように心がけています。
ただ、ブラウザの単体テストには Selenium があるのですが、Excelマクロ (VBA, Visual Basic for Applications) には適当なツールがありません。

そこで、我流のコードで VBA 単体テストを書いてみました!

テスト対象の関数

その前に、テスト対象の関数は以下の通りです。
(セルが選択されていればそのままにする、選択されていなければ自動で範囲を選択する)

Private Sub SelectRange()
    'セルが範囲選択されているなら、そのままにする
    '  OK例: $A$5:$A$6
    '  NG例: $A$5
    If (InStr(Selection.Address, ":") <> 0) Then
        Exit Sub
    End If
    
    '使用されている範囲を選択する
    ActiveSheet.UsedRange.Select
End Sub

テストの作成

テストコードは VBScript で記述しました。
サンプルファイル一式は、こちらからダウンロードできます


処理の流れは、一般的な単体テストとあまり変わりありません。

  • Excel を起動して、テスト対象(Excelアドイン)とファイルを開く
  • テストの前提条件を整えて、対象の関数を実行
  • 期待値と実際値が違えば、テスト失敗とする
failures = 0
current = Replace(WScript.ScriptFullName, WScript.ScriptName,"")

'Excel を起動して、テスト対象(Excelアドイン)とテストファイルを開く
Set excel = CreateObject("Excel.Application")
excel.Workbooks.Open(current & "ExcelIncrementalSearch.xla")
excel.Workbooks.Open(current & "search.xlsx")

AutoRangeTest
ManualRangeTest

excel.DisplayAlerts = false
excel.Quit()

WScript.Quit(failures)

'------------------------------------

Sub AssertEquals(message, expected, actual)
    If (expected <> actual) Then
        WScript.Echo(message + " expected:<" + expected + "> but was:<" + actual + ">)")
        failures = failures + 1
    End If
End Sub

'------------------------------------

Sub AutoRangeTest()
    excel.Cells(2, 2).Select
    excel.Run("ExcelIncrementalSearch.xla!SelectRange")
    
    AssertEquals "AutoRangeTest Failed.", "$B$2:$F$10", excel.Selection.Address
End Sub

'------------------------------------

Sub ManualRangeTest()
    excel.Range("C4:E6").Select
    excel.Run("ExcelIncrementalSearch.xla!SelectRange")
    
    AssertEquals "ManualRangeTest Failed.", "$C$4:$E$6", excel.Selection.Address
End Sub


実行してみると*1、テストに失敗した場合にこんな感じでメッセージが表示されます。

よりわかりやすく

ただ、これだとちょっと不便です。

  • 失敗した場合、メッセージで止まってしまう
  • テストファイルを複数に分散できない
  • テストの成功/失敗が Red/Green で示されていない


そこで、これらの問題をバッチファイルで解決してみました!
(「test.bat」のように、拡張子 bat で保存します)

@echo off
set FAILURES=0

for /r %%i in (*.vbs) do (
    echo %%~nxi
    cscript %%i //Nologo
    call set /a FAILURES=FAILURES + %%ERRORLEVEL%%
)

echo.
echo Failures: %FAILURES%
if %FAILURES% == 0 (
    color 27
) else (
    color 47
)

pause
exit /b %FAILURES%


あとは、このバッチファイルを実行するだけです。
すると、こんな感じで上記の問題がすべて一挙に解決できていることが確認できます!

まとめ

テストがないコードはレガシーコードだ! *2

テストがあれば、Excelマクロ(VBA)だってレガシーコードじゃないです。

*1:保存した vbs ファイルをダブルクリックするだけです。

*2:レガシーコード改善ガイド (Object Oriented SELECTION)より