【テクニカル・上級編】【プロ】Access VBAによる「テーブル定義のユニットテスト」環境の構築 – Access VBA解析バイブル

スポンサーリンク

【プロ】Access VBAによる「テーブル定義のユニットテスト」環境の構築

レガシーシステムの最前線に立つ我々にとって、Accessは単なる「デスクトップの簡易DB」ではない。時には数千人のユーザーを背負う基幹のフロントエンドであり、時には複雑なデータ整合性を維持する要塞だ。

しかし、Access開発現場の多くは、未だに「目視によるテーブル定義の確認」という前近代的なアプローチから抜け出せていない。誰かが勝手にフィールドのデータ型を変更した、インデックスを削除した、あるいはリレーションシップの外部キー制約(カスケード削除)が外れていた——。これらが原因で、夜間バッチが沈黙し、翌朝に業務が完全停止する。この悪夢を、あなたは何度経験しただろうか。

シニアエンジニアよ、もう「祈るようなデプロイ」はやめにしよう。
本稿では、DAO(Data Access Objects)の深層を突き詰め、Access VBAの領域に「テーブル定義のユニットテスト(構造的アテスト)」というCI/CD的アプローチを完全実装する方法を授ける。

1. なぜ「テーブル定義のテスト」がVBAに必要なのか

アプリケーションコードのテスト(単体テスト)を書くエンジニアは多いが、「DBの物理スキーマをテストする」発想を持つ者は少ない。特にAccess(JET/ACEエンジン)は、SQLのDDL(ALTER TABLE等)の挙動がANSI標準と微妙に異なる点が多く、GUIでの手動変更が横行しやすい。

プロフェッショナルなアーキテクチャにおいて、DB構造は「不変の契約(Contract)」である。アプリケーションが動く前に、またはデプロイの瞬間に、この契約が破られていないかをプログラム自身に検証させなければならない。

DAOのライフサイクルとパフォーマンスの罠

テーブル定義を走査・検証する際、最も陥りがちなのがメモリリークとCOMオブジェクトの解放漏れである。
`CurrentDb` や `TableDefs`、`Fields` といったDAOのコレクションは、背後でCOM(Component Object Model)のポインタを保持している。これらを適切に解放(`Set obj = Nothing`)しないと、Accessの内部メモリが肥大化し、最悪の場合はACEエンジンが破損する。

極限のパフォーマンスと堅牢性を両立させたテストランナーのコードを以下に提示する。

2. 実装:構造検証テストランナー(VBA)

以下のコードは、指定されたテーブルが「期待されるフィールド名、データ型、サイズ、属性、インデックス」を持っているかを厳密に検証し、違反があれば即座にイミディエイトウィンドウへエラーを出力、あるいは例外を発生させるフレームワークのコアである。

Option Compare Database
Option Explicit

‘ =========================================================================
‘ 構造体定義:期待されるフィールド定義
‘ =========================================================================
Public Type ExpectedField
Name As String
DataType As Long ‘ DAO.DataTypeEnum (dbText, dbLong, dbDate 等)
Size As Long ‘ 文字列長や数値のバイト数
Required As Boolean ‘ 必須入力か
AllowZeroLength As String ‘ 長さ0の文字列を許容するか (Text型のみ)
End Type

‘ =========================================================================
‘ メイン:テーブル定義の統合テストスイート
‘ =========================================================================
Public Sub RunTableSchemaTests()
Dim isSuccess As Boolean
isSuccess = True

On Error GoTo ErrorHandler

Debug.Print “=== テーブル定義ユニットテスト開始: ” & Now & ” ===”

‘ テストケース1: 「M_顧客」テーブルの構造検証
If Not VerifyTable_M_Customer() Then isSuccess = False

‘ テストケース2: 「T_受注明細」テーブルの構造検証(外部キー・インデックス含む)
If Not VerifyTable_T_OrderDetails() Then isSuccess = False

If isSuccess Then
Debug.Print “=== [SUCCESS] すべてのテーブル定義テストが正常終了しました ===”
MsgBox “テーブル定義の整合性検証に合格しました。”, vbInformation, “テスト成功”
Else
Debug.Print “=== [FAILURE] テーブル定義に不一致が見つかりました ===”
Err.Raise 9999, “SchemaTest”, “テーブル定義テストが失敗しました。詳細はイミディエイトウィンドウを確認してください。”
End If

CleanUp:
Exit Sub

ErrorHandler:
MsgBox “テスト実行中に致命的なエラーが発生しました: ” & Err.Description, vbCritical, “テスト異常終了”
Resume CleanUp
End Sub

‘ =========================================================================
‘ 個別テーブル検証ロジック: M_顧客
‘ =========================================================================
Private Function VerifyTable_M_Customer() As Boolean
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim fld As DAO.Field
Dim result As Boolean

result = True
Set db = CurrentDb()

On Error GoTo TableNotFound
Set tdf = db.TableDefs(“M_顧客”)
On Error GoTo 0

‘ 1. フィールド存在確認と属性チェック
‘ 顧客ID (Long, 必須)
If Not CheckField(tdf, “顧客ID”, dbLong, 4, True) Then result = False

‘ 顧客名 (Text, 50文字, 必須)
If Not CheckField(tdf, “顧客名”, dbText, 50, True) Then result = False

‘ 登録日時 (Date, 必須)
If Not CheckField(tdf, “登録日時”, dbDate, 8, True) Then result = False

‘ 2. 主キー(Primary Key)の存在確認
If Not CheckPrimaryKey(tdf, Array(“顧客ID”)) Then
Debug.Print “[FAIL] M_顧客: 主キーの構成が期待値と異なります。”
result = False
End If

VerifyTable_M_Customer = result

CleanUp:
‘ 【重要】COMオブジェクトの明示的解放によるメモリ最適化
Set fld = Nothing
Set tdf = Nothing
Set db = Nothing
Exit Function

TableNotFound:
Debug.Print “[FAIL] テーブル ‘M_顧客’ が存在しません。”
result = False
Resume CleanUp
End Function

‘ =========================================================================
‘ 個別テーブル検証ロジック: T_受注明細
‘ =========================================================================
Private Function VerifyTable_T_OrderDetails() As Boolean
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim result As Boolean

result = True
Set db = CurrentDb()

On Error GoTo TableNotFound
Set tdf = db.TableDefs(“T_受注明細”)
On Error GoTo 0

‘ 複合主キーの検証 (受注ID, 明細行番号)
If Not CheckPrimaryKey(tdf, Array(“受注ID”, “明細行番号”)) Then
Debug.Print “[FAIL] T_受注明細: 複合主キーの構成が不正です。”
result = False
End If

‘ リレーションシップ(外部キー)の物理整合性チェック
If Not CheckRelation(db, “M_顧客”, “T_受注明細”, “顧客ID”, “顧客ID”) Then
Debug.Print “[FAIL] 外部キー制約 (M_顧客 -> T_受注明細) が存在しないか、設定が不正です。”
result = False
End If

VerifyTable_T_OrderDetails = result

CleanUp:
Set tdf = Nothing
Set db = Nothing
Exit Function

TableNotFound:
Debug.Print “[FAIL] テーブル ‘T_受注明細’ が存在しません。”
result = False
Resume CleanUp
End Function

3. アサーション(検証)ヘルパー関数の極意

上記のテストスイートを支える、再利用性の高いアサーション関数群だ。DAOの隠れたプロパティ(`Attributes`, `Required`, `Type` 等)を正確に叩くことで、人間の目では気づけないわずかな型の違いをも看破する。

‘ =========================================================================
‘ 汎用フィールドアサーション
‘ =========================================================================
Private Function CheckField(tdf As DAO.TableDef, fieldName As String, expectedType As Long, expectedSize As Long, expectedRequired As Boolean) As Boolean
Dim fld As DAO.Field
Dim isValid As Boolean

isValid = True

On Error GoTo FieldNotFound
Set fld = tdf.Fields(fieldName)
On Error GoTo 0

‘ データ型の検証
If fld.Type <> expectedType Then
Debug.Print “[FAIL] ” & tdf.Name & “.” & fieldName & ” 型不一致: 期待値=” & expectedType & “, 実際=” & fld.Type
isValid = False
End If

‘ サイズの検証(Text型などの場合)
If fld.Type = dbText Then
If fld.Size <> expectedSize Then
Debug.Print “[FAIL] ” & tdf.Name & “.” & fieldName & ” サイズ不一致: 期待値=” & expectedSize & “, 実際=” & fld.Size
isValid = False
End If
End If

‘ 必須プロパティの検証
If fld.Required <> expectedRequired Then
Debug.Print “[FAIL] ” & tdf.Name & “.” & fieldName & ” 必須設定不一致: 期待値=” & expectedRequired & “, 実際=” & fld.Required
isValid = False
End If

CheckField = isValid

CleanUp:
Set fld = Nothing
Exit Function

FieldNotFound:
Debug.Print “[FAIL] テーブル ‘” & tdf.Name & “‘ にフィールド ‘” & fieldName & “‘ が存在しません。”
isValid = False
Resume CleanUp
End Function

‘ =========================================================================
‘ 主キー検証アサーション
‘ =========================================================================
Private Function CheckPrimaryKey(tdf As DAO.TableDef, expectedKeys() As Variant) As Boolean
Dim idx As DAO.Index
Dim i As Long
Dim pkFound As Boolean

pkFound = False

For Each idx In tdf.Indexes
If (idx.Attributes & dbPrimaryKey) = dbPrimaryKey Then
pkFound = True
‘ キーの数の一致確認
If idx.Fields.Count <> (UBound(expectedKeys) – LBound(expectedKeys) + 1) Then
Set idx = Nothing
CheckPrimaryKey = False
Exit Function
End If

‘ 各構成フィールドの一致確認
For i = LBound(expectedKeys) To UBound(expectedKeys)
‘ 注: インデックス内のフィールド順序も厳密にチェック
If idx.Fields(i – LBound(expectedKeys)).Name <> expectedKeys(i) Then
Set idx = Nothing
CheckPrimaryKey = False
Exit Function
End If
Next i
End If
Next idx

Set idx = Nothing
CheckPrimaryKey = pkFound
End Function

‘ =========================================================================
‘ 外部キー(リレーションシップ)検証アサーション
‘ =========================================================================
Private Function CheckRelation(db As DAO.Database, primaryTable As String, foreignTable As String, primaryField As String, foreignField As String) As Boolean
Dim rel As DAO.Relation
Dim found As Boolean

found = False

For Each rel In db.Relations
If rel.Table = primaryTable And rel.ForeignTable = foreignTable Then
If rel.Fields(0.Name) = primaryField And rel.Fields(0.ForeignName) = foreignField Then
‘ カスケード更新・削除のオプションが有効かどうかもここで検証可能
‘ (rel.Attributes & dbRelationUpdateCascade) 等
found = True
Exit For
End If
End If
Next rel

Set rel = Nothing
CheckRelation = found
End Function

4. チーフアーキテクトからの実践的提言:CI/CDパイプラインへの組込

このテストコードを書いて「おしまい」にしてはならない。真のプロフェッショナルは、これを外部の自動化スクリプト(PowerShell等)からヘッドレス(GUI非表示)で実行し、ビルドパイプラインやデプロイメントのゲートとして機能させる。

例えば、以下のようなPowerShellスクリプトをタスクスケジューラやGitHub Actions(セルフホステッドランナー環境)に組み込むことで、AccessのDB構造検証を完全に自動化できる。

PowerShellによるAccessユニットテストのヘッドレス実行
$accessPath = “C:\Projects\EnterpriseApp\Backend.accdb”
$access = New-Object -ComObject Access.Application
$access.OpenCurrentDatabase($accessPath)

try {
# VBA内のテストランナープロシージャを呼出
$access.Run(“RunTableSchemaTests”)
Write-Host “テーブル定義テストを正常に通過しました。” -ForegroundColor Green
}
catch {
Write-Error “テーブル定義テストが失敗しました。ビルドを中断します。”
$access.Quit()
exit 1
}

$access.Quit()
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($access) | Out-Null

結論

Accessというレガシーなプラットフォームであっても、設計思想をモダンなCI/CDの文脈にアライメントさせることは十分に可能だ。
「動けばいい」という妥協を捨て、テーブル定義そのものをコードで守る。この規律を導入した瞬間から、あなたの管理するシステムは、現場の泥臭い改修に揺るぎない堅牢性を手に入れる。

コードを書け。テストを走らせろ。アーキテクチャの尊厳をその手で守り抜け。

タイトルとURLをコピーしました