Access VBAのデバッグとテスト|イミディエイトとブレークポイント
Access VBAの不具合を効率的に見つけるVBEのデバッグ道具を整理。ブレークポイント・ステップ実行(F8)・イミディエイトウィンドウ・ウォッチ式の使い方と、改修前に既存VBAの挙動を確認する簡易テストの考え方を解説します。
結論:ブレークポイントとイミディエイトウィンドウを使えば、問題箇所をすばやく絞り込める
当社の実務経験では、Access VBAのデバッグで時間がかかるケースの多くで、「どこで値がおかしくなったか」を確認せずにコードを読み続けているという共通点があります。VBE(Visual Basic Editor)には実行を止めて変数を覗く道具が最初から揃っています。ブレークポイント・ステップ実行・イミディエイトウィンドウの3つを組み合わせれば、「動いているつもりだったが実は空文字だった」「ループが意図した回数を回っていなかった」といった見落としを実行中に直接確認できます。
本記事では、VBEのデバッグ機能を道具ごとに整理します。エラー処理(On Error文)とは別の話で、「エラーにはなっていないが結果がおかしい」「処理はするが途中で止まる」といった場面に使う技術です。エラー処理の書き方はAccess VBAのエラー処理入門を参照してください。
VBEのデバッグ道具を整理する
まず道具の一覧を把握します。
| 道具 | 開き方 | 主な用途 |
|---|---|---|
| ブレークポイント | 行をクリックしてF9、または行の左端をクリック | 指定した行で実行を一時停止する |
| ステップ実行(F8) | F8キー | 1行ずつ実行して変数の変化を追う |
| プロシージャ ステップ(Shift+F8) | Shift+F8キー | 呼び出し先プロシージャを1塊として実行(中に入らない) |
| イミディエイトウィンドウ | Ctrl+G または 表示メニュー | ?で変数を確認、Debug.PrintでVBAから出力、式を直接実行 |
| ローカルウィンドウ | 表示メニュー → ローカルウィンドウ | 現在のプロシージャ内の全変数と値を一覧表示 |
| ウォッチウィンドウ | デバッグメニュー → ウォッチ式の追加 | 特定の式を監視し、値が変わったときにブレークする |
| Stop文 | コード内にStopと記述 | ブレークポイントと同じ効果をコードで埋め込む |
| Debug.Assert | コード内にDebug.Assert 条件式と記述 | 条件がFalseのときだけブレークする(開発環境専用。公式ドキュメントは実行ファイルへのコンパイル時に省略されると記載) |
ブレークポイントとステップ実行の使い方
ブレークポイントを設定するには、VBEのコードウィンドウで止めたい行にカーソルを置いてF9を押します。行の左端のグレーバーをクリックしても同じです。設定すると行が赤茶色でハイライトされます。同じ操作でブレークポイントを解除できます。
実行してブレークポイントに達すると、黄色いハイライトと矢印が表示されて処理が止まります。この状態(ブレークモード)でF8を押すと1行ずつ実行できます。黄色い矢印は「次に実行する行」を示します。ループが何度も回る処理や、条件分岐でどちらに入ったかを確認するのに便利です。
呼び出しているプロシージャの中まで追う必要がない場合はShift+F8を使います。呼び出し先を1塊として実行し、呼び出した行の次から再開します。
ブレークモードを解除してそのまま実行を続けるにはF5を押します。次のブレークポイントがあればそこで止まり、なければ最後まで動きます。
イミディエイトウィンドウの実践的な使い方
イミディエイトウィンドウはCtrl+Gで開きます(表示メニューからも開けます)。公式ドキュメント(Immediate window)によると、ブレークモード中の処理はその時点のスコープで実行されます。たとえばDAOレコードセット変数rsが見えているブレーク中にPrint rs.RecordCountと入力してEnterを押せば件数が確認できます。VBEではPrintを?と省略して入力する慣行もあります(? rs.RecordCount)。DAOレコードセットのRecordCountは、公式リファレンスのとおり、種類によっては全件数を得るためにMoveLastで最後のレコードまで移動しておく必要がある点に注意してください。
VBAのコード側からイミディエイトウィンドウへ出力するのがDebug.Printです。公式ドキュメント(Print method)に「Prints text in the Immediate window」と定義されています。
Sub ProcessOrders()
Dim i As Long
Dim db As DAO.Database
Set db = CurrentDb()
Dim rs As DAO.Recordset
Set rs = db.OpenRecordset("SELECT * FROM 受注テーブル WHERE 処理済 = False")
Do While Not rs.EOF
Debug.Print "処理中ID: " & rs!受注ID & " / 金額: " & rs!金額
' 処理本体
rs.MoveNext
Loop
rs.Close
Set rs = Nothing
End Sub上のコードを実行すると、ループを回るたびにイミディエイトウィンドウへ「処理中ID: 123 / 金額: 45000」のような行が追加されます。ループが想定回数を回っているか、どこで止まっているかを見るのに使えます。
注意点として、通常のAccess環境ではDebug.PrintはMsgBoxと違って実行を止めないため、残っていても処理の邪魔にはなりません(イミディエイトウィンドウが開いていない場合は出力が見えないだけです)。ただしaccdeやAccess Runtimeなどイミディエイトウィンドウを使えない配布環境では出力を確認できないため、見えない出力に依存せず、配布前に不要なDebug系の呼び出しは削除またはコメントアウトしておくのが安全です。
ローカルウィンドウとウォッチウィンドウの使い分け
ブレークモード中に全変数をまとめて確認したいときはローカルウィンドウが早いです。公式ドキュメント(Locals window)には「Automatically displays all of the declared variables in the current procedure and their values」と書かれており、プロシージャ内の変数が名前・値・型の一覧で表示されます。変数が多い場合にイミディエイトウィンドウで1つずつ?を打つより速く確認できます。
一方、「特定の変数が特定の値になったときに止めたい」という場面にはウォッチ式が便利です。公式ドキュメント(Add Watch dialog box)によると、ウォッチ式は「Break When Value Is True(式がTrueのときブレーク)」「Break When Value Changes(値が変わったときブレーク)」という条件ブレークを設定できます。ループが1,000回以上回るときや、「500件目だけおかしい」といったケースでF8を使って1行ずつ追うのは現実的ではありません。そういうときはデバッグメニューの「ウォッチ式の追加」で条件を設定します。
' ウォッチ式の設定例(デバッグメニュー → ウォッチ式の追加 で GUI から設定)
' 式: i = 500
' コンテキスト: プロシージャ名・モジュール名を指定
' ウォッチの種類: 「式がTrueのときブレーク」を選択
' → i が 500 である間、式が評価された箇所で停止する(再開後も i=500 のままなら再度停止する)Stop文とDebug.Assertの使いどころ
Stop文はコードの中に直接書くブレークポイントです。公式ドキュメント(Stop statement)には「Suspends execution...similar to setting a breakpoint in the code」と明記されています。VBEのGUIでブレークポイントを設定するのと効果は同じですが、バージョン管理やコード共有の際に「ここで止めてほしい」という意図をコードに残せる点が違います。同ドキュメントには.exeにコンパイルした場合はStop文でファイルを閉じ変数をクリアすると記載されていますが、AccessのaccdeはVBAを.exeに変換するものではなく、この記述を直接適用できません。accdeでのStop文の挙動は公式には明記されていないため、当社の方針として配布前に削除することを推奨します。
Debug.Assertは条件がFalseのときだけ止まります。公式ドキュメント(Assert method)には「Conditionally suspends execution when booleanexpression returns False」と定義されており、「Assert invocations work only within the development environment. When the module is compiled into an executable, the method calls on the Debug object are omitted」とも記されています。この記述はVBAを.exeにコンパイルした場合のものです。Accessのaccdeは、VBAソースコードを取り除いて配布するAccess固有の形式であり、.exeとは動作が異なります。本記事で確認したStop文・Assertメソッドの公式ページには、accdeでの挙動は明記されていません。そのため当社の方針として、本番配布前にDebug系の呼び出しを整理することを推奨します。
Sub UpdateStock(itemID As Long, qty As Long)
' qty が正の数であることを前提とした処理(開発時の補助チェック)
Debug.Assert qty > 0 ' qty が 0 以下ならここでブレークしてデバッグできる
' 本番用の入力検証は明示的に行うこと(例: If qty <= 0 Then Err.Raise vbObjectError + 1, , "数量が不正です")
' 予期しない実行時エラーは別途 On Error で処理する
' Debug.Assert は開発環境専用のため、本番の防衛策にはならない
' 処理本体
Dim db As DAO.Database
Set db = CurrentDb()
db.Execute "UPDATE 在庫テーブル SET 数量 = 数量 - " & qty & _
" WHERE 商品ID = " & itemID, dbFailOnError
End Sub属人的なVBAを引き継ぐときのデバッグ調査
実務でデバッグが難しいのは、自分が書いていないVBAを読むときです。前任者が残したコードはコメントが少なく、変数名がxやtmpだったり、長いプロシージャに処理が詰め込まれていたりします。
こういう状況では、まず実行経路を追うことから始めます。最初のSubやFunctionの先頭にブレークポイントを置いて実行し、F8でステップ実行しながらローカルウィンドウで変数がどう変わるかを追います。呼ばれていると思っていなかったプロシージャが呼ばれていた、という発見が多いです。
ループの中で処理される件数やキー値を確認したいときはDebug.Printが使いやすいです。MsgBoxをループ内に置くと毎回OKを押すことになるため、繰り返しが多い処理ではDebug.Printを先頭に書いてイミディエイトウィンドウで流れを確認する方が実際には速く終わります。
ただし、難読なVBAが10本・20本と積み重なっている状態では、デバッグで動作を把握するだけでも数日かかることがあります。その状態で移行や改修を進めようとすると、どこまで調査が終わったか管理できなくなります。属人化したVBAの調査や可視化についてはブラックボックス化したAccessの解析手順で詳しく整理しています。
現状のVBAがどれだけ複雑か、移行前に確認したい場合は無料の解析可否チェックから状況を整理することもできます。
なお、VBAのトランザクション処理など整合性を守る仕組みについてはAccess VBAのトランザクション処理も参考にしてください。
よくある質問
Q. ブレークポイントを設定できない行があるのはなぜですか。
変数宣言行(Dim文)やコメント行、コンパイル不可の行にはブレークポイントを設定できません。実行される処理の行(代入・関数呼び出し・条件分岐など)にだけ設定できます。Dim x As Longの次の行に処理があれば、そちらに設定してください。
Q. Debug.PrintとMsgBoxのどちらを使えばよいですか。
ループの中やイベントが多発する処理にはDebug.Printが向いています。MsgBoxは実行を止めてOKを押すまで次に進まないため、ループ内で使うと非常に手間がかかります。Debug.Printはイミディエイトウィンドウに書き出されるだけで処理は止まりません。一方、「ここで止めて確認したい」というときはMsgBoxか、ブレークポイントを使います。
Q. Stop文はaccdeにコンパイルしたファイルでも動きますか。
公式ドキュメントによると、Stop文はVBAを.exeにコンパイルした場合にはファイルを閉じ変数をクリアするとされています。ただしAccessが作るaccdeは.exeとは別形式で、この記述がaccdeに直接当てはまるかは公式に明記されていません。Debug.Assertについても、公式ドキュメントに「development environmentでのみ動作し、実行ファイルにコンパイルされると呼び出しが省略される」と記載されていますが、accdeへの適用については明記がありません。accdeでの各動作については環境での確認が必要です。当社の実務方針として、StopやDebug系の呼び出しは配布前に取り除くことを推奨します。
Q. ウォッチ式で「式がTrueのときブレーク」を設定したのに止まりません。
ウォッチ式のコンテキスト(プロシージャ名・モジュール名)が正しく指定されていない場合、公式ドキュメントにある通り「コンテキストがスコープに入っていないと現在の値が表示されない」状態になります。ウォッチ式の追加ダイアログで、対象のモジュール名とプロシージャ名を正しく選択しているか確認してください。また、変数名のタイプミスも原因になります。
触れないAccessが「診断できるか」だけ、確かめませんか。
顧客データは送信不要。発注の義務もありません。
約2分・ファイル送信不要・発注義務なし/説明はオンライン・売り込みはしません