背景と課題
現在、Windows Formsの保守開発を担当していますが、小さな変更でも予期せぬ影響範囲が大きく、作業に多くの時間を要している状況です。
「レガシーコード改善ガイド」を参考に、テストコードの導入による品質向上と開発効率化を検討し、パイロットプロジェクトとして実装してみましたので、その知見を共有します。
開発環境の制約
長期運用されているシステム特有の課題として、以下のような古い開発環境での動作が必須条件となっています:
OS: Windows 2000、Windows 7など
IDE: Visual Studio 2008~2010など
.NET Framework: 比較的古いバージョン
この制約により、以下の最新ツールは採用できませんでした:
WinAppDriver: Windows 10以降が必要
Playwright: .NET Core/.NET 5+が必要
TestContainers: 環境構築の複雑さと依存関係の問題
TestStack.White採用の理由
古い.NET Frameworkバージョン(.NET 3.5以降)をサポート
既存の開発環境で追加インストールなしで動作可能
UI Automationベースで安定した動作
学習コストが比較的低い
以下の制約は認識していますが、現在の業務要件では問題ありません:
プロジェクトの更新が停止している(最終更新: 2016年頃)
クロスプラットフォーム対応がない(Windows専用)
一部の最新UI要素への対応が不完全
構成
| 名称 | 説明 |
|---|---|
| Form1 | テスト対象の画面 |
| UnitTest1.vb | E2Eのテストコード |
| testdata.csv | テストデータ |
開発環境
古い環境でもうごくかは別途確認します。
| 名称 | バージョン | 説明 |
|---|---|---|
| Windows | 10 | OS |
| VisualStudio | Community 2022 | IDE |
| .Net FrameWork | 4.8 | WindowsFormのフレームワーク |
NuGet関係のパッケージ
| 名称 | バージョン | 説明 |
|---|---|---|
| xUnit | 2.9.3 | C#のユニットテストのフレームワーク |
| TestStack.White | 0.13.3 | UI自動化のフレームワーク |
成果物
プロダクトプロジェクト
フォーム読み込み時に、SQLを発行して、テーブルの件数を取得する機能です。
画面
コード
Imports System.Data.SqlClient
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
TextBox1.Text = "HelloWorld!"
End Sub
Private connectionString As String = "Server=127.0.0.1,1432;Database=master;User Id=sa;Password=user@12345;"
Public ReadOnly Property GetFolderPath As String
Get
Return Environment.CurrentDirectory
End Get
End Property
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Using conn As New SqlConnection(connectionString)
conn.Open()
Dim countSql As String = "SELECT COUNT(*) FROM dbo.TestUsers;"
Using cmd As New SqlCommand(countSql, conn)
CountText.Text = CInt(cmd.ExecuteScalar())
End Using
End Using
End Sub
End Class
DDL
CREATE TABLE TestUsers (
Id INT PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(100) NOT NULL,
Email NVARCHAR(100) NOT NULL,
Age INT,
CreatedAt DATETIME DEFAULT GETDATE()
);
テストプロジェクト
testdata.csv
Name,Email,Age
山田太郎,yamada@example.com,30
佐藤花子,sato@example.com,25
田中一郎,tanaka@example.com,35
テストコード
Imports System
Imports System.Threading
Imports TestStack.White
Imports TestStack.White.UIItems
Imports TestStack.White.UIItems.WindowItems
Imports TestStack.White.UIItems.Finders
Imports Xunit
Imports TestStack.White.Factory
Imports VBWinform
Imports System.IO
Imports System.Data.SqlClient
Public Class NotepadTestsAdvanced
Implements IDisposable
Private application As Application
Private window As Window
Private Shared ReadOnly testProjectName As String = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name
Private Shared ReadOnly solutionPath As String = AppContext.BaseDirectory.Substring(0, AppContext.BaseDirectory.IndexOf(testProjectName))
Private Shared ReadOnly NotepadAppId As String = Path.Combine(solutionPath + "\VBWinform\bin\Debug", "VBWinform.exe")
Private connectionString As String = "Server=127.0.0.1,1432;Database=master;User Id=sa;Password=user@12345;"
Public Sub New()
' データベース初期化
InitializeDatabase()
' CSVファイルからデータを読み込んで、Insert
Dim csvPath As String = Path.Combine(solutionPath + "\WhiteGUITest", "testdata.csv")
LoadDataFromCsv(csvPath)
' アプリケーション起動
application = Application.Launch(NotepadAppId)
Thread.Sleep(500) ' ウィンドウが開くまで待機
window = application.GetWindow("Form1", InitializeOption.NoCache)
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
' テスト完了後のクリーンアップ
CleanupDatabase()
Try
If window IsNot Nothing AndAlso Not window.IsClosed Then
window.Close()
End If
Catch ex As Exception
' ウィンドウが既に閉じている場合のエラーを無視
End Try
If application IsNot Nothing Then
application.Close()
application.Dispose()
End If
End Sub
''' <summary>
''' データベースを初期化(テーブルの削除と再作成)
''' </summary>
Private Sub InitializeDatabase()
Using conn As New SqlConnection(connectionString)
conn.Open()
' 既存テーブルを削除
Dim dropTableSql As String = "DELETE FROM dbo.TestUsers;"
Using cmd As New SqlCommand(dropTableSql, conn)
cmd.ExecuteNonQuery()
End Using
Console.WriteLine("データベースを初期化しました。")
End Using
End Sub
''' <summary>
''' CSVファイルからデータを読み込んでInsert
''' </summary>
''' <param name="csvFilePath">CSVファイルのパス</param>
Private Sub LoadDataFromCsv(csvFilePath As String)
If Not File.Exists(csvFilePath) Then
Console.WriteLine($"CSVファイルが見つかりません: {csvFilePath}")
Return
End If
Using conn As New SqlConnection(connectionString)
conn.Open()
' CSVファイルを読み込み
Dim lines As String() = File.ReadAllLines(csvFilePath)
' ヘッダー行をスキップ(最初の行がヘッダーの場合)
For i As Integer = 1 To lines.Length - 1
Dim values As String() = lines(i).Split(","c)
If values.Length >= 3 Then
Dim insertSql As String = "
INSERT INTO dbo.TestUsers (Name, Email, Age)
VALUES (@Name, @Email, @Age);
"
Using cmd As New SqlCommand(insertSql, conn)
cmd.Parameters.AddWithValue("@Name", values(0).Trim())
cmd.Parameters.AddWithValue("@Email", values(1).Trim())
Dim age As Integer
If Integer.TryParse(values(2).Trim(), age) Then
cmd.Parameters.AddWithValue("@Age", age)
Else
cmd.Parameters.AddWithValue("@Age", 0)
End If
cmd.ExecuteNonQuery()
End Using
End If
Next
Console.WriteLine($"CSVファイルからデータを読み込みました: {lines.Length - 1}件")
End Using
End Sub
''' <summary>
''' データが正しく挿入されたかを確認するヘルパーメソッド
''' </summary>
Private Function GetRecordCount() As Integer
Using conn As New SqlConnection(connectionString)
conn.Open()
Dim countSql As String = "SELECT COUNT(*) FROM dbo.TestUsers;"
Using cmd As New SqlCommand(countSql, conn)
Return CInt(cmd.ExecuteScalar())
End Using
End Using
End Function
''' <summary>
''' データベースをクリーンアップ
''' </summary>
Private Sub CleanupDatabase()
Using conn As New SqlConnection(connectionString)
conn.Open()
' テストデータを削除
Dim deleteSql As String = "DELETE FROM dbo.TestUsers;"
Using cmd As New SqlCommand(deleteSql, conn)
cmd.ExecuteNonQuery()
End Using
Console.WriteLine("データベースをクリーンアップしました。")
End Using
End Sub
<Fact>
Public Sub レコードの件数が取得できていること()
Dim textBox = window.Get(Of TextBox)("CountText")
Assert.Equal("3", textBox.Text)
End Sub
End Class
参考
TestStack White github
github commit分
