【入門編】【上級者向け】VB.NETによるWPFアプリケーションの単体テスト(Unit Testing)自動化とUIテストの極意 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク
スポンサーリンク

脱・マクロの壁へ。VB.NETによるWPFアプリケーションの「品質」を自動で守る極意

「昨日まで動いていたプログラムが、少しコードを書き換えただけで動かなくなった……」
「UIのボタン配置を変えたら、計算ロジックまでどこかおかしくなった気がする……」

もしあなたが、Excelマクロのような「動けばいい」というフェーズから、本格的な業務アプリケーション開発へと足を踏み入れようとしているなら、一度立ち止まってこの記事を読んでみてください。

こんにちは。今日は、VB.NETを用いたモダンなデスクトップ開発、特にWPF (Windows Presentation Foundation) において、エンジニアがもっとも恐れる「デグレード(先祖返り・機能退行)」を防ぐための自動テストの仕組みについてお話しします。

単に「テストコードを書く方法」を教えるのではありません。なぜその構造が必要なのか、どうすれば「壊れにくい、メンテナンスしやすい」設計にできるのか。その本質的な設計思想を伝授します。ここをクリアすれば、あなたは単なる「コードを書く人」から、真の「ソフトウェアエンジニア」へと進化できるはずですよ。

1. なぜWPFでは「テスト」が難しいのか?

WPFは非常に強力なフレームワークです。XAML(ザムル)を使って美しいUIを作り、データバインディングによって画面とロジックを繋ぎます。しかし、この「繋がっている」ことが、テストにおいては最大の敵になります。

従来のWindows Formsなどでは、ボタンをクリックした時の処理がすべて「画面(Code Behind)」に書かれがちでした。しかし、それだと「画面が表示されないと、ロジックが正しいか確認できない」という事態に陥ります。

これを解決する魔法の設計パターンが MVVM (Model-View-ViewModel) です。

MVVMの構造を理解する

テストの自動化を成功させる鍵は、「画面(View)を介さずに、脳(ViewModel)だけをテストする」ことにあります。

  • Model: データそのもの(データベースや計算ロジック)。
  • View: XAMLで書かれた見た目(ボタン、テキストボックス)。
  • ViewModel: ViewとModelの仲介役。ここがテストの主戦場です。

2. 第一の守護神:ViewModelの単体テスト (Unit Testing)

まずは、画面を一切起動せず、メモリ上だけでロジックの正しさを検証する「単体テスト」を構築しましょう。ここでは、Visual Studio標準の `MSTest` を使用する例を紹介します。

実装例:挨拶をするだけのシンプルなViewModel

まずは、テスト対象となるViewModelを作成します。

Imports System.ComponentModel

''' <summary>
''' ユーザーの挨拶を管理するViewModel
''' </summary>
Public Class MainViewModel
    Implements INotifyPropertyChanged

    Private _userName As String = ""
    Private _greetingMessage As String = ""

    ' プロパティの変更を通知するためのイベント
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    ''' <summary>
    ''' ユーザー名
    ''' </summary>
    Public Property UserName As String
         eyes Get
            Return _userName
        End Get
        Set(value As String)
            If _userName <> value Then
                _userName = value
                OnPropertyChanged(NameOf(UserName))
            End If
        End Set
    End Property

    ''' <summary>
    ''' 表示される挨拶メッセージ
    ''' </summary>
    Public Property GreetingMessage As String
         eyes Get
            Return _greetingMessage
        End Get
        Set(value As String)
            If _greetingMessage <> value Then
                _greetingMessage = value
                OnPropertyChanged(NameOf(GreetingMessage))
            End If
        End Set
    End Property

    ''' <summary>
    ''' 挨拶を生成するコマンド(ロジック)
    ''' </summary>
    Public Sub ExecuteGreet()
        If String.IsNullOrWhiteSpace(UserName) Then
            GreetingMessage = "名前を入力してください。"
        Else
            GreetingMessage = $"こんにちは、{UserName}さん!"
        End If
    End Sub

    ''' <summary>
    ''' プロパティ変更通知の補助メソッド
    ''' </summary>
    Protected Sub OnPropertyChanged(propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub
End Class

テストコード:ロジックが正しいか検証する

次に、このViewModelが期待通りに動くかをテストします。

Imports Microsoft.VisualStudio.TestTools.UnitTesting

<TestClass>
Public Class MainViewModelTests

    <TestMethod>
    Public Sub ExecuteGreet_ValidName_SetsCorrectMessage()
        ' 1. Arrange (準備): テストしたい状態を作る
        Dim viewModel As New MainViewModel()
        viewModel.UserName = "田中"

        ' 2. Act (実行): テストしたい動作をさせる
        viewModel.ExecuteGreet()

        ' 3. Assert (検証): 結果が期待通りか確認する
        ' 「こんにちは、田中さん!」という文字列になっていることを期待
        Assert.AreEqual("こんにちは、田中さん!", viewModel.GreetingMessage)
    End Sub

    <TestMethod>
    Public Sub ExecuteGreet_EmptyName_SetsErrorMessage()
        ' 準備
        Dim viewModel As New MainViewModel()
        viewModel.UserName = ""

        ' 実行
        viewModel.ExecuteGreet()

        ' 検証: エラーメッセージが出ることを確認
        Assert.AreEqual("名前を入力してください。", viewModel.GreetingMessage)
    End Sub
End Class

💡 ここがポイント!
このテストは、GUIを一切起動していません。 だから、実行速度が爆速で、CI(継続的インテグレーション)環境でも数千個のテストを数秒で回せます。

3. 第二の守護神:FlaUIによるUI自動テスト (UI Testing)

ViewModelが完璧でも、「ボタンが画面に配置されていない」「ボタンが他の要素に隠れて押せない」といった問題が起これば、ユーザーはアプリケーションを使えません。

そこで登場するのが FlaUI です。これは、人間がマウスやキーボードで操作する様子を、プログラムに模倣させるフレームワークです。

UIテストの極意:`AutomationId` を使い倒せ

UIテストを書くとき、初心者がやりがちな失敗は「ボタンのテキスト(例:’送信’)」で要素を探すことです。これでは、ボタンの名前を「送信する」に変えただけでテストが壊れてしまいます。

プロは、XAMLで `AutomationId` を指定します。これは、見た目に関わらず「プログラムから特定するためのID」です。

# XAML側の準備

<!-- View.xaml -->
<StackPanel>
    <TextBox x:Name="UserNameInput" AutomationId="UserNameTextBox" />
    <Button x:Name="GreetButton" Content="挨拶する" AutomationId="GreetButton" Command="{Binding GreetCommand}" />
    <TextBlock x:Name="MessageDisplay" AutomationId="MessageTextBlock" Text="{Binding GreetingMessage}" />
</StackPanel>

FlaUIによるUI操作テストの実装例

Imports FlaUI.Core
Imports FlaUI.UIA3
Imports Microsoft.VisualStudio.TestTools.UnitTesting

<TestClass>
Public Class UiAutomationTests

    <TestMethod>
    Public Sub UserCanClickGreetButton_AndSeeMessage()
        ' 1. アプリケーションの起動
        ' ※ 実際の実行ファイルのパスを指定してください
        Dim app = FlaUI.Core.Application.Launch("YourWpfApp.exe")

        Using automation As New UIA3Automation()
            ' メインウィンドウを取得
            Dim window = app.GetMainWindow(automation)

            ' 2. 要素の特定 (AutomationIdを使用するのがプロの鉄則!)
            Dim nameInput = window.FindFirstDescendant(Function(f) f.AsTextBox().AutomationId = "UserNameTextBox")
            Dim greetButton = window.FindFirstDescendant(Function(f) f.AsButton().AutomationId = "GreetButton")
            Dim messageLabel = window.FindFirstDescendant(Function(f) f.AsLabel().AutomationId = "MessageTextBlock")

            ' 3. 操作 (ユーザーの動きをシミュレート)
            nameInput.AsTextBox().Enter("エンジニア太郎")
            greetButton.AsButton().Click()

            ' 4. 検証 (画面上の文字が変わったか)
            Assert.AreEqual("こんにちは、エンジニア太郎さん!", messageLabel.AsLabel().Text)
        End Using

        ' アプリを終了
        app.Close()
    End Sub
End Class

4. 陥りやすいエラーと、エンジニアとしての心得

ここまで学んだあなたに、私が現場で見てきた「落とし穴」を伝えておきます。

① UIテストは「重い」ことを忘れない

単体テスト(ViewModel)は数ミリ秒で終わりますが、UIテストはアプリの起動待ちやアニメーション待ちが発生するため、非常に時間がかかります。

  • 戦略: ロジックの検証はすべて単体テストで行い、UIテストは「主要な導線(ログインができるか、メイン機能が動くか)」に絞って、最小限に留めるのが、スマートな設計です。

② 非同期処理(Async/Await)の罠

WPFでAPI通信などを行う場合、`Async/Await` を多用します。テストコード内で、非同期処理が終わる前に `Assert`(検証)が走ってしまうと、テストは必ず失敗します。

  • 対策: テストメソッド自体を `Async Function` にし、`Await` を適切に使って、処理の完了を待ってから検証するようにしましょう。

③ 依存関係を注入する (Dependency Injection)

ViewModelの中で直接 `New DatabaseConnection()` と書いてしまうと、テスト時に本物のデータベースに接続しに行ってしまいます。

  • 対策: インターフェース(`IDatabase` など)を使い、テストの時だけ「偽物のデータ(Mock)」を渡せるように設計してください。

まとめ:品質は「後付け」できない

「テストなんて、完成してから書けばいい」
もしそう思っていたとしたら、それは非常に危険な考え方です。

テストコードは、設計の良し悪しを測る物差しです。
もしテストが書きにくいと感じたら、それはあなたのコードが「密結合(依存しすぎている)」であるという、設計からの警告なのです。

1. MVVMでロジックを画面から切り離す。
2. ViewModelに対して、高速な単体テストを走らせる。
3. 重要なUI操作だけ、FlaUIでAutomationIdを使ってテストする。

このサイクルを回せるようになれば、あなたはどんなに大規模なシステムでも、自信を持って変更を加えることができる「伝説のエンジニア」への道を歩み始めています。

さあ、次はあなたのコードを、テストという名の盾で守ってあげてください。応援していますよ!

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