Contents
VBAのコード、「動いてるから大丈夫」で済ませていませんか?
「先月まで正常に動いていたマクロが、今月になって突然おかしくなった」
「一箇所修正したら、別の場所が壊れていた」
「修正するたびに手動で全パターン確認するのが本当に面倒…」
VBAで業務マクロを作っていると、こうした問題に必ずぶつかります。特にコードが大きくなってくると、ちょっとした修正が思わぬバグを生むことがあります。いわゆる「デグレード(回帰バグ)」です。
プロの開発現場では、こうした問題を防ぐために自動テストを導入しています。コードを修正するたびに、ボタンひとつで全機能の動作チェックを実行する仕組みです。
「でも、自動テストってVBAでもできるの?」
はい、できます。しかも、意外とシンプルな仕組みで実現できます。
この記事では、VBAで使えるAssert関数による単体テストの基本から、複数のテストを一括実行するテストスイート、結果をシートにレポート出力する実務版まで、すべてコピペで使えるコード付きで解説します。
—
どんな場面で使う?
- マクロを修正するたびに手動で全パターン確認する手間をなくしたいとき
- コードの一部を修正したら別の場所が壊れる「回帰バグ」を自動で検出したいとき
- 関数単位のテストを書いて、ロジックの正しさを保証しながら開発したいとき
- テスト結果をシートに一覧出力して、PASS/FAILを可視化したいとき
Before / After:テスト自動化で何が変わるか
Before(手動テスト)
| 項目 | 状態 |
|---|---|
| テスト方法 | 手動でExcelを操作して目視確認 |
| 所要時間 | 修正のたびに15〜30分 |
| テスト漏れ | 確認し忘れるパターンが必ず出る |
| 回帰テスト | 面倒なのでやらない(やれない) |
| 品質の信頼度 | 「たぶん大丈夫」レベル |
After(自動テスト導入後)
| 項目 | 状態 |
|---|---|
| テスト方法 | マクロ実行で全パターン自動チェック |
| 所要時間 | ボタンひとつで数秒〜数十秒 |
| テスト漏れ | テストケースとして記録されるので漏れない |
| 回帰テスト | 毎回全テスト実行するので安心 |
| 品質の信頼度 | 「テスト全件パスしている」と言い切れる |
—
ストーリー:テスト自動化が救った現場
経理部門の田中さんは、毎月の決算処理を行うVBAマクロを管理していました。50以上のプロシージャから成る大規模なマクロです。
ある月、消費税の計算ロジックにバグが見つかり、修正を行いました。修正自体は1行の変更でしたが、翌月の決算で別の帳票の金額がずれていることが発覚。原因を調べると、修正した関数を別の帳票でも使っていたのです。
「修正するたびに全帳票を手動でチェックするのは無理だ…」
そこで田中さんは、主要な計算関数に対して自動テストを導入しました。各関数の入力と期待結果をテストコードとして記録し、修正のたびにワンクリックで全テストを実行する仕組みです。
導入後、別の修正で回帰バグが発生しかけましたが、テストが即座に検出。手動確認では見逃していたであろうバグを未然に防ぐことができました。
田中さんは言います。「テストコードを書く時間は、バグ対応に費やす時間に比べたら微々たるもの。もっと早く導入すればよかった」
—
事前準備
必要な環境
- Excel 2010以降(Windows版)
- VBAエディタ(Alt + F11で起動)
モジュール構成
この記事では、以下の3つの標準モジュールを作成します。
| モジュール名 | 役割 |
|---|---|
MAssert |
Assert関数群(テストの判定ロジック) |
MTestRunner |
テスト実行エンジン(テストの発見・実行・レポート) |
MTests |
テストケース(実際のテストコード) |
モジュールの追加手順
- Alt + F11でVBAエディタを開く
- 左側のプロジェクトエクスプローラーで対象ブックを右クリック
- 「挿入」→「標準モジュール」を選択
- プロパティウィンドウで
(オブジェクト名)を上記の名前に変更 - 3つのモジュール分、この手順を繰り返す
VBAエディタの基本操作やデバッグの方法については、VBAのデバッグ技術を使いこなす方法で詳しく解説しています。
—
手順の全体像
自動テストの導入は、以下の5ステップで進めます。
| ステップ | 内容 |
|---|---|
| Step 1 | テスト対象の関数を用意する |
| Step 2 | Assert関数モジュールを作成する |
| Step 3 | テストケースを書く |
| Step 4 | テストランナーで実行する |
| Step 5 | 結果を確認する |
—
最小限コード:シンプルなテストランナー
まずは最小限のコードで、テスト自動化の全体像を掴みましょう。
Step 1:テスト対象の関数
税込価格を計算する簡単な関数をテスト対象とします。
'================================================
' テスト対象の関数(本番コードに相当)
'================================================
Public Function CalcTax(ByVal price As Long, ByVal taxRate As Double) As Long
'----------------------------------------------
' 機能 : 税込価格を計算する
' 引数 : price - 税抜価格
' taxRate - 税率(例:0.1 = 10%)
' 戻り値 : 税込価格(小数点以下切り捨て)
'----------------------------------------------
CalcTax = Int(price * (1 + taxRate))
End Function
Public Function CalcDiscount(ByVal price As Long, ByVal discountPercent As Long) As Long
'----------------------------------------------
' 機能 : 割引後の価格を計算する
' 引数 : price - 元の価格
' discountPercent - 割引率(%)
' 戻り値 : 割引後の価格(小数点以下切り捨て)
'----------------------------------------------
If discountPercent < 0 Or discountPercent > 100 Then
CalcDiscount = -1 ' エラーを示す
Exit Function
End If
CalcDiscount = Int(price * (100 - discountPercent) / 100)
End Function
Step 2:Assert関数(最小版)
テストの合否を判定するAssert関数を作ります。
'================================================
' MAssert モジュール(最小版)
' テストの合否判定を行うAssert関数群
'================================================
Option Explicit
' テスト結果を格納する変数
Private mPassCount As Long
Private mFailCount As Long
Private mResults As String
Public Sub InitTest()
'----------------------------------------------
' テスト結果を初期化する
'----------------------------------------------
mPassCount = 0
mFailCount = 0
mResults = ""
End Sub
Public Sub AssertEqual(ByVal testName As String, _
ByVal expected As Variant, _
ByVal actual As Variant)
'----------------------------------------------
' 期待値と実際の値が一致するか判定する
' 引数 : testName - テスト名(結果表示用)
' expected - 期待値
' actual - 実際の値
'----------------------------------------------
If expected = actual Then
mPassCount = mPassCount + 1
mResults = mResults & "[PASS] " & testName & vbCrLf
Else
mFailCount = mFailCount + 1
mResults = mResults & "[FAIL] " & testName & _
" (期待値: " & CStr(expected) & _
" 実際: " & CStr(actual) & ")" & vbCrLf
End If
End Sub
Public Sub AssertTrue(ByVal testName As String, _
ByVal condition As Boolean)
'----------------------------------------------
' 条件がTrueであるか判定する
' 引数 : testName - テスト名
' condition - 判定する条件式
'----------------------------------------------
If condition Then
mPassCount = mPassCount + 1
mResults = mResults & "[PASS] " & testName & vbCrLf
Else
mFailCount = mFailCount + 1
mResults = mResults & "[FAIL] " & testName & _
" (Trueを期待したがFalseだった)" & vbCrLf
End If
End Sub
Public Sub ShowResults()
'----------------------------------------------
' テスト結果をメッセージボックスに表示する
'----------------------------------------------
Dim summary As String
summary = "=== テスト結果 ===" & vbCrLf
summary = summary & "合格: " & mPassCount & " 件" & vbCrLf
summary = summary & "失敗: " & mFailCount & " 件" & vbCrLf
summary = summary & "合計: " & (mPassCount + mFailCount) & " 件" & vbCrLf
summary = summary & vbCrLf & mResults
MsgBox summary, IIf(mFailCount = 0, vbInformation, vbExclamation), _
"テスト結果"
End Sub
Public Property Get PassCount() As Long
PassCount = mPassCount
End Property
Public Property Get FailCount() As Long
FailCount = mFailCount
End Property
Public Property Get ResultText() As String
ResultText = mResults
End Property
Step 3:テストケースを書く
'================================================
' MTests モジュール(最小版)
' テスト対象の関数に対するテストケース
'================================================
Option Explicit
Public Sub Test_CalcTax_通常の税率10パーセント()
'----------------------------------------------
' CalcTax関数のテスト:標準税率(10%)
'----------------------------------------------
AssertEqual "税込計算_1000円_10%", 1100, CalcTax(1000, 0.1)
AssertEqual "税込計算_500円_10%", 550, CalcTax(500, 0.1)
AssertEqual "税込計算_0円_10%", 0, CalcTax(0, 0.1)
End Sub
Public Sub Test_CalcTax_軽減税率8パーセント()
'----------------------------------------------
' CalcTax関数のテスト:軽減税率(8%)
'----------------------------------------------
AssertEqual "税込計算_1000円_8%", 1080, CalcTax(1000, 0.08)
AssertEqual "税込計算_999円_8%", 1078, CalcTax(999, 0.08)
End Sub
Public Sub Test_CalcDiscount_正常系()
'----------------------------------------------
' CalcDiscount関数のテスト:正常な割引率
'----------------------------------------------
AssertEqual "割引_1000円_10%OFF", 900, CalcDiscount(1000, 10)
AssertEqual "割引_1000円_50%OFF", 500, CalcDiscount(1000, 50)
AssertEqual "割引_1000円_0%OFF", 1000, CalcDiscount(1000, 0)
AssertEqual "割引_1000円_100%OFF", 0, CalcDiscount(1000, 100)
End Sub
Public Sub Test_CalcDiscount_異常系()
'----------------------------------------------
' CalcDiscount関数のテスト:不正な割引率
'----------------------------------------------
AssertEqual "割引_マイナス割引率", -1, CalcDiscount(1000, -10)
AssertEqual "割引_100超の割引率", -1, CalcDiscount(1000, 150)
End Sub
Step 4:テストランナー(最小版)
'================================================
' テスト実行プロシージャ(最小版)
' すべてのテストを順番に実行する
'================================================
Public Sub RunAllTests()
'----------------------------------------------
' すべてのテストケースを実行して結果を表示する
'----------------------------------------------
' テスト結果を初期化
InitTest
' 各テストを実行
Test_CalcTax_通常の税率10パーセント
Test_CalcTax_軽減税率8パーセント
Test_CalcDiscount_正常系
Test_CalcDiscount_異常系
' 結果を表示
ShowResults
End Sub
実行すると、メッセージボックスに以下のような結果が表示されます。
=== テスト結果 ===
合格: 9 件
失敗: 0 件
合計: 9 件
[PASS] 税込計算_1000円_10%
[PASS] 税込計算_500円_10%
[PASS] 税込計算_0円_10%
[PASS] 税込計算_1000円_8%
[PASS] 税込計算_999円_8%
[PASS] 割引_1000円_10%OFF
[PASS] 割引_1000円_50%OFF
[PASS] 割引_1000円_0%OFF
[PASS] 割引_1000円_100%OFF
—
最小限コードの解説
Assert関数の仕組み
Assert関数は「期待する結果」と「実際の結果」を比較して、一致すればPASS、不一致ならFAILと記録するシンプルな仕組みです。
AssertEqual "テスト名", 期待値, 実際の値
この1行で、以下の処理が行われます。
期待値と実際の値を比較- 一致すれば
mPassCountを加算し、結果に[PASS]を記録 - 不一致なら
mFailCountを加算し、結果に[FAIL]と差分を記録
テストケースの命名規則
テストプロシージャの名前はTest_で始める規約にしています。これは後の実務版で、テストの自動発見機能に活用します。
Public Sub Test_CalcTax_通常の税率10パーセント()
' ^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
' 対象関数 テスト内容の説明
Test_ + 対象の関数名 + _ + テストの内容 という命名にすると、テスト結果を見たときに何をテストしているかが一目でわかります。
テストランナーの役割
テストランナーは、すべてのテストを順番に呼び出す「指揮者」のような存在です。最小版では単純に各テストプロシージャを手動で列挙していますが、実務版ではこれを自動化します。
変数の中身を確認するデバッグ手法については、Debug.Printとイミディエイトウィンドウで変数の中身を確認する方法も参考にしてください。
—
実務版コード:本格テストスイートフレームワーク
最小版では手動でテスト関数を呼び出していましたが、実務版では以下の機能を追加します。
- テストプロシージャの自動発見(
Test_で始まるプロシージャを自動検出) - テスト結果のシート出力(専用シートに一覧表示)
- 処理時間の計測(各テスト・全体の実行時間を記録)
- AssertNotEqual / AssertFalse / AssertErrorの追加
- テストのスキップ機能
- カラー表示(PASS=緑、FAIL=赤、SKIP=黄色)
MAssert モジュール(実務版)
'================================================
' MAssert モジュール(実務版)
' テストの合否判定を行うAssert関数群
'
' 機能:
' - AssertEqual : 値の一致を検証
' - AssertNotEqual : 値の不一致を検証
' - AssertTrue : 条件がTrueであることを検証
' - AssertFalse : 条件がFalseであることを検証
' - AssertError : エラーが発生することを検証
' - SkipTest : テストをスキップ扱いにする
'
' 使い方:
' 1. InitTest でテスト結果を初期化
' 2. 各Assert関数でテストを実行
' 3. GetResults で結果配列を取得
'================================================
Option Explicit
' --- テスト結果の定数 ---
Public Const TEST_PASS As String = "PASS"
Public Const TEST_FAIL As String = "FAIL"
Public Const TEST_SKIP As String = "SKIP"
Public Const TEST_ERROR As String = "ERROR"
' --- テスト結果の格納用 ---
Private Type TestResult
TestName As String ' テスト名
Status As String ' PASS / FAIL / SKIP / ERROR
Expected As String ' 期待値(文字列表現)
Actual As String ' 実際の値(文字列表現)
Message As String ' 補足メッセージ
ModuleName As String ' テストが属するモジュール名
ElapsedMs As Double ' 個別テストの実行時間(ms)
End Type
Private mResults() As TestResult
Private mResultCount As Long
Private mPassCount As Long
Private mFailCount As Long
Private mSkipCount As Long
Private mErrorCount As Long
Private mCurrentModule As String
Private mTestStartTime As Double
' --- Win32 API:高精度タイマー ---
#If VBA7 Then
Private Declare PtrSafe Function QueryPerformanceCounter _
Lib "kernel32" (lpPerformanceCount As Currency) As Long
Private Declare PtrSafe Function QueryPerformanceFrequency _
Lib "kernel32" (lpFrequency As Currency) As Long
#Else
Private Declare Function QueryPerformanceCounter _
Lib "kernel32" (lpPerformanceCount As Currency) As Long
Private Declare Function QueryPerformanceFrequency _
Lib "kernel32" (lpFrequency As Currency) As Long
#End If
Private mFrequency As Currency
Public Sub InitTest()
'----------------------------------------------
' テスト結果を初期化する
' すべてのテスト実行前に必ず呼ぶこと
'----------------------------------------------
mResultCount = 0
mPassCount = 0
mFailCount = 0
mSkipCount = 0
mErrorCount = 0
mCurrentModule = ""
Erase mResults
QueryPerformanceFrequency mFrequency
End Sub
Public Sub SetCurrentModule(ByVal moduleName As String)
'----------------------------------------------
' 現在のテストモジュール名を設定する
' 引数: moduleName - モジュール名
'----------------------------------------------
mCurrentModule = moduleName
End Sub
Public Sub StartTimer()
'----------------------------------------------
' 個別テストのタイマーを開始する
'----------------------------------------------
QueryPerformanceCounter mTestStartTime
End Sub
Private Function GetElapsedMs() As Double
'----------------------------------------------
' タイマー開始からの経過時間をミリ秒で返す
'----------------------------------------------
Dim endTime As Currency
QueryPerformanceCounter endTime
If mFrequency > 0 Then
GetElapsedMs = (endTime - mTestStartTime) / mFrequency * 1000
Else
GetElapsedMs = 0
End If
End Function
Private Sub AddResult(ByVal testName As String, _
ByVal status As String, _
ByVal expected As String, _
ByVal actual As String, _
Optional ByVal message As String = "")
'----------------------------------------------
' テスト結果を内部配列に追加する
'----------------------------------------------
mResultCount = mResultCount + 1
ReDim Preserve mResults(1 To mResultCount)
With mResults(mResultCount)
.TestName = testName
.Status = status
.Expected = expected
.Actual = actual
.Message = message
.ModuleName = mCurrentModule
.ElapsedMs = GetElapsedMs()
End With
End Sub
Public Sub AssertEqual(ByVal testName As String, _
ByVal expected As Variant, _
ByVal actual As Variant)
'----------------------------------------------
' 期待値と実際の値が一致するか判定する
' 引数: testName - テスト名
' expected - 期待値
' actual - 実際の値
'----------------------------------------------
If expected = actual Then
mPassCount = mPassCount + 1
AddResult testName, TEST_PASS, CStr(expected), CStr(actual)
Else
mFailCount = mFailCount + 1
AddResult testName, TEST_FAIL, CStr(expected), CStr(actual), _
"値が一致しません"
End If
End Sub
Public Sub AssertNotEqual(ByVal testName As String, _
ByVal notExpected As Variant, _
ByVal actual As Variant)
'----------------------------------------------
' 値が不一致であることを判定する
' 引数: testName - テスト名
' notExpected - この値と一致しないことを期待
' actual - 実際の値
'----------------------------------------------
If notExpected <> actual Then
mPassCount = mPassCount + 1
AddResult testName, TEST_PASS, "<>" & CStr(notExpected), CStr(actual)
Else
mFailCount = mFailCount + 1
AddResult testName, TEST_FAIL, "<>" & CStr(notExpected), CStr(actual), _
"値が一致してしまっています"
End If
End Sub
Public Sub AssertTrue(ByVal testName As String, _
ByVal condition As Boolean)
'----------------------------------------------
' 条件がTrueであるか判定する
' 引数: testName - テスト名
' condition - 判定する条件式
'----------------------------------------------
If condition Then
mPassCount = mPassCount + 1
AddResult testName, TEST_PASS, "True", "True"
Else
mFailCount = mFailCount + 1
AddResult testName, TEST_FAIL, "True", "False", _
"Trueを期待しましたがFalseでした"
End If
End Sub
Public Sub AssertFalse(ByVal testName As String, _
ByVal condition As Boolean)
'----------------------------------------------
' 条件がFalseであるか判定する
' 引数: testName - テスト名
' condition - 判定する条件式
'----------------------------------------------
If Not condition Then
mPassCount = mPassCount + 1
AddResult testName, TEST_PASS, "False", "False"
Else
mFailCount = mFailCount + 1
AddResult testName, TEST_FAIL, "False", "True", _
"Falseを期待しましたがTrueでした"
End If
End Sub
Public Sub AssertError(ByVal testName As String, _
ByVal errNumber As Long)
'----------------------------------------------
' 直前にOn Error Resume Nextで捕捉したエラーを検証する
' 使用例:
' On Error Resume Next
' result = SomeFunction() ' エラーが起きるはず
' AssertError "エラーテスト", 11 ' 0除算エラー
' On Error GoTo 0
'
' 引数: testName - テスト名
' errNumber - 期待するエラー番号
'----------------------------------------------
If Err.Number = errNumber Then
mPassCount = mPassCount + 1
AddResult testName, TEST_PASS, "Error " & errNumber, _
"Error " & Err.Number
Else
mFailCount = mFailCount + 1
AddResult testName, TEST_FAIL, "Error " & errNumber, _
"Error " & Err.Number, _
"期待したエラーが発生しませんでした"
End If
Err.Clear
End Sub
Public Sub SkipTest(ByVal testName As String, _
Optional ByVal reason As String = "")
'----------------------------------------------
' テストをスキップ扱いにする
' 引数: testName - テスト名
' reason - スキップ理由
'----------------------------------------------
mSkipCount = mSkipCount + 1
AddResult testName, TEST_SKIP, "", "", reason
End Sub
Public Sub RecordError(ByVal testName As String, _
ByVal errMsg As String)
'----------------------------------------------
' テスト実行中のエラーを記録する
' 引数: testName - テスト名
' errMsg - エラーメッセージ
'----------------------------------------------
mErrorCount = mErrorCount + 1
AddResult testName, TEST_ERROR, "", "", errMsg
End Sub
' === プロパティ ===
Public Property Get PassCount() As Long
PassCount = mPassCount
End Property
Public Property Get FailCount() As Long
FailCount = mFailCount
End Property
Public Property Get SkipCount() As Long
SkipCount = mSkipCount
End Property
Public Property Get ErrorCount() As Long
ErrorCount = mErrorCount
End Property
Public Property Get TotalCount() As Long
TotalCount = mPassCount + mFailCount + mSkipCount + mErrorCount
End Property
Public Property Get ResultCount() As Long
ResultCount = mResultCount
End Property
Public Function GetResultItem(ByVal index As Long) As Variant
'----------------------------------------------
' 指定インデックスのテスト結果を配列で返す
' 戻り値: Array(テスト名, ステータス, 期待値,
' 実際の値, メッセージ, モジュール名, 実行時間ms)
'----------------------------------------------
If index < 1 Or index > mResultCount Then
GetResultItem = Array("", "", "", "", "", "", 0)
Exit Function
End If
With mResults(index)
GetResultItem = Array(.TestName, .Status, .Expected, _
.Actual, .Message, .ModuleName, .ElapsedMs)
End With
End Function
MTestRunner モジュール(実務版)
'================================================
' MTestRunner モジュール(実務版)
' テストの自動発見・実行・レポート出力
'
' 機能:
' - Test_ で始まるプロシージャを自動発見して実行
' - テスト結果を専用シートに出力
' - 全体の実行時間を計測
' - PASS/FAIL/SKIPをカラー表示
'
' 使い方:
' RunAllTests を実行するだけ
'================================================
Option Explicit
' --- Win32 API:高精度タイマー ---
#If VBA7 Then
Private Declare PtrSafe Function QPC _
Lib "kernel32" Alias "QueryPerformanceCounter" _
(lpPerformanceCount As Currency) As Long
Private Declare PtrSafe Function QPF _
Lib "kernel32" Alias "QueryPerformanceFrequency" _
(lpFrequency As Currency) As Long
#Else
Private Declare Function QPC _
Lib "kernel32" Alias "QueryPerformanceCounter" _
(lpPerformanceCount As Currency) As Long
Private Declare Function QPF _
Lib "kernel32" Alias "QueryPerformanceFrequency" _
(lpFrequency As Currency) As Long
#End If
' --- 定数 ---
Private Const RESULT_SHEET_NAME As String = "TestResults"
Private Const TEST_PREFIX As String = "Test_"
' --- カラー定数 ---
Private Const COLOR_PASS As Long = 5287936 ' 緑 RGB(0,176,80)
Private Const COLOR_FAIL As Long = 255 ' 赤 RGB(255,0,0)
Private Const COLOR_SKIP As Long = 49407 ' 黄 RGB(255,192,0)
Private Const COLOR_ERROR As Long = 10053120 ' 紫 RGB(128,0,153)
Private Const COLOR_HEADER As Long = 12566463 ' ライトグレー RGB(191,191,191)
Private Const COLOR_WHITE As Long = 16777215 ' 白
Public Sub RunAllTests()
'----------------------------------------------
' メインのテスト実行プロシージャ
' Test_ で始まるすべてのプロシージャを検出・実行し
' 結果を専用シートに出力する
'----------------------------------------------
Dim startTime As Currency
Dim endTime As Currency
Dim freq As Currency
Dim totalElapsed As Double
' 画面更新を停止(高速化)
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
' タイマー開始
QPF freq
QPC startTime
' テスト結果を初期化
InitTest
' テストプロシージャを収集
Dim testProcs() As String
Dim testModules() As String
Dim testCount As Long
testCount = CollectTestProcedures(testProcs, testModules)
If testCount = 0 Then
MsgBox "Test_ で始まるプロシージャが見つかりません。" & vbCrLf & _
"テストモジュールにTest_で始まるSubプロシージャを作成してください。", _
vbExclamation, "テスト未検出"
GoTo Cleanup
End If
' 各テストを実行
Dim i As Long
For i = 1 To testCount
ExecuteOneTest testProcs(i), testModules(i)
Next i
' タイマー終了
QPC endTime
If freq > 0 Then
totalElapsed = (endTime - startTime) / freq * 1000
End If
' 結果をシートに出力
OutputResultsToSheet totalElapsed
' サマリーメッセージ
Dim msg As String
msg = "テスト完了!" & vbCrLf & vbCrLf
msg = msg & "合格: " & PassCount & " 件" & vbCrLf
msg = msg & "失敗: " & FailCount & " 件" & vbCrLf
msg = msg & "スキップ: " & SkipCount & " 件" & vbCrLf
msg = msg & "エラー: " & ErrorCount & " 件" & vbCrLf
msg = msg & "合計: " & TotalCount & " 件" & vbCrLf
msg = msg & vbCrLf & "実行時間: " & Format(totalElapsed, "#,##0.0") & " ms"
Dim icon As VbMsgBoxStyle
If FailCount = 0 And ErrorCount = 0 Then
icon = vbInformation
Else
icon = vbExclamation
End If
MsgBox msg, icon, "テスト結果サマリー"
Cleanup:
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Private Function CollectTestProcedures(ByRef procNames() As String, _
ByRef modNames() As String) As Long
'----------------------------------------------
' VBProject内からTest_で始まるSubプロシージャを収集
' 戻り値: 見つかったテスト数
'----------------------------------------------
Dim vbProj As Object
Dim vbComp As Object
Dim codeMod As Object
Dim lineNum As Long
Dim procName As String
Dim procKind As Long ' vbext_pk_Proc = 0
Dim count As Long
Set vbProj = ThisWorkbook.VBProject
' まずテスト数をカウント
count = 0
For Each vbComp In vbProj.VBComponents
' 標準モジュールのみ対象(Type = 1)
If vbComp.Type = 1 Then
Set codeMod = vbComp.CodeModule
lineNum = 1
Do While lineNum < codeMod.CountOfLines
procName = codeMod.ProcOfLine(lineNum, procKind)
If Len(procName) > 0 Then
If Left(procName, Len(TEST_PREFIX)) = TEST_PREFIX Then
count = count + 1
End If
' 次のプロシージャへスキップ
lineNum = lineNum + _
codeMod.ProcCountLines(procName, procKind)
Else
lineNum = lineNum + 1
End If
Loop
End If
Next vbComp
If count = 0 Then
CollectTestProcedures = 0
Exit Function
End If
' 配列に格納
ReDim procNames(1 To count)
ReDim modNames(1 To count)
Dim idx As Long
idx = 0
Dim prevProc As String
For Each vbComp In vbProj.VBComponents
If vbComp.Type = 1 Then
Set codeMod = vbComp.CodeModule
lineNum = 1
prevProc = ""
Do While lineNum < codeMod.CountOfLines
procName = codeMod.ProcOfLine(lineNum, procKind)
If Len(procName) > 0 Then
If Left(procName, Len(TEST_PREFIX)) = TEST_PREFIX Then
' 同じプロシージャを重複追加しない
If procName <> prevProc Then
idx = idx + 1
procNames(idx) = procName
modNames(idx) = vbComp.Name
prevProc = procName
End If
End If
lineNum = lineNum + _
codeMod.ProcCountLines(procName, procKind)
Else
lineNum = lineNum + 1
End If
Loop
End If
Next vbComp
CollectTestProcedures = idx
End Function
Private Sub ExecuteOneTest(ByVal procName As String, _
ByVal moduleName As String)
'----------------------------------------------
' テストプロシージャを1つ実行する
' エラーが発生しても他のテストに影響しないよう
' エラーハンドリングで保護する
'----------------------------------------------
SetCurrentModule moduleName
StartTimer
On Error GoTo ErrHandler
Application.Run procName
On Error GoTo 0
Exit Sub
ErrHandler:
RecordError procName, _
"実行時エラー " & Err.Number & ": " & Err.Description
On Error GoTo 0
End Sub
Private Sub OutputResultsToSheet(ByVal totalMs As Double)
'----------------------------------------------
' テスト結果を専用シートに出力する
' シートが存在しなければ新規作成する
'----------------------------------------------
Dim ws As Worksheet
' 結果シートを取得または作成
Set ws = GetOrCreateSheet(RESULT_SHEET_NAME)
ws.Cells.Clear
' --- ヘッダー行 ---
Dim headers As Variant
headers = Array("No.", "モジュール", "テスト名", "結果", _
"期待値", "実際の値", "メッセージ", "実行時間(ms)")
Dim col As Long
For col = 1 To 8
ws.Cells(1, col).Value = headers(col - 1)
Next col
' ヘッダーの書式設定
With ws.Range("A1:H1")
.Font.Bold = True
.Interior.Color = COLOR_HEADER
.Borders(xlEdgeBottom).LineStyle = xlContinuous
.Borders(xlEdgeBottom).Weight = xlMedium
End With
' --- データ行 ---
Dim row As Long
Dim resultItem As Variant
For row = 1 To ResultCount
resultItem = GetResultItem(row)
ws.Cells(row + 1, 1).Value = row ' No.
ws.Cells(row + 1, 2).Value = resultItem(5) ' モジュール
ws.Cells(row + 1, 3).Value = resultItem(0) ' テスト名
ws.Cells(row + 1, 4).Value = resultItem(1) ' 結果
ws.Cells(row + 1, 5).Value = resultItem(2) ' 期待値
ws.Cells(row + 1, 6).Value = resultItem(3) ' 実際の値
ws.Cells(row + 1, 7).Value = resultItem(4) ' メッセージ
ws.Cells(row + 1, 8).Value = Format(resultItem(6), "0.000") ' 実行時間
' 結果に応じた色分け
Select Case CStr(resultItem(1))
Case TEST_PASS
ws.Cells(row + 1, 4).Font.Color = COLOR_PASS
Case TEST_FAIL
ws.Cells(row + 1, 4).Font.Color = COLOR_FAIL
ws.Cells(row + 1, 4).Font.Bold = True
' FAIL行全体を薄い赤で強調
ws.Range(ws.Cells(row + 1, 1), ws.Cells(row + 1, 8)) _
.Interior.Color = RGB(255, 230, 230)
Case TEST_SKIP
ws.Cells(row + 1, 4).Font.Color = COLOR_SKIP
Case TEST_ERROR
ws.Cells(row + 1, 4).Font.Color = COLOR_ERROR
ws.Cells(row + 1, 4).Font.Bold = True
ws.Range(ws.Cells(row + 1, 1), ws.Cells(row + 1, 8)) _
.Interior.Color = RGB(240, 220, 255)
End Select
Next row
' --- サマリー行 ---
Dim summaryRow As Long
summaryRow = ResultCount + 3
ws.Cells(summaryRow, 1).Value = "=== テスト結果サマリー ==="
ws.Cells(summaryRow, 1).Font.Bold = True
ws.Cells(summaryRow + 1, 1).Value = "合格"
ws.Cells(summaryRow + 1, 2).Value = PassCount
ws.Cells(summaryRow + 1, 2).Font.Color = COLOR_PASS
ws.Cells(summaryRow + 2, 1).Value = "失敗"
ws.Cells(summaryRow + 2, 2).Value = FailCount
ws.Cells(summaryRow + 2, 2).Font.Color = COLOR_FAIL
ws.Cells(summaryRow + 3, 1).Value = "スキップ"
ws.Cells(summaryRow + 3, 2).Value = SkipCount
ws.Cells(summaryRow + 3, 2).Font.Color = COLOR_SKIP
ws.Cells(summaryRow + 4, 1).Value = "エラー"
ws.Cells(summaryRow + 4, 2).Value = ErrorCount
ws.Cells(summaryRow + 4, 2).Font.Color = COLOR_ERROR
ws.Cells(summaryRow + 5, 1).Value = "合計"
ws.Cells(summaryRow + 5, 2).Value = TotalCount
ws.Cells(summaryRow + 5, 1).Font.Bold = True
ws.Cells(summaryRow + 5, 2).Font.Bold = True
ws.Cells(summaryRow + 7, 1).Value = "実行時間"
ws.Cells(summaryRow + 7, 2).Value = Format(totalMs, "#,##0.0") & " ms"
ws.Cells(summaryRow + 8, 1).Value = "実行日時"
ws.Cells(summaryRow + 8, 2).Value = Format(Now, "yyyy/mm/dd hh:nn:ss")
' --- 列幅の自動調整 ---
ws.Columns("A:H").AutoFit
' シートをアクティブにして結果を表示
ws.Activate
ws.Range("A1").Select
End Sub
Private Function GetOrCreateSheet(ByVal sheetName As String) As Worksheet
'----------------------------------------------
' 指定名のシートを取得する(なければ作成)
' 引数 : sheetName - シート名
' 戻り値 : Worksheetオブジェクト
'----------------------------------------------
Dim ws As Worksheet
On Error Resume Next
Set ws = ThisWorkbook.Worksheets(sheetName)
On Error GoTo 0
If ws Is Nothing Then
Set ws = ThisWorkbook.Worksheets.Add( _
After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.count))
ws.Name = sheetName
End If
Set GetOrCreateSheet = ws
End Function
MTests モジュール(実務版)
'================================================
' MTests モジュール(実務版)
' テストケースの実例
'
' 命名規則:
' Sub Test_[対象関数名]_[テスト内容]()
' 例: Test_CalcTax_標準税率
'================================================
Option Explicit
' ===================================================
' CalcTax 関数のテスト
' ===================================================
Public Sub Test_CalcTax_標準税率10パーセント()
'----------------------------------------------
' 税率10%の税込計算テスト
'----------------------------------------------
AssertEqual "CalcTax(1000, 0.1) = 1100", 1100, CalcTax(1000, 0.1)
AssertEqual "CalcTax(500, 0.1) = 550", 550, CalcTax(500, 0.1)
AssertEqual "CalcTax(1, 0.1) = 1", 1, CalcTax(1, 0.1)
End Sub
Public Sub Test_CalcTax_軽減税率8パーセント()
'----------------------------------------------
' 税率8%の税込計算テスト
'----------------------------------------------
AssertEqual "CalcTax(1000, 0.08) = 1080", 1080, CalcTax(1000, 0.08)
AssertEqual "CalcTax(999, 0.08) = 1078", 1078, CalcTax(999, 0.08)
AssertEqual "CalcTax(123, 0.08) = 132", 132, CalcTax(123, 0.08)
End Sub
Public Sub Test_CalcTax_境界値()
'----------------------------------------------
' 境界値テスト(0円、大きな金額)
'----------------------------------------------
AssertEqual "CalcTax(0, 0.1) = 0", 0, CalcTax(0, 0.1)
AssertEqual "CalcTax(999999, 0.1) = 1099998", _
1099998, CalcTax(999999, 0.1)
AssertTrue "CalcTax結果は0以上", CalcTax(100, 0.1) >= 0
End Sub
' ===================================================
' CalcDiscount 関数のテスト
' ===================================================
Public Sub Test_CalcDiscount_正常な割引()
'----------------------------------------------
' 正常な割引率でのテスト
'----------------------------------------------
AssertEqual "10%OFF: 1000 -> 900", 900, CalcDiscount(1000, 10)
AssertEqual "20%OFF: 1000 -> 800", 800, CalcDiscount(1000, 20)
AssertEqual "50%OFF: 1000 -> 500", 500, CalcDiscount(1000, 50)
AssertEqual "33%OFF: 1000 -> 670", 670, CalcDiscount(1000, 33)
End Sub
Public Sub Test_CalcDiscount_境界値()
'----------------------------------------------
' 0%と100%の境界値テスト
'----------------------------------------------
AssertEqual "0%OFF: 変化なし", 1000, CalcDiscount(1000, 0)
AssertEqual "100%OFF: 0円", 0, CalcDiscount(1000, 100)
End Sub
Public Sub Test_CalcDiscount_異常値()
'----------------------------------------------
' 不正な割引率でのエラーハンドリングテスト
'----------------------------------------------
AssertEqual "負の割引率はエラー(-1)", -1, CalcDiscount(1000, -10)
AssertEqual "100超の割引率はエラー(-1)", -1, CalcDiscount(1000, 150)
AssertNotEqual "正常割引はエラーではない", -1, CalcDiscount(1000, 10)
End Sub
' ===================================================
' 文字列操作関数のテスト(追加例)
' ===================================================
Public Sub Test_文字列結合_基本動作()
'----------------------------------------------
' VBA標準の文字列結合が正しく動くことを確認
' (自作関数テストの参考例)
'----------------------------------------------
AssertEqual "文字列結合", "Hello World", "Hello" & " " & "World"
AssertEqual "空文字結合", "ABC", "ABC" & ""
AssertTrue "Len関数の動作", Len("テスト") = 3
End Sub
Public Sub Test_数値変換_IsNumeric()
'----------------------------------------------
' IsNumericの動作を確認するテスト例
'----------------------------------------------
AssertTrue "数値文字列はTrue", IsNumeric("123")
AssertTrue "小数もTrue", IsNumeric("12.5")
AssertFalse "文字列はFalse", IsNumeric("ABC")
AssertFalse "空文字はFalse", IsNumeric("")
End Sub
' ===================================================
' スキップテストの例
' ===================================================
Public Sub Test_外部連携_API呼び出し()
'----------------------------------------------
' 外部APIへの接続テスト(環境依存のためスキップ)
'----------------------------------------------
SkipTest "外部API連携テスト", "外部接続が必要なためスキップ"
End Sub
—
実務版コードの解説
テストの自動発見の仕組み
実務版の最大の特徴は、CollectTestProcedures関数によるテストの自動発見機能です。
' VBProject内の全モジュールをスキャン
For Each vbComp In vbProj.VBComponents
If vbComp.Type = 1 Then ' 標準モジュールのみ
Set codeMod = vbComp.CodeModule
' プロシージャ名がTest_で始まるものを収集
If Left(procName, Len(TEST_PREFIX)) = TEST_PREFIX Then
' テスト対象として登録
End If
End If
Next vbComp
この仕組みにより、新しいテストを追加するときは Test_ で始まるSubプロシージャを書くだけで自動的に検出されます。テストランナー側の修正は不要です。
注意:この機能を使うには、「VBAプロジェクトオブジェクトモデルへのアクセスを信頼する」設定が必要です。Excel の「ファイル」→「オプション」→「トラストセンター」→「トラストセンターの設定」→「マクロの設定」で、この項目にチェックを入れてください。
処理時間の計測
各テストの実行時間を QueryPerformanceCounter APIで計測しています。これにより、処理が遅いテストを特定できます。
処理時間の計測について詳しくは、マクロの処理時間を計測して高速化のボトルネックを見つける方法を参考にしてください。
' 高精度タイマーAPIを使用
Private Declare PtrSafe Function QueryPerformanceCounter _
Lib "kernel32" (lpPerformanceCount As Currency) As Long
Timer関数(秒単位)では精度が足りないため、マイクロ秒精度のQueryPerformanceCounterを使用しています。Currency型(64ビット整数)で受け取ることで、LongLong型が使えないVBA環境でも正確に計測できます。
シートへの結果出力
OutputResultsToSheetプロシージャでは、結果を「TestResults」シートに出力します。
| 出力項目 | 説明 |
|---|---|
| No. | テストの連番 |
| モジュール | テストが属するモジュール名 |
| テスト名 | Assert関数に渡したテスト名 |
| 結果 | PASS / FAIL / SKIP / ERROR |
| 期待値 | 期待した値 |
| 実際の値 | 実際に得られた値 |
| メッセージ | 失敗時の補足情報 |
| 実行時間 | テストの実行時間(ms) |
結果は色分けされるので、どこが失敗しているかが一目でわかります。
- 緑(PASS):テスト合格
- 赤(FAIL):テスト失敗(行全体が薄い赤背景)
- 黄(SKIP):スキップ
- 紫(ERROR):実行時エラー(行全体が薄い紫背景)
エラーハンドリングの工夫
テスト実行時のエラーハンドリングは重要なポイントです。1つのテストでエラーが発生しても、他のテストを継続できるよう設計しています。
Private Sub ExecuteOneTest(ByVal procName As String, _
ByVal moduleName As String)
On Error GoTo ErrHandler
Application.Run procName
On Error GoTo 0
Exit Sub
ErrHandler:
' エラーを記録して次のテストに進む
RecordError procName, _
"実行時エラー " & Err.Number & ": " & Err.Description
On Error GoTo 0
End Sub
エラー処理の基本パターンについては、エラー処理で止まらないマクロを作る方法で詳しく解説しています。また、リトライやログ出力を組み合わせた応用パターンは、エラー処理の応用パターン(リトライ・ログ・通知)を実装する方法で紹介しています。
AssertError の使い方
AssertErrorは、エラーが「発生すること」をテストする特殊なAssertです。使い方にはコツがあります。
Public Sub Test_ゼロ除算のエラーテスト()
Dim result As Double
' On Error Resume Next でエラーを捕捉
On Error Resume Next
result = 1 / 0 ' ゼロ除算エラーが発生するはず
AssertError "ゼロ除算エラー", 11 ' エラー番号11を期待
On Error GoTo 0
End Sub
ポイントは、On Error Resume Next でエラーを握りつぶした直後に AssertError を呼ぶことです。AssertError は Err.Number を確認して、期待したエラー番号と一致するかを検証します。
—
よくある落とし穴と対処法
落とし穴1:VBProjectへのアクセスが拒否される
症状:CollectTestProceduresでエラー「プログラミングによるVisual Basicプロジェクトへのアクセスは信頼性に欠けます」が出る。
対処法:
- Excelの「ファイル」→「オプション」を開く
- 「トラストセンター」→「トラストセンターの設定」をクリック
- 「マクロの設定」を選択
- 「VBAプロジェクトオブジェクトモデルへのアクセスを信頼する」にチェック
- OKで閉じる
この設定変更は会社のセキュリティポリシーで制限されている場合があります。その場合は、最小版のように手動でテストプロシージャを列挙する方法を使ってください。
落とし穴2:テスト内でシートを操作するとテスト同士が干渉する
症状:テスト単体では成功するが、全テストを一括実行すると失敗するテストがある。
原因:前のテストが変更したシートの状態が、次のテストに影響している。
対処法:テスト内でシートを操作する場合は、テストの開始時に初期化(セットアップ)、終了時に後片付け(ティアダウン)を行います。
Public Sub Test_シート操作_セル書き込み()
' セットアップ:テスト用シートを初期化
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
ws.Range("A1:Z100").ClearContents
' テスト実行
ws.Range("A1").Value = "テスト"
AssertEqual "セル書き込み", "テスト", ws.Range("A1").Value
' ティアダウン:テスト用データを消去
ws.Range("A1:Z100").ClearContents
End Sub
落とし穴3:浮動小数点の比較で失敗する
症状:計算結果が正しいはずなのに AssertEqual が失敗する。
原因:浮動小数点演算の誤差。例えば 0.1 + 0.2 は正確に 0.3 にならない。
対処法:浮動小数点を比較する場合は、許容誤差を設けた比較を行います。
Public Sub AssertAlmostEqual(ByVal testName As String, _
ByVal expected As Double, _
ByVal actual As Double, _
Optional ByVal tolerance As Double = 0.0001)
'----------------------------------------------
' 浮動小数点の近似一致を判定する
'----------------------------------------------
If Abs(expected - actual) <= tolerance Then
' PASS処理
Else
' FAIL処理
End If
End Sub
落とし穴4:Application.Runで日本語プロシージャ名が動かない
症状:テストプロシージャ名に日本語を使うと Application.Run でエラーになるケースがある。
原因:Excelのバージョンや環境によっては、Application.Runに日本語名を渡すと文字化けすることがある。
対処法:テストプロシージャ名はアルファベットのみにするか、モジュール名を明示的に指定します。
' モジュール名を含めた完全修飾名で呼び出す
Application.Run "MTests.Test_CalcTax_Rate10"
安全策として、プロシージャ名自体はアルファベットで書き、Assert関数のテスト名(第1引数)に日本語の説明を入れるスタイルがおすすめです。
落とし穴5:テスト結果シートが保護されている
症状:テスト結果の出力時にエラーが発生する。
原因:「TestResults」シートまたはブックにシート保護やブック保護がかかっている。
対処法:結果出力前に保護を解除するコードを追加します。
' シート保護の解除
On Error Resume Next
ws.Unprotect Password:=""
On Error GoTo 0
落とし穴6:テスト対象のコードがPrivateで参照できない
症状:Private宣言された関数やサブルーチンをテストから呼べない。
対処法:テスト対象の関数を Public にするか、テスト用のラッパー関数を作ります。テスト可能性を考慮して、なるべくPublicの小さな関数に分割する設計が望ましいです。
落とし穴7:大量テストの実行でメモリ不足になる
症状:テスト数が数百件を超えると、実行が極端に遅くなるか、メモリ不足エラーが発生する。
対処法:ReDim Preserve を毎回呼んでいると、配列の再確保でパフォーマンスが低下します。事前に大きめの配列を確保するか、一定件数ごとに結果を出力してメモリを解放します。
' 事前に十分なサイズを確保
ReDim mResults(1 To 1000) ' 最大1000件
' 実際の件数はmResultCountで管理
VBAのテスト自動実行でVBProjectアクセスが拒否されるときの対処法
「プログラミングによるVisual Basicプロジェクトへのアクセスは信頼性に欠けます」エラーが出る場合、トラストセンターの設定が原因だ。「ファイル」→「オプション」→「トラストセンター」→「マクロの設定」で「VBAプロジェクトオブジェクトモデルへのアクセスを信頼する」にチェックを入れよう。会社のポリシーで制限されている場合は手動列挙方式を使う。
VBAのテストが一括実行で失敗するときの対処法
「テスト単体ではPASSするのに全テスト実行だとFAILになる」場合、テスト同士がシートの状態を共有していて前のテストの結果が次に影響していることが原因だ。各テストの冒頭でシートを初期化するSetup処理を入れ、テスト終了後にTeardownでクリーンアップする仕組みを導入しよう。
—
よくある質問(FAQ)
Q1:テストはいつ実行すればよいですか?
A1:最低限、以下のタイミングで実行してください。
- コードを修正したとき(回帰テスト)
- 新しい機能を追加したとき(新機能の動作確認 + 既存機能の回帰テスト)
- 本番環境に反映する前(リリース前テスト)
理想的には、修正するたびに毎回全テストを実行する習慣をつけるのがベストです。自動テストなら数秒で完了するので、負担にはなりません。
Q2:テストケースはどのくらい書けばよいですか?
A2:最低限、以下の3パターンを網羅しましょう。
- 正常系:通常の入力でのテスト(最も基本)
- 境界値:0、空文字、最大値、最小値など端の値
- 異常系:不正な入力(マイナス値、型違い、Null等)
すべての関数にテストを書く必要はありません。まずはバグが起きると被害が大きい重要な関数(金額計算、日付処理など)から始めてください。
Q3:テスト対象の関数がシートのデータに依存しています。どうすればよいですか?
A3:テスト用のデータをテストコード内でセットアップする方法が確実です。
Public Sub Test_集計関数()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
' セットアップ:テスト用データを書き込む
ws.Range("A1").Value = 100
ws.Range("A2").Value = 200
ws.Range("A3").Value = 300
' テスト実行
AssertEqual "合計", 600, ws.Range("A1").Value + _
ws.Range("A2").Value + _
ws.Range("A3").Value
' ティアダウン:後片付け
ws.Range("A1:A3").ClearContents
End Sub
または、テスト専用のシートを作成して、そこにテストデータを置く方法もあります。
Q4:MsgBoxやInputBoxが含まれるコードはテストできますか?
A4:MsgBoxやInputBoxはユーザーの操作を待つため、そのままでは自動テストできません。対処法は以下の2つです。
方法1:処理ロジックをUI部分から分離する
' NG:UIとロジックが混在
Sub 処理実行()
Dim input As String
input = InputBox("金額を入力")
MsgBox "税込: " & Int(CLng(input) * 1.1)
End Sub
' OK:ロジックを関数に分離(テスト可能)
Function CalcTaxAmount(ByVal price As Long) As Long
CalcTaxAmount = Int(price * 1.1)
End Function
Sub 処理実行()
Dim input As String
input = InputBox("金額を入力")
MsgBox "税込: " & CalcTaxAmount(CLng(input))
End Sub
方法2:テスト時はMsgBoxを表示しないフラグを用意する
Public gIsTestMode As Boolean
Sub ShowResult(ByVal msg As String)
If Not gIsTestMode Then
MsgBox msg
End If
End Sub
Q5:テストを特定のモジュールだけ実行することはできますか?
A5:CollectTestProcedures関数にフィルター条件を追加すれば可能です。例えば、モジュール名で絞り込む関数を作ります。
Public Sub RunTestsInModule(ByVal targetModule As String)
InitTest
Dim testProcs() As String
Dim testModules() As String
Dim testCount As Long
testCount = CollectTestProcedures(testProcs, testModules)
Dim i As Long
For i = 1 To testCount
If testModules(i) = targetModule Then
ExecuteOneTest testProcs(i), testModules(i)
End If
Next i
OutputResultsToSheet 0
End Sub
Q6:テストが失敗したとき、デバッグのコツはありますか?
A6:以下の手順でデバッグするのが効率的です。
- テスト結果シートで失敗箇所を確認:「期待値」と「実際の値」の差分を見る
- 該当のテストプロシージャにブレークポイントを設定:テスト単体をF5で実行
- Debug.Printで中間値を確認:Debug.Printとイミディエイトウィンドウで変数の中身を確認する方法を参考に
- 期待値が間違っていないか確認:テストコード自体のバグの場合もある
Q7:他のブックの関数もテストできますか?
A7:Application.Runにブック名を含めることで、他のブックの関数も呼び出せます。ただし、対象のブックが開いている必要があります。
Dim result As Variant
result = Application.Run("'対象ブック.xlsm'!CalcTax", 1000, 0.1)
AssertEqual "他ブックのCalcTax", 1100, CLng(result)
—
まとめ
この記事では、VBAでテストを自動化する方法を解説しました。
この記事で学んだこと
| 項目 | 内容 |
|---|---|
| Assert関数 | AssertEqual / AssertTrue / AssertFalse / AssertNotEqual / AssertError で合否判定 |
| テストケース | Test_ で始まるプロシージャとして記述 |
| テストランナー | VBProjectのコードモジュールからTest_プロシージャを自動発見・実行 |
| 結果レポート | 専用シートにPASS/FAIL/SKIP/ERRORを色分けで出力 |
| 処理時間計測 | QueryPerformanceCounter APIで各テストの実行時間を測定 |
テスト自動化を成功させるポイント
- 小さく始める:まずは最も重要な関数1つのテストから書く
- 関数を小さく保つ:テストしやすいコード = 保守しやすいコード
- UIとロジックを分離する:ロジック部分を純粋な関数として切り出す
- 修正のたびにテスト実行:回帰バグを即座に検出する習慣をつける
- テスト名は分かりやすく:失敗時に何が壊れたかすぐ分かる命名を
テスト自動化は最初の導入コストこそかかりますが、コードが増えるほど、修正が増えるほど、その効果は絶大です。「動いてるから大丈夫」から「テストがパスしているから大丈夫」へ。ぜひ一歩を踏み出してみてください。
—
次のステップ
テスト自動化をマスターしたら、以下の記事でさらにスキルアップしましょう。
- VBAのデバッグ技術を使いこなす方法:ブレークポイント、ステップ実行、ウォッチ式を活用した効率的なデバッグ手法
- Debug.Printとイミディエイトウィンドウで変数の中身を確認する方法:テスト失敗時のデバッグに必須のテクニック
- エラー処理の応用パターン(リトライ・ログ・通知)を実装する方法:テスト対象コードの品質を上げるエラー処理パターン
- マクロの処理時間を計測して高速化のボトルネックを見つける方法:テストの実行時間が長いときの最適化手法
- エラー処理で止まらないマクロを作る方法:安定して動作するマクロの基本
—
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "【VBA】VBAコードの動作テストを自動化する方法(コピペOK)",
"description": "VBAコードの動作テストを自動化する方法を解説。Assert関数による単体テストの基本と、テストスイート・結果レポート・回帰テスト自動実行の実務版をコピペで使えるコード付きで紹介します。",
"datePublished": "2026-03-26",
"dateModified": "2026-03-26",
"author": {
"@type": "Person",
"name": "VBA業務自動化ラボ"
},
"publisher": {
"@type": "Organization",
"name": "コピペで始めるVBA",
"logo": {
"@type": "ImageObject",
"url": "https://example.com/logo.png"
}
},
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://example.com/181"
},
"keywords": ["VBA", "テスト", "自動化", "品質チェック", "Assert", "単体テスト", "デバッグ", "Excel VBA"],
"articleSection": "シート操作",
"inLanguage": "ja",
"about": {
"@type": "SoftwareSourceCode",
"programmingLanguage": "VBA",
"runtimePlatform": "Microsoft Excel"
},
"hasPart": [
{
"@type": "HowTo",
"name": "VBAコードの動作テストを自動化する手順",
"step": [
{
"@type": "HowToStep",
"position": 1,
"name": "テスト対象の関数を用意する",
"text": "テストしたい関数(CalcTaxなど)を標準モジュールにPublic Functionとして作成します。"
},
{
"@type": "HowToStep",
"position": 2,
"name": "Assert関数モジュールを作成する",
"text": "MAssertモジュールを作成し、AssertEqual、AssertTrue等のテスト判定関数を実装します。"
},
{
"@type": "HowToStep",
"position": 3,
"name": "テストケースを書く",
"text": "MTestsモジュールにTest_で始まるSubプロシージャとしてテストケースを記述します。"
},
{
"@type": "HowToStep",
"position": 4,
"name": "テストランナーで実行する",
"text": "RunAllTestsプロシージャを実行し、全テストを自動的に検出・実行します。"
},
{
"@type": "HowToStep",
"position": 5,
"name": "結果を確認する",
"text": "TestResultsシートに出力されたPASS/FAIL/SKIP/ERRORの結果を確認します。"
}
]
}
],
"isPartOf": {
"@type": "CreativeWork",
"name": "コピペで始めるVBA",
"url": "https://example.com"
}
}

コメント