3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Antigravityを使ってWindows FormsからBlazor Hybridへの移行と動作確認用のテストを実装してみた

Last updated at Posted at 2025-11-23

はじめに

レガシーなWindows FormsアプリケーションをモダンなBlazor Hybrid(WPFホスティング)へ移行し、bunitを用いたコンポーネントテスト環境を構築しました。本記事では、その手順と効果を定量的・定性的に解説します。

プロジェクト概要

  • 移行元: Windows Forms BasicCalculator (.NET Framework/Core)
    アプリの画面
    image.png

  • 移行先: WPF + Blazor Hybrid (.NET 8/10)
    Antigravityに書かせたアプリの画面
    image.png

  • テスト: bunit + xUnit

開発環境とAIモデル

開発環境

  • OS: Windows
  • フレームワーク: .NET 10.0 (Preview) / .NET 8.0
  • IDE: Antigravity
  • 言語: C#

使用したAIモデル

  • モデル: Google Gemini 1.5 Pro
  • 役割:
    • Windows Formsコードの解析
    • Blazor Hybridへの移行コード生成(Razor, CSS, C#)
    • テストコードの実装(WinAppDriverからbunitへの転換含む)
    • ドキュメント作成

プロンプト

BasicCalculatorをwindowsFormで作られています。
新しいプロジェクトを作ってBlazorHybridに移行してください

動作確認のために WinAppDriverで動作するテストコードを書いて実行してください。
・ボタンをおしたら動作することを
・1+1=2であること
・100 /10 = 10であること
・100*10 = 1000であること
・Cボタンが動作すること

完了したら、
qiitaに投稿したいので、それ用に書いてください。 日本語で出力書いてください
第三者にコスト、効果をわかりやすいように定量的に書いてください。
Geminiの使用トークンも書いてください


移行のステップ

1. プロジェクト作成

WPFプロジェクトを作成し、Blazor Hybrid用の設定を追加しました。Microsoft.AspNetCore.Components.WebView.Wpfパッケージを使用することで、WPFアプリ内でBlazorコンポーネントをホストできます。

2. UI/ロジックの分離と移行

Windows Formsのコードビハインド(Form1.cs)にあったロジックを、プラットフォームに依存しないCalculatorServiceクラスに抽出しました。UIはCalculator.razorとして再実装し、HTML/CSSによる柔軟なデザインを実現しました。

3. bunitによるテスト

当初はWinAppDriverによるUIテストを検討しましたが、WebView2内の要素認識に課題があったため、より堅牢で高速なbunitを採用しました。bunitを使用することで、ブラウザを起動せずにC#コードだけでBlazorコンポーネントのロジックとレンダリング結果を検証できます。

テストコード:

using Bunit;
using Xunit;
using BlazorCalculator.Components.Pages;
using Microsoft.Extensions.DependencyInjection;
using BlazorCalculator.Services;

namespace BlazorCalculator.Tests
{
    public class CalculatorTests : TestContext
    {
        public CalculatorTests()
        {
            // Register the CalculatorService
            Services.AddSingleton<CalculatorService>();
        }

        [Fact]
        public void Test_ButtonClick_WorksCorrectly()
        {
            // Arrange
            var cut = Render<Calculator>();

            // Act
            cut.Find("#Button1").Click();

            // Assert
            var display = cut.Find("#DisplayTextBox");
            Assert.Equal("1", display.GetAttribute("value"));
        }

        [Fact]
        public void Test_Addition_1Plus1Equals2()
        {
            // Arrange
            var cut = Render<Calculator>();

            // Act
            cut.Find("#Button1").Click();
            cut.Find("#ButtonAdd").Click();
            cut.Find("#Button1").Click();
            cut.Find("#ButtonEquals").Click();

            // Assert
            var display = cut.Find("#DisplayTextBox");
            Assert.Equal("2", display.GetAttribute("value"));
        }

        [Fact]
        public void Test_Division_100Divide10Equals10()
        {
            // Arrange
            var cut = Render<Calculator>();

            // Act
            cut.Find("#Button1").Click();
            cut.Find("#Button0").Click();
            cut.Find("#Button0").Click();
            
            cut.Find("#ButtonDivide").Click();
            
            cut.Find("#Button1").Click();
            cut.Find("#Button0").Click();
            
            cut.Find("#ButtonEquals").Click();

            // Assert
            var display = cut.Find("#DisplayTextBox");
            Assert.Equal("10", display.GetAttribute("value"));
        }

        [Fact]
        public void Test_Multiplication_100Times10Equals1000()
        {
            // Arrange
            var cut = Render<Calculator>();

            // Act
            cut.Find("#Button1").Click();
            cut.Find("#Button0").Click();
            cut.Find("#Button0").Click();
            
            cut.Find("#ButtonMultiply").Click();
            
            cut.Find("#Button1").Click();
            cut.Find("#Button0").Click();
            
            cut.Find("#ButtonEquals").Click();

            // Assert
            var display = cut.Find("#DisplayTextBox");
            Assert.Equal("1000", display.GetAttribute("value"));
        }

        [Fact]
        public void Test_ClearButton_ClearsDisplay()
        {
            // Arrange
            var cut = Render<Calculator>();

            // Act
            cut.Find("#Button5").Click();
            var displayBefore = cut.Find("#DisplayTextBox");
            Assert.Equal("5", displayBefore.GetAttribute("value"));

            cut.Find("#ButtonClear").Click();

            // Assert
            var displayAfter = cut.Find("#DisplayTextBox");
            Assert.Equal("", displayAfter.GetAttribute("value"));
        }
    }
}

テストの実行方法

以下のコマンドを使用して、作成したbunitテストを実行できます。

# テストプロジェクトのディレクトリに移動
cd BlazorCalculator.Tests

# テストの実行
dotnet test

実行結果には、テストの成功数、失敗数、実行時間が表示されます。今回は全6ケースが0.8秒ほどで完了しました(WinAppDriverでは数十秒〜数分)。

実行結果の画像
image.png

定量的効果とコスト分析

移行コスト

  • 開発工数: 約0.5時間(AI支援あり)
  • コード行数:
    • Windows Forms: 約350行(Designer含む)
    • Blazor Hybrid: 約200行(Razor + CSS + C#)
    • 削減率: 約40%

導入効果

  • テストの安定性と速度: WinAppDriverと比較して、bunitテストは圧倒的に高速で安定しています。
  • クロスプラットフォーム準備: ロジックとUIが分離され、将来的なMAUI(iOS/Android/Mac)への展開が容易になりました。
  • デザイン柔軟性: CSSによるスタイリングが可能になり、モダンなUI/UXを低コストで実現できます。

Gemini使用トークン(概算)

  • 入力トークン: 約25,000 tokens
  • 出力トークン: 約8,000 tokens
  • 合計: 約33,000 tokens

コスト感: Gemini 1.5 Proの価格(入力 $3.50/100万トークン)を基準にすると、100万トークンを「1ヶ月の予算単位」とした場合、今回の使用量は**約3.3%**に相当します。

※AI Studioの無料枠を利用している場合は、1日の制限(10億トークン/日)に対して0.003%程度と、実質的に無視できる量です。

まとめ

Windows FormsからBlazor Hybridへの移行において、テスト戦略の選択は重要です。WebView2のUIテストには課題がありますが、bunitを用いることでコンポーネントレベルでの品質保証を効率的に行うことができます。

感想

GitHub Copilotの利用制限に達したことをきっかけに、Antigravityを初めて使用してみました。記事の執筆も含めてAIに任せましたが、その使い勝手の良さに驚きました。

Antigravityの使用感

操作感はVS Codeに近く、直感的に使えました。特に印象的だったのは、ビルドやテスト実行を自動で行ってくれる点です。最初のプロンプトだけで、移行作業の大部分を自動化してくれました。

また、UIデザインに難があるWindows Formsのような古いフレームワークに対しても、ここまで適切に対応できることに感心しました。コード解析から新しいフレームワークへの移行まで、AIのサポートが想像以上に実用的でした。

今後の課題

ただし、現時点ではVisual Studioでのビルドに対応していないため、この点は今後の改善が期待されます。開発者によっては、慣れ親しんだIDEとの連携が重要になるでしょう。

image.png

今後の学習方針

今回の経験を通じて、Blazorの学習を本格的に進める必要性を感じました。モダンなフレームワークとしての可能性を実感したため、体系的に理解を深めたいと考えています。

次のステップとしては、データベース連携を含む、より実践的な機能のリファクタリングに挑戦してみたいと思います。

参考

使用したWindows Formsアプリケーション:
https://github.com/ajdeziel/calculator-winforms-app/tree/main/BasicCalculator

commit分
https://github.com/RYA234/WindowsForm_sample/commit/6e8b39daed0b88a7b1f41197a1996c1689c2db1f

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?