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

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

「動けばいい」という甘えを捨てろ:VB.NET/WPFにおける、堅牢な自動テスト基盤の構築術

業務自動化ツールの開発において、最も恐ろしいのは「バグ」ではない。「修正したはずの箇所が、別の場所を壊す」という、制御不能な連鎖反応だ。

WPF(Windows Presentation Foundation)を用いたモダンなUIアプリケーションを構築する際、多くの開発者が「見た目」の美しさに目を奪われ、その背後にある「品質担保の仕組み」を軽視する。その結果、リリース直前に予期せぬ例外が飛び出し、深夜のデバッグ作業に追われることになる。

本稿では、単なるテストコードの書き方などという低次元な話はしない。「テストができる設計(Testable Design)」とは何か、そして、ViewModelのロジックからUIの挙動に至るまでを、どのように自動化の網にかけ、堅牢なプロダクトへと昇華させるべきか。その極限の知見を伝授する。

1. 設計思想:テスト不能なコードは「負債」である

まず、残酷な真実を突きつけよう。ViewModelの中に `New DatabaseService()` や `File.ReadAllText()` といった記述が直接書かれている時点で、そのコードはテスト不可能(Untestable)だ。

テストとは、対象のコンポーネントを「隔離」して検証することである。外部のリソース(DB、ファイル、ネットワーク)に依存しているコードは、テストのたびに環境を汚染し、結果の再現性を失わせる。

解決策:依存性の注入(Dependency Injection: DI)

テストを可能にする唯一の道は、「インターフェースへの依存」である。具象クラスではなく、抽象(Interface)を介してやり取りする。これにより、テスト時には「本物のDB」の代わりに「メモリ上で動く偽物(Mock)」を注入できるようになる。

2. ViewModelの単体テスト:ロジックの純粋性を守る

WPFにおけるメインの戦場はViewModelだ。ここにはビジネスロジックが凝縮されている。
ここでは、`MSTest` と、依存関係をシミュレートするための手法を用いた、プロダクトレベルのコード例を示す。

【設計例】データ保存機能を持つViewModel

まず、依存するサービスをインターフェースとして定義する。

' --- 抽象化されたインターフェース ---
Public Interface IDataRepository
    Function SaveData(content As String) As Boolean
End Interface

' --- ViewModel ---
Public Class MainViewModel
    Implements INotifyPropertyChanged

    Private ReadOnly _repository As IDataRepository
    Public Property StatusMessage As String

    ' コンストラクタで依存関係を受け取る(DIの核心)
    Public Sub New(repository As IDataRepository)
        _repository = repository
    End Sub

    ' 保存実行コマンド(簡易化のためメソッドとして記述)
    Public Async Function ExecuteSaveCommand(input As String) As Task
        If String.IsNullOrWhiteSpace(input) Then
            StatusMessage = "エラー: 入力が空です"
            Return
        End If

        ' 非同期でリポジトリを呼び出し
        Dim success As Boolean = Await Task.Run(Function() _repository.SaveData(input))

        If success Then
            StatusMessage = "保存成功"
        Else
            StatusMessage = "保存失敗"
        End If
    End Function

    ' INotifyPropertyChangedの実装は省略(実務では必須)
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
End Class

【テスト例】Mockを用いた単体テスト

次に、テストコードだ。ここでは本物のDBは使わず、`IDataRepository` を模倣した「Mock」を使用する。

<TestClass>
Public Class MainViewModelTests
    ' 簡易的なMockクラス(Moq等のライブラリを使うのが一般的だが、概念を示すため自作)
    Private Class MockRepository
        Implements IDataRepository
        Public Property ShouldSucceed As Boolean = True
        Public Property LastSavedContent As String

        Public Function SaveData(content As String) As Boolean Implements IDataRepository.SaveData
            LastSavedContent = content
            Return ShouldSucceed
        End Function
    End Class

    <TestMethod>
    Public Async Function ExecuteSaveCommand_ValidInput_SetsSuccessMessage() As Task
        ' 1. Arrange (準備)
        Dim mockRepo As New MockRepository()
        Dim viewModel As New MainViewModel(mockRepo)
        Dim testInput As String = "Hello, Architect!"

        ' 2. Act (実行)
        Await viewModel.ExecuteSaveCommand(testInput)

        ' 3. Assert (検証)
        Assert.AreEqual("保存成功", viewModel.StatusMessage)
        Assert.AreEqual(testInput, mockRepo.LastSavedContent)
    End Function

    <TestMethod>
    Public Async Function ExecuteSaveCommand_EmptyInput_SetsErrorMessage() As Task
        ' 1. Arrange
        Dim mockRepo As New MockRepository()
        Dim viewModel As New MainViewModel(mockRepo)

        ' 2. Act
        Await viewModel.ExecuteSaveCommand("")

        ' 3. Assert
        Assert.AreEqual("エラー: 入力が空です", viewModel.StatusMessage)
    End Sub
End Class

3. UIテストの極意:FlaUIによる「人間らしい」操作の自動化

ViewModelが正しくても、XAMLの `Binding` が壊れていたり、ボタンの `Command` が紐付いていなければ、アプリケーションは機能しない。これを防ぐのがUIテストだ。

WPFのUIテストにおいて、最も避けるべきは「座標指定」である。コントロールの配置が変わった瞬間にテストが壊れる、脆弱なテストはゴミだ。`AutomationId` を活用せよ。

【XAML】AutomationIdを付与する

<!-- View.xaml -->
<StackPanel>
    <TextBox x:Name="InputBox" AutomationId="InputTextBox" />
    <Button x:Name="SaveButton" 
            Content="保存" 
            AutomationId="SaveButton" 
            Command="{Binding SaveCommand}" />
    <TextBlock x:Name="StatusLabel" 
               AutomationId="StatusLabel" 
               Text="{Binding StatusMessage}" />
</StackPanel>

【テスト例】FlaUIによるエンドツーエンド(E2E)テスト

`FlaUI` を使用して、実際にアプリケーションを起動し、ボタンをクリックする一連の流れを自動化する。

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

<TestClass>
Public Class UiAutomationTests
    Private _app As Application
    Private _window As Window

    <TestInitialize>
    Public Sub Setup()
        ' アプリケーションの起動(実行ファイルのパスを指定)
        _app = Application.Launch("YourWpfApp.exe")
        Dim automation = new UIA3Automation()
        _window = _app.GetMainWindow(automation)
    End Sub

    <TestCleanup>
    Public Sub Teardown()
        _app.Close()
    End Sub

    <TestMethod>
    Public Sub FullFlow_InputAndSave_ShowsSuccessMessage()
        ' 1. InputTextBox を探してテキストを入力
        Dim inputField = _window.FindFirstDescendant(Function(x) x.AutomationId = "InputTextBox").AsTextBox()
        inputField.Text = "Automated Test Content"

        ' 2. SaveButton をクリック
        Dim saveButton = _window.FindFirstDescendant(Function(x) x.AutomationId = "SaveButton").AsButton()
        saveButton.Click()

        ' 3. StatusLabel のテキストを確認
        Dim statusLabel = _window.FindFirstDescendant(Function(x) x.AutomationId = "StatusLabel").AsLabel()
        
        ' UIの更新待ちを考慮し、Retryロジックを挟むのがプロの作法
        Assert.AreEqual("保存成功", statusLabel.Text)
    End Sub
End Class

4. 現場で陥る「地雷」を回避するための鉄則

設計と実装ができても、運用で躓く者が後を絶たない。以下の3点は、血肉として刻んでおけ。

① ファイル・DB連携の「副作用」を制御せよ

テスト中に `C:\Data\config.xml` を直接書き換えるようなコードを書いてはならない。テストが失敗した際、環境が汚染されて次のテストが失敗する「汚染伝播」が起きる。

  • 対策: テスト用のディレクトリを `Path.GetTempPath()` で生成するか、常にインターフェース経由で「メモリ内ストレージ」へ逃がす設計にせよ。

② 非同期処理の「待ち」を制御せよ

WPFはマルチスレッドだ。`Task.Delay` で適当に待機(Sleep)するコードは、CI(継続的インテグレーション)環境では「タイミング依存の不安定なテスト(Flaky Test)」を生む最大の原因となる。

  • 対策: UIテストでは、特定の要素が現れるまで待機する `Wait.Until()` 系のアプローチを徹底せよ。

③ AutomationId は「契約」である

`AutomationId` を付けることは、UIデザイナー(あるいは自分自身)に対する「ここを変えてはいけない」という契約だ。これを適当に扱う者は、テスト自動化の恩恵を一生受けられない。

結論:自動化は「未来の自分」への投資である

「テストを書く時間がもったいない」と言う者がいる。だが、バグを修正するために費やす、あの忌々しい数時間を考えてみろ。

設計を疎結合にし、インターフェースを武器にロジックを隔離し、AutomationIdによってUIを定義する。

このプロセスを経て構築されたテスト基盤は、単なる確認作業ではない。君が自信を持って「リリース」ボタンを押すための、最強の盾となるのだ。

さあ、コードを書き直せ。テストができる設計に、今すぐに。

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