Excel VBAを使った新しい関数のテスト結果確認の方法

Excel VBAを使った新しい関数のテスト結果確認の方法を現在の環境で使うなら「A1:A100が同じ文字列かを見るのではなく、test case tableからinputとexpectedを読み、functionを呼び、actual・error・pass/failを記録します。blank、boundary、invalid、errorを含め、全case数とfailed数で判定します。」が基本方針です。対象はproduction workbookとは別copyで、input、expected、tolerance、actual、pass/failを一行一caseにするケースです。最初の確認を読み取りだけに限定し、変更後に何を照合し、どの証跡から戻すかまで明記します。 確認ポイント:各caseには期待値、許容差、期待errorを持たせ、変更したApplication状態は終了経路にかかわらず復元します。

目次

test case表と対象関数の境界を固定する

VBA関数テストは、入力値、期待値、実値、エラー番号、合否を一ケース一行で残します。元ブックを複製し、外部ファイルやシートを変更しないテスト用データで正常系・境界値・エラー系を実行します。一件で例外が起きても次へ進め、最終的に総数が成功数と失敗数の合計に一致することを確認します。

  • functionの仕様、input型、return型、error契約を確認する
  • test workbookとproduction dataを分離する
  • expected値と数値toleranceをreviewする
  • Application stateとexternal dependencyを固定する

変更前の関数版と期待値を保存する

testはproduction workbookやDBを変更しないcopyで行います。functionがmail、file、DB update等のside effectを持つ場合はdependencyをstub化し、本番endpointへ接続しません。実行前backupを取り、Application stateを必ず復元します。失敗時はtest outputだけを破棄し、production dataへ修正を自動反映しません。

失敗case後も次のcaseを実行できるか確認する

  • 全cellをExpectedValue一つと比較する
  • 一件失敗で残りcaseを実行しない
  • errorをResume Nextで隠す
  • floating pointを完全一致する
  • production dataでtestする

入力・実行・記録を一caseずつ分離する

CaseIdとExpectedをtableで定義する

' tblTests columns: CaseId, Input1, Input2, Expected, Tolerance, Actual, ErrorNumber, Passed
' case IDはuniqueにする

expectedをcodeへ一つだけ直書きしません。

数値と文字列で比較規則を切り替える

actual = FunctionUnderTest(input1, input2)
If IsNumeric(actual) And IsNumeric(expected) Then
  passed = Abs(CDbl(actual)-CDbl(expected)) <= tolerance
Else
  passed = (CStr(actual)=CStr(expected))
End If

型とtoleranceを仕様に合わせます。

Err.Numberをcase結果へ格納する

On Error GoTo CaseFail
' call function and set actual/passed
WriteResult:
  ' write Actual, ErrorNumber, Passed
  GoTo NextCase
CaseFail:
  errNo = Err.Number: errText = Err.Description
  actual = "#ERROR: " & errText: passed = False
  Resume WriteResult
NextCase:
  ' continue with the next ListRow

On Error Resume Nextで全errorを隠しません。

pass・fail件数を合計する

passedCount=WorksheetFunction.CountIf(testTable.ListColumns("Passed").DataBodyRange,True)
failedCount=testTable.ListRows.Count-passedCount
Debug.Print testTable.ListRows.Count,passedCount,failedCount

一件不一致で残りtestを止めず全caseを記録します。

Application.Calculationを必ず復元する

oldCalc=Application.Calculation
On Error GoTo CleanFail
Application.Calculation=xlCalculationManual
' tests
CleanExit:
Application.Calculation=oldCalc
Exit Sub
CleanFail:
Resume CleanExit

ScreenUpdatingやEnableEventsも変更した場合は元へ戻します。

totalとpass+failの一致を機械判定する

文字列、date、floating point、Excel error、Empty、Nullは比較方法が違います。数値の完全一致を要求すると丸め差を誤判定し、toleranceを広くしすぎると不具合を見逃します。testがpassしても未定義caseやexternal data変化は残ります。functionのside effectがないかも確認します。

一時状態を戻してから結果sheetを保存する

normal、boundary、blank、invalid、error、locale/dateのcaseを用意し、故意に一件expectedを変えてharnessがfailを検知することも確認します。case合計=pass+fail、unique ID、actual列、error列を照合し、再実行で同じ結果になることを確認します。

関数test完了の合格基準

tblTestsの各rowを読み、FunctionUnderTestのactual、Err.Number、Passedを同じrowへ書きます。case error時はResume WriteResultでhandlerを抜けた後、GoTo NextCaseで次rowへ進み、active errorなしのResumeや未定義labelを使いません。

case単位で継続するtest runner

Option Explicit

' 動作確認用。実案件ではこの関数だけを対象関数呼出しへ置き換える。
Private Function FunctionUnderTest(ByVal input1 As Variant, ByVal input2 As Variant) As Variant
  If CStr(input1) = "RAISE" Then Err.Raise vbObjectError + 2201, , "sample error"
  FunctionUnderTest = CDbl(input1) + CDbl(input2)
End Function

Public Sub RunFunctionTests()
  Dim testTable As ListObject, lr As ListRow, ids As Object
  Dim input1 As Variant, input2 As Variant, expected As Variant, actual As Variant
  Dim tolerance As Double, passed As Boolean, errNo As Long, errText As String
  Dim caseId As String, passedCount As Long, failedCount As Long
  Dim oldCalc As XlCalculation, stateCaptured As Boolean
  On Error GoTo FatalFail

  oldCalc = Application.Calculation
  stateCaptured = True
  Application.Calculation = xlCalculationManual
  Set testTable = ThisWorkbook.Worksheets("Tests").ListObjects("tblTests")
  Set ids = CreateObject("Scripting.Dictionary")

  For Each lr In testTable.ListRows
    caseId = Trim$(CStr(lr.Range.Cells(1, testTable.ListColumns("CaseId").Index).Value2))
    If Len(caseId) = 0 Or ids.Exists(caseId) Then Err.Raise vbObjectError + 2202, , "Blank or duplicate CaseId: " & caseId
    ids.Add caseId, True
    input1 = lr.Range.Cells(1, testTable.ListColumns("Input1").Index).Value
    input2 = lr.Range.Cells(1, testTable.ListColumns("Input2").Index).Value
    expected = lr.Range.Cells(1, testTable.ListColumns("Expected").Index).Value
    tolerance = CDbl(lr.Range.Cells(1, testTable.ListColumns("Tolerance").Index).Value)
    actual = Empty: errNo = 0: errText = "": passed = False
    On Error GoTo CaseFail

    actual = FunctionUnderTest(input1, input2)
    If IsNumeric(actual) And IsNumeric(expected) Then
      passed = Abs(CDbl(actual) - CDbl(expected)) <= tolerance
    Else
      passed = (CStr(actual) = CStr(expected))
    End If

WriteResult:
    On Error GoTo FatalFail
    lr.Range.Cells(1, testTable.ListColumns("Actual").Index).Value = actual
    lr.Range.Cells(1, testTable.ListColumns("ErrorNumber").Index).Value2 = errNo
    lr.Range.Cells(1, testTable.ListColumns("Passed").Index).Value = passed
    If passed Then passedCount = passedCount + 1 Else failedCount = failedCount + 1
    GoTo NextCase

CaseFail:
    errNo = Err.Number
    errText = Err.Description
    actual = "#ERROR: " & errText
    passed = False
    Resume WriteResult

NextCase:
  Next lr
  Debug.Assert testTable.ListRows.Count = passedCount + failedCount
  Debug.Print "total=" & testTable.ListRows.Count, "passed=" & passedCount, "failed=" & failedCount

CleanExit:
  If stateCaptured Then Application.Calculation = oldCalc
  Exit Sub
FatalFail:
  MsgBox "RunFunctionTests failed: " & Err.Number & " " & Err.Description, vbExclamation
  Resume CleanExit
End Sub

このcodeは画面表示だけを見るための例ではありません。通常系では「全caseにActual/ErrorNumber/Passedが入り、total=passed+failedになる」を確認し、0件と実行errorを別の結果として保存します。

成功・期待不一致・例外発生を分類する

  • 期待どおり:全caseにActual/ErrorNumber/Passedが入り、total=passed+failedになる
  • 0件・非適用:test table 0行はNoCasesとしてapplication stateを復元して終了する
  • 実行error:個別function errorはそのcaseをFalseにして継続し、table/schema等のfatal errorは全体を停止する

Resume NextCaseはlabel不在ならcompile errorで、Resume CaseDone後に再びResumeするとerror 20になり得ます。期待値比較ではnumeric toleranceとstring exactを分け、Application.Calculation等を必ず元へ戻します。

tolerance境界・文字列・例外を試す

' 正常数値、tolerance境界、文字列、FunctionUnderTestがerrorを投げるcaseを用意し、
' error case後も次caseが実行されtotal=passed+failedになることを確認する。

0件、pass/fail、tolerance丁度、type違い、個別error、fatal schema errorをtestします。CaseId、expected/actual、error number、Passed、集計、復元後Calculationを保存します。

Excel VBAを使った新しい関数のテスト結果確認の方法の証跡には、実行対象と取得時刻に加え、通常・0件・errorのどれへ分類したかを残します。通常系は「全caseにActual/ErrorNumber/Passedが入り、total=passed+failedになる」、停止系は「個別function errorはそのcaseをFalseにして継続し、table/schema等のfatal errorは全体を停止する」を判断文としてそのまま作業票へ写し、担当者ごとの言い換えで意味が変わらないようにします。

VBA testを反復実行する場合は、CaseIdの重複をschema errorとして先に停止し、各caseのactual、型、許容差、Err.Numberを独立したrowへ出します。一つのcaseで起きたerrorを後続caseのexpectedへ流用せず、fatal errorと通常failを別集計にします。全case終了後にCalculationを復元できたことを確認し、未実行caseがあればpass率だけでreleaseを許可しません。

公式情報・参考資料

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次