【実務・中級編】VB.NETでの単体テスト自動化:NUnit/xUnitとMoqを用いたモックオブジェクトの活用法 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

外部依存を断つ!VB.NETにおけるNUnitとMoqを用いた「真の単体テスト」極限実践ガイド

業務システムの自動化ツールやデスクトップアプリケーションをVB.NETで開発する現場において、いまだに根強く残る悪習がある。それは、「データベースや外部APIに接続しないとテストができない」という設計、そして「開発者の手動によるポチポチ確認」という名の非効率な検証作業だ。

ネットワークが切断されたCI/CD環境でビルドが落ちる。テストデータの汚染によって前日まで通っていたテストが突如失敗する。これらはコードのバグではなく、「外部リソースに密結合した設計の怠慢」に他ならない。

今回は、世界最高峰の自動化アーキテクトである私が、VB.NET(.NET Core / .NET 6以降のモダンな環境)において、NUnitMoq を駆使して外部依存を完全にモック(擬似化)し、いかなる環境でも一瞬で高精度に実行できる堅牢な単体テストの構築手法を伝授する。

1. なぜ「生のデータベース・API接続」は悪なのか?

業務ロジックの中に `SqlConnection` の生成や、直接的な `HttpClient` による外部API呼び出しをベタ書きしているコードをよく見かける。

.net
‘ 【アンチパターン】これでは単体テストが書けない
Public Class OrderProcessor
Public Function ProcessOrder(orderId As Integer) As Boolean
‘ DBに直接接続する処理(テスト時にDBサーバーが必須になる)
Using conn As New SqlConnection(“…”)
‘ … 処理 …
End Using
Return True
End Function
End Class

このアプローチの何が問題か。
1. 実行速度の劣化: DBやネットワークI/Oはメモリ上の処理に比べて数万倍遅い。テストスイート全体で数分の足止めを食らう。
2. 環境依存性: テスト実行時にDBの起動状態やネットワーク接続に左右され、自動化(CI/CD)の信頼性が崩壊する。
3. テストシナリオの限界: 「APIがタイムアウトした時の挙動」「DBがデッドロックを起こした時の例外処理」などを意図的に再現することが極めて困難。

これを解決唯一の解法が、「インターフェースを通した依存性の分離(DI)」「Moqによる振る舞いの制御」である。

2. 実践:依存性を排除した堅牢な設計とプロダクションコード

まずは、テスト可能な設計へのリファクタリングを行う。外部リソース(DBやAPI)アクセスを抽象化するインターフェースを定義し、それをビジネスロジックに注入(Inject)する。

以下のサンプルは、売上データを取得して外部APIへ送信し、その結果をDBに保存する処理を想定したモジュール群である。

① 抽象化インターフェースとデータ構造

.net
Namespace BusinessLogic
‘ 売上データモデル
Public Class OrderData
Public Property OrderId As String
Public Property Amount As Decimal
Public Property IsProcessed As Boolean
End Class

‘ 外部APIの抽象
Public Interface IExternalApiService
Function SendData(data As OrderData) As Boolean
End Interface

‘ データベースアクセスの抽象
Public Interface IOrderRepository
Function GetOrder(orderId As String) As OrderData
Sub SaveOrder(data As OrderData)
End Interface
End Namespace

② ビジネスロジック本体(プロダクションコード)

ここにテスト対象のロジックを実装する。コンストラクタインジェクションを使用し、具体的なDBやAPIのクラスを一切知覚させない。

.net
Imports BusinessLogic

Namespace BusinessLogic
Public Class OrderProcessor
Private ReadOnly _apiService As IExternalApiService
Private ReadOnly _repository As IOrderRepository

‘ 依存性を外部から注入する(DI)
Public Sub New(apiService As IExternalApiService, repository As IOrderRepository)
_apiService = apiService
_repository = repository
End Sub

‘ 処理実行メソッド
Public Function ProcessAndSyncOrder(orderId As String) As Boolean
‘ 1. DBからオーダー取得
Dim order = _repository.GetOrder(orderId)
If order Is Nothing OrElse order.IsProcessed Then
Return False
End If

‘ 2. 外部APIへ送信
Dim apiSuccess As Boolean = _apiService.SendData(order)

‘ 3. 送信成功ならステータスを更新して保存
If apiSuccess Then
order.IsProcessed = True
_repository.SaveOrder(order)
Return True
End If

Return False
End Function
End Class
End Namespace

3. MoqとNUnitを用いた極限テストコードの構築

ここからが本題だ。NUnitをテストフレームワークとして使用し、Moqを使って `IExternalApiService` と `IOrderRepository` の「モック(偽物)」をメモリ上に生成する。実際のDBやネットワークには1バイトもアクセスしない。

プロジェクトに NuGet経由で以下のパッケージを導入しておくこと。

  • `NUnit`
  • `NUnit3TestAdapter`
  • `Moq`
  • `Microsoft.NET.Test.Sdk`

完全なテストクラスの実装例(VB.NET)

.net
Imports NUnit.Framework
Imports Moq
Imports BusinessLogic


Public Class OrderProcessorTests

Private _mockApiService As Mock(Of IExternalApiService)
Private _mockRepository As Mock(Of IOrderRepository)
Private _processor As OrderProcessor

‘ 各テストケース実行前に初期化

Public Sub Setup()
_mockApiService = New Mock(Of IExternalApiService)()
_mockRepository = New Mock(Of IOrderRepository)()

‘ モック化された依存性を注入して対象クラスをインスタンス化
_processor = New OrderProcessor(_mockApiService.Object, _mockRepository.Object)
End Sub


Public Sub ProcessAndSyncOrder_WhenOrderIsValidAndApiSucceeds_ShouldReturnTrueAndSave()
‘ — 1. 準備 (Arrange) —
Dim testOrderId = “ORD-001”
Dim mockOrder As New OrderData With {
.OrderId = testOrderId,
.Amount = 15000D,
.IsProcessed = False
}

‘ リポジトリが呼ばれたらモックデータを返すよう設定
_mockRepository.Setup(Function(r) r.GetOrder(testOrderId)).Returns(mockOrder)

‘ APIサービスがどんなデータで呼ばれても True (成功) を返すよう設定
_mockApiService.Setup(Function(a) a.SendData(It.IsAny(Of OrderData)())).Returns(True)

‘ — 2. 実行 (Act) —
Dim result As Boolean = _processor.ProcessAndSyncOrder(testOrderId)

‘ — 3. 検証 (Assert) —
Assert.IsTrue(result, “処理が成功しTrueを返すべきです。”)
Assert.IsTrue(mockOrder.IsProcessed, “オーダーの処理済みフラグがTrueに更新されるべきです。”)

‘ リポジトリの SaveOrder が正確に1回呼び出されたことを厳密に検証(Verifiable)
_mockRepository.Verify(Sub(r) r.SaveOrder(mockOrder), Times.Once())
End Sub


Public Sub ProcessAndSyncOrder_WhenApiFails_ShouldNotSaveOrder()
‘ — 1. 準備 (Arrange) —
Dim testOrderId = “ORD-002”
Dim mockOrder As New OrderData With {
.OrderId = testOrderId,
.Amount = 5000D,
.IsProcessed = False
}

_mockRepository.Setup(Function(r) r.GetOrder(testOrderId)).Returns(mockOrder)

‘ 意図的に外部APIが失敗(Falseを返す)するシナリオを構築
_mockApiService.Setup(Function(a) a.SendData(It.IsAny(Of OrderData)())).Returns(False)

‘ — 2. 実行 (Act) —
Dim result As Boolean = _processor.ProcessAndSyncOrder(testOrderId)

‘ — 3. 検証 (Assert) —
Assert.IsFalse(result, “APIが失敗した場合はFalseを返すべきです。”)

‘ APIが失敗した場合、DBの SaveOrder が「一度も呼ばれていないこと」を検証
_mockRepository.Verify(Sub(r) r.SaveOrder(It.IsAny(Of OrderData)()), Times.Never())
End Sub

End Class

4. アーキテクトからの実践的なアドバイス:保守性を高める極意

1. VB.NET特有のラムダ式の構文に注意する
C#に比べてVB.NETのラムダ式(`Function(…)` や `Sub(…)`)は記述がやや冗長になりがましい。特に `It.IsAny(Of T)()` を使う際の型推論の挙動には注意し、必要に応じて明示的に型を記述すること。
2. 「振る舞いの検証」と「状態の検証」を使い分ける
今回のテストコードでも示した通り、結果の戻り値やオブジェクトの状態を見るだけでなく、「特定のメソッドが想定通りの回数呼び出されたか(`Times.Once()`, `Times.Never()`)」を `Verify` で検証することが、モックを使ったテストの本質である。
3. CI/CDパイプラインへの組み込み
このテストコード群は、外連味のない純粋なメモリ上での実行となるため、GitHub ActionsやAzure DevOpsなどのパイプラインにおいて、わずか数秒で全テストが完了する。ビルドサーバーにSQL Serverや外部APIのモックサーバーを立てる必要は一切ない。

結び

「VB.NETだからテスト自動化が難しい」というのは、単なる設計の逃げ口上にすぎない。
適切にインターフェースを切り出し、Moqによって外部環境のノイズを完全に遮断すれば、VB.NETによるエンタープライズアプリケーションであっても、極めてモダンで堅牢なテスト駆動開発(TDD)に近い品質管理体制を構築できる。

手動テストによる疲弊からエンジニアを解放し、真に価値のあるビジネスロジックの構築に集中するためのコードを、今日からあなたのプロジェクトへ導入してほしい。

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