VB のたまご

作成日: 2016/12/04, 更新日: 2016/12/06


Friendlyを使ったWinFormsアプリの自動テストを楽したいのリメイク版

  •  この記事は、Visual Basic Advent Calendar 2016 の4日目のエントリーです。 また、以前投稿した記事(Friendlyを使ったWinFormsアプリの自動テストを楽したい)のリメイク版という位置づけです。

  •  Friendly は、株式会社Codeer (コーディア) 様が開発された、結合テスト作成用のライブラリです。

  •  本記事では、VB.NET で使う場合の Friendly のあとちょっとなところを記載していますが、これは Friendly を批判したいのではなくて、 その部分が何とかなれば、みんな Friendly を使うようになって、それにより、早く家に帰ってワシャワシャできるよねっていう、願望が含まれた未来に向かうための位置づけです。

  •  ぶっちゃけてしまうと、慣れの問題なので、使い続けていれば気にならなくなるし、UI テストが自動化できるから回帰テストで楽できるし、 私の記事が無くても、Friendly があればそれでいいんですよ!実行中のプログラムに介入して、傷つけることなく相手を操作できるっていうのは、UI テストする上で最強機能です!

  •  本記事内のソースを使う場合は、あらかじめ作成しておいた単体テストプロジェクトに、本ソースを追加して使います。 ただし、本来は、Friendly を拡張した DLL に含めておいて、単体テストプロジェクトでは、そのDLLを参照して使うのが正当な使い方かなと思います。

  •  さてさて、Friendly という最強のテスト自動化ライブラリを使って、UI テストを自動化しちゃうぞ~!っつって、UI 操作をプログラミングしていきます。 単体テストプロジェクトで利用する Friendly のうち、WinForms で使いそうな以下の2つを NuGet してきます。

  • ' メッセージボックス(ネイティブウィンドウ)系のUI操作ヘルパー
    Codeer.Friendly.Windows.NativeStandardControls
    
    ' WinForms 系のUI操作ヘルパー
    Ong.Friendly.FormsStandardControls
    

  •  以下は、操作対象の WindowsApplication です。プライベートなフィールドとメソッドがあります。

  • Public Class Form1
    
        Private testValue As Integer = 0
    
        Private Sub SetTestValue(value As Integer)
    
            Me.Text = value.ToString()
            Me.testValue = value
    
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    
            MessageBox.Show("Hello, Friendly!",
                            "インフォメーション",
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Information)
    
        End Sub
    
    End Class
    

デフォルト書き方

  •  まずは、普通に Friendly を使った場合のテストソースをみて見ます。 以降では、Option Strict Off を明示的に書いていますが、 これは、私の VS 設定では、デフォルト設定は、Option Strict On で見守っててね。と、書いてあるためで、 元から Off に設定してある場合は不要でいけるのかもしれませんが、未確認です。 また、MSTest にしていますが、NUnit でも xUnit でも OK です。

  • Option Strict Off ' Dynamic 型を扱う場合は必要
    
    Imports System.Text
    Imports System.Diagnostics
    Imports System.Windows.Forms
    Imports System.Threading
    
    Imports Microsoft.VisualStudio.TestTools.UnitTesting
    
    Imports Codeer.Friendly
    Imports Codeer.Friendly.Windows
    Imports Codeer.Friendly.Windows.Grasp
    Imports Codeer.Friendly.Dynamic
    Imports Codeer.Friendly.Windows.NativeStandardControls
    
    Imports Ong.Friendly.FormsStandardControls
    
    <TestClass()>
    Public Class UnitTest1
    
        Private app As WindowsAppFriend = Nothing
        Private p As Process = Nothing
    
        <TestInitialize()>
        Public Sub TestInitialize()
    
            ' 実行中のプロセス一覧から操作対象プロセスを探して、見つけて操作する
            Dim exeFile As String = "WindowsApplication2.exe"
            Process.Start(exeFile)
            p = Process.GetProcessesByName(exeFile.Replace(".exe", String.Empty))(0)
            Me.app = New WindowsAppFriend(p)
    
        End Sub
    
        <TestCleanup()>
        Public Sub TestCleanup()
    
            p.CloseMainWindow()
            Me.app.Dispose()
    
        End Sub
    
        <TestMethod()>
        Public Sub TestMethod1()
    
            Dim form As Object = app.Type(Of Control)().FromHandle(p.MainWindowHandle)
            form.SetTestValue(5)
            form.testValue = 12
    
            ' C# では以下の操作でできるが、VB だとキャストエラー
            ' System.InvalidCastException: 型 'DynamicAppVar' から型 'Integer' への変換は無効です。
            'Dim value As Integer = form.testValue
            'Dim text As String = form.Text
    
            ' 代わりに、メンバーを取得する場合は、古い取得方法(Dynamic 形式ではなく、AppVar 形式)で取ってくる
            ' メンバー名を指定して Core プロパティから取ってくる
            ' Core プロパティは Object 型だけど、Integer型 や String型 に自動変換してくれている。
            ' プリミティブ型だからか?独自クラス型でもいけるのかは未確認
            Dim value As Integer = CType(form, DynamicAppVar).CodeerFriendlyAppVar("testValue")().Core
            Dim text As String = CType(form, DynamicAppVar).CodeerFriendlyAppVar("Text")().Core
    
            Assert.AreEqual(12, value)
            Assert.AreEqual("5", text)
    
        End Sub
    
    End Class
    

  •  上記テストソースの通り、Friendly は、UI 操作ができますが、実はロジック系のテストでも活用できます。 つまり、Private とか Protected とか、アクセスレベル関係なくアクセスできますので、内部メソッドとかの内部メンバーに対するテストだってできちゃいます。 すげーです、まじで。

  •  で上記の通り、VB の場合、Object(DynamicAppVar) → 明示的なDynamicAppVar → AppVar → プリミティブ型の順でキャストしないと、値を引っ張ってくることができません。 なんでだろう?このことから、VB の場合、設定系のダイナミック操作は良い感じで扱えますが、 取得系になると途端にダイナミック操作が扱いづらくなる(ようなやり方しか探せなかった)んですね。

  •  ちなみに、.NET 3.5 以下版の取得方法が以下です。

  • ' 文字列指定なので、名前空間+クラス名+メソッド名と、もしあれば引数
    Dim form2 As AppVar = app("System.Windows.Forms.Control.FromHandle")(p.MainWindowHandle)
    form2("SetTestValue")(10) ' Form2.SetTestValue(10) と同義
    form2("testValue")(24)      ' Form2.testValue = 24 と同義
    
    Dim value2 As Integer = form2("testValue")().Core
    Dim text2 As String = form2("Text")().Core
    
    Assert.AreEqual(24, value2)
    Assert.AreEqual("10", text2)
    

  •  設定系が独特ですがなんとなくは分かると思います。ダイナミック編で書いた取得系の型変換は、そもそもこっち側の取得方法なのでした。 それでは、コントロール操作はどうでしょうか?画面操作ではコントロール操作がほとんどです。

  • ' 画面上にあるボタンを押して、メッセージボックスを閉じてみよう。
    ' メッセージボックスが見えないので1秒待機
    Dim button1 As New FormsButton(form2("Button1")())
    button1.EmulateClick(New Async())
    Thread.Sleep(1000)
    
    Dim formCore As New WindowControl(form2)
    Dim dlgCore = formCore.WaitForNextModal()
    Dim dlg As New NativeMessageBox(dlgCore)
    Dim msg = dlg.Message
    dlg.EmulateButtonClick("OK")
    
    Assert.AreEqual("Hello, Friendly!", msg)
    

  •  ボタン操作用のコントロール変数を経由して操作する、メッセージボックス用のコントロール変数を経由して操作するというサンプルです。 これはこういう感じかなという感じです。

  •  まあ、ちょっと不便なところもあるけど、これでもいっかなとか思ってたんですよ。 私が感じた不便なところは、ダイナミック形式での操作が前提となるので、①取得系が面倒、キャストが面倒、という点です。

  • この違い・・・
    Dim text As String = form.Text
    Dim text As String = CType(form, DynamicAppVar).CodeerFriendlyAppVar("Text")().Core
    

  •  後、②インテリセンスが表示されないっていう点です。

  • コードは分かりやすいけど、コーディングしづらい・・・
    form.Button1.PerformClick()
    

  •  特に、私はインテリセンスに介護されているようなものなので、インテリセンスが出ないと、途端にコーディングスピードがカメさん並みに落ちてしまいます。 そんなときに、ネットの海を泳いでいたところ、Java 用テスティングツールの FEST-Swing の紹介記事を見つけました! 画面名.コントロール名.メンバー名という、ハイパー分かりやすい扱い方です。

VBFriendly な書き方

  •  で、パクった結果、出来上がったものがこちらになります。

  • Option Strict Off ' Dynamic 型を扱う場合は必要
    
    Imports System.Text
    Imports System.Threading
    
    Imports Microsoft.VisualStudio.TestTools.UnitTesting
    
    Imports Codeer.Friendly
    
    <TestClass()>
    Public Class UnitTest1
    
        <TestInitialize()>
        Public Sub TestInitialize()
    
            ' 実行中のプロセス一覧から操作対象プロセスを探して、見つけて操作する
            Dim exeFile As String = "WindowsApplication2.exe"
            Process.Start(exeFile)
            Dim p As Process = Process.GetProcessesByName(exeFile.Replace(".exe", String.Empty))(0)
            VBFriendly.Initialize(p)
    
        End Sub
    
        <TestCleanup()>
        Public Sub TestCleanup()
    
            VBFriendly.Dispose()
    
        End Sub
    
        <TestMethod()>
        Public Sub TestMethod1()
    
            ' 設定系、画面を指定して、ダイナミック操作(なのでインテリセンスが効かないorz)
            Dim form = VBFriendly.FindForm("Form1")
            Dim targetForm = form.TargetForm
            targetForm.SetTestValue(5)
            targetForm.testValue = 10
    
            ' 取得系、若干記述量が減った程度
            Dim value = VBFriendly.GetData(Of Integer)(targetForm, "testValue")
            Dim text = VBFriendly.GetData(Of String)(targetForm, "Text")
    
            Assert.AreEqual(10, value)
            Assert.AreEqual("5", text)
    
            ' ボタンを押してメッセージボックスを閉じる
            ' メッセージボックスが見えないので1秒待機
            form.Button("Button1").EmulateClick(New Async())
            Thread.Sleep(1000)
    
            Dim dlg = VBFriendly.FindMessageBox()
            Dim msg = dlg.Message
            dlg.EmulateButtonClick("OK")
    
            Assert.AreEqual("Hello, Friendly!", msg)
    
        End Sub
    
    End Class
    

  •  UI 操作に関しては、こんな感じです。 Friendly の改良点として一番重視したのが、クラス名の隠ぺいでした。初見で、WindowsAppFriend, WindowControl, AppVar, DynamicAppVar, ... だなんだと出てこられると、うーって気が引けてしまい、ちょっと難しそうだねと敬遠しちゃうんじゃないかなと思ったので、基本的に VBFriendly.~から操作が始まって、 後はインテリセンスに教えてもらうような仕組みにしたかったので、ドンピシャでした。

  •  記述量が少ないから、見渡しやすくなったと思いませんか? まあ、やりたいことが増えてくれば、または細かいことをしたくなったら、元々のクラスを使う場面も出て来るかと思います。 でも、Friendly の始まりは、こんな感じでもいいんじゃないかと思っています。

VBFriendly の構成

  •  だいたいこんな感じです。

  • イメージ
  •  画面を見つける人、画面を操作する人、キーボード操作する人、画面キャプチャする人と、 いろいろ役割を持たせていますが、全部を VBFriendly というクラスにまとめつつ隠しています。 それでは1つ1つのクラスを見ていきます。

画面を操作する人(FormOperator)

  •  ここでは、取得した画面から、操作したいコントロール【に対応する操作用コントロール】を返却する仕事をしています。 サンプルでは、.NET 標準コントロール【に対応する操作用コントロール】を返却していて、つまり橋渡ししているだけのクラスとなります。 もしも、独自コントロールを操作したい場合は、あらかじめ対応する操作用コントロールを用意する必要があります。

  • Option Strict Off ' Dynamic を使いたいため
    
    Imports Codeer.Friendly
    Imports Codeer.Friendly.Dynamic
    Imports Ong.Friendly.FormsStandardControls
    
    ' NuGet から、FormsStandardControls と NativeStandardControls を追加しています。
    ' 操作対象画面内にある各コントロールを、簡単操作できるようにサポートするクラスです。
    Public Class FormOperator
    
        ' 実体は、DynamicAppVar 型
        ' やりたい機能が提供されていない場合は、TargetForm を直接操作してください。
        ' xxx.TargetForm.button1.PerformClick() 等。
        Public TargetForm As Object = Nothing
    
        Public Sub New(ByVal form As Object)
    
            TargetForm = form
    
        End Sub
    
        Public Function Button(ByVal name As String) As FormsButton
    
            Dim control As AppVar = GetControl(name)
            Return New FormsButton(control)
    
        End Function
    
        Public Function CheckBox(ByVal name As String) As FormsCheckBox
    
            Dim control As AppVar = GetControl(name)
            Return New FormsCheckBox(control)
    
        End Function
    
        Public Function CheckedListBox(ByVal name As String) As FormsCheckedListBox
    
            Dim control As AppVar = GetControl(name)
            Return New FormsCheckedListBox(control)
    
        End Function
    
        Public Function ComboBox(ByVal name As String) As FormsComboBox
    
            Dim control As AppVar = GetControl(name)
            Return New FormsComboBox(control)
    
        End Function
    
        Public Function DataGridView(ByVal name As String) As FormsDataGridView
    
            Dim control As AppVar = GetControl(name)
            Return New FormsDataGridView(control)
    
        End Function
    
        Public Function DateTimePicker(ByVal name As String) As FormsDateTimePicker
    
            Dim control As AppVar = GetControl(name)
            Return New FormsDateTimePicker(control)
    
        End Function
    
        ' Label コントロール用の操作コントロール
        Public Function Label(ByVal name As String) As FormsLabel
    
            Dim control As AppVar = GetControl(name)
            Return New FormsLabel(control)
    
        End Function
    
        Public Function LinkLabel(ByVal name As String) As FormsLinkLabel
    
            Dim control As AppVar = GetControl(name)
            Return New FormsLinkLabel(control)
    
        End Function
    
        'Public Function ListBox(ByVal name As String) As FormsListBox
    
        '    Dim control As AppVar = GetControl(name)
        '    Return New FormsListBox(control)
    
        'End Function
    
        ' ダブルクリックイベントの操作に対応した ListBox コントロール用の操作コントロール
        Public Function ListBox(ByVal name As String) As FormsListBoxEx
    
            Dim control As AppVar = GetControl(name)
            Return New FormsListBoxEx(control)
    
        End Function
    
        Public Function ListView(ByVal name As String) As FormsListView
    
            Dim control As AppVar = GetControl(name)
            Return New FormsListView(control)
    
        End Function
    
        Public Function MaskedTextBox(ByVal name As String) As FormsMaskedTextBox
    
            Dim control As AppVar = GetControl(name)
            Return New FormsMaskedTextBox(control)
    
        End Function
    
        Public Function MonthCalendar(ByVal name As String) As FormsMonthCalendar
    
            Dim control As AppVar = GetControl(name)
            Return New FormsMonthCalendar(control)
    
        End Function
    
        Public Function NumericUpDown(ByVal name As String) As FormsNumericUpDown
    
            Dim control As AppVar = GetControl(name)
            Return New FormsNumericUpDown(control)
    
        End Function
    
        Public Function ProgressBar(ByVal name As String) As FormsProgressBar
    
            Dim control As AppVar = GetControl(name)
            Return New FormsProgressBar(control)
    
        End Function
    
        Public Function RadioButton(ByVal name As String) As FormsRadioButton
    
            Dim control As AppVar = GetControl(name)
            Return New FormsRadioButton(control)
    
        End Function
    
        Public Function RichTextBox(ByVal name As String) As FormsRichTextBox
    
            Dim control As AppVar = GetControl(name)
            Return New FormsRichTextBox(control)
    
        End Function
    
        Public Function TabControl(ByVal name As String) As FormsTabControl
    
            Dim control As AppVar = GetControl(name)
            Return New FormsTabControl(control)
    
        End Function
    
        Public Function TextBox(ByVal name As String) As FormsTextBox
    
            Dim control As AppVar = GetControl(name)
            Return New FormsTextBox(control)
    
        End Function
    
        Public Function ToolStrip(ByVal name As String) As FormsToolStrip
    
            Dim control As AppVar = GetControl(name)
            Return New FormsToolStrip(control)
    
        End Function
    
        Public Function TrackBar(ByVal name As String) As FormsTrackBar
    
            Dim control As AppVar = GetControl(name)
            Return New FormsTrackBar(control)
    
        End Function
    
        Public Function TreeView(ByVal name As String) As FormsTreeView
    
            Dim control As AppVar = GetControl(name)
            Return New FormsTreeView(control)
    
        End Function
    
        ' 指定のコントロール名を元に、操作中の画面からコントロールを探して返却します。
        Private Function GetControl(ByVal controlName As String) As AppVar
    
            Dim dItem As DynamicAppVar = CType(TargetForm, DynamicAppVar)
            Dim form As AppVar = dItem.CodeerFriendlyAppVar
            Dim control As AppVar = form(controlName)()
            Return control
    
        End Function
    
    End Class
    
    Label 用の操作用コントロール は未対応なので、自前で作成しています。
    
    Imports Codeer.Friendly
    Imports Codeer.Friendly.Windows
    Imports Ong.Friendly.FormsStandardControls
    
    Public Class FormsLabel
        Inherits FormsControlBase
    
        '<Obsolete("Please use New(ByVal windowObject As AppVar).", False)>
        'Public Sub New(ByVal app As WindowsAppFriend, ByVal _appVar As AppVar)
        '    MyBase.New(app, _appVar)
    
        'End Sub
    
        'Public Sub New(ByVal src As WindowControl)
        '    MyBase.New(src)
    
        'End Sub
    
        Public Sub New(ByVal _appVar As AppVar)
            MyBase.New(_appVar)
    
            ' FormsLabel.vb を定義しているアセンブリデータを、相手プロセスにロードして動かせるようにする
            ' ※本来このプロジェクトはテストプロジェクトなので、本当は別の dll プロジェクトを作成して、
            '  そこに本ソースを入れて、そのアセンブリだけインジェクションしたいところ(責務を分けたい)
            WindowsAppExpander.LoadAssembly(CType(_appVar.App, WindowsAppFriend), GetType(FormsLabel).Assembly)
    
        End Sub
    
        ' Text プロパティは、FormsControlBase 側で定義済だったので実装不要orz
    
    End Class
    
    ListBox 用の操作用コントロールは準備してありますが、ダブルクリックイベントには未対応なので、
    拡張して、ダブルクリックイベントに対応したものを自前で作成しています。
    
    Imports Codeer.Friendly
    Imports Codeer.Friendly.Windows
    Imports Ong.Friendly.FormsStandardControls
    Imports System.Windows.Forms
    Imports System.Reflection
    
    Public Class FormsListBoxEx
        Inherits FormsListBox
    
        '<Obsolete("Please use New(ByVal windowObject As AppVar).", False)>
        'Public Sub New(ByVal app As WindowsAppFriend, ByVal _appVar As AppVar)
        '    MyBase.New(app, _appVar)
    
        '    ' FormsListBoxEx.vb を定義しているアセンブリデータを、相手プロセスにロードして動かせるようにする
        '    WindowsAppExpander.LoadAssembly(app, GetType(FormsListBoxEx).Assembly)
    
        'End Sub
    
        'Public Sub New(ByVal src As WindowControl)
        '    MyBase.New(src)
    
        '    ' FormsListBoxEx.vb を定義しているアセンブリデータを、相手プロセスにロードして動かせるようにする
        '    ' ここのタイミングで、App が準備完了しているのか?未検証
        '    WindowsAppExpander.LoadAssembly(App, GetType(FormsListBoxEx).Assembly)
    
        'End Sub
    
        Public Sub New(ByVal _appVar As AppVar)
            MyBase.New(_appVar)
    
            ' FormsListBoxEx.vb を定義しているアセンブリデータを、相手プロセスにロードして動かせるようにする
            ' ※本来このプロジェクトはテストプロジェクトなので、本当は別の dll プロジェクトを作成して、
            '  そこに本ソースを入れて、そのアセンブリだけインジェクションしたいところ(責務を分けたい)
            WindowsAppExpander.LoadAssembly(CType(_appVar.App, WindowsAppFriend), GetType(FormsListBoxEx).Assembly)
    
        End Sub
    
        ' リストボックス選択欄のダブルクリック
        Public Sub EmulateDoubleClick(ByVal index As Integer)
    
            App(Me.GetType(), "EmulateDoubleClickInTarget")(AppVar, index)
    
        End Sub
    
        Public Sub EmulateDoubleClick(ByVal index As Integer, ByVal _async As Async)
    
            App(Me.GetType(), "EmulateDoubleClickInTarget", _async)(AppVar, index)
    
        End Sub
    
        Shared Sub EmulateDoubleClickInTarget(ByVal _listbox As ListBox, ByVal index As Integer)
    
            _listbox.Focus()
            _listbox.SelectedIndex = index
            _listbox.GetType().GetMethod("OnDoubleClick", BindingFlags.NonPublic Or BindingFlags.Instance).Invoke(_listbox, New Object() {New EventArgs()})
    
        End Sub
    
    End Class
    

画面を見つける人(FormFinder)

  •  アタッチしたプロセスから、表示中の画面を探して返却します。 この時、そのまま返すのではなくて、FormOperator のインスタンスを返すことで、すぐにコントロール操作が可能になるように狙っています。

  • Option Strict Off ' Dynamic を使いたいため
    
    Imports Codeer.Friendly
    Imports Codeer.Friendly.Dynamic
    Imports Codeer.Friendly.Windows
    Imports Codeer.Friendly.Windows.Grasp
    Imports Codeer.Friendly.Windows.NativeStandardControls
    Imports System.Threading
    Imports System.Windows.Forms
    
    ' 取得したプロセス(の先のインスタンス?)を元に、操作したい画面を見つけて返却するクラスです。
    Public Class FormFinder
    
        ' アクティブ画面を見つけて返却
        Public Shared Function ActiveForm(ByVal app As WindowsAppFriend) As FormOperator
    
            ' 何かは入っているが、Friendly 的には Null であるという状態があるみたいなので、二重チェック
            Dim form As Object = Nothing
            While (form Is Nothing) OrElse (CType(form, DynamicAppVar).CodeerFriendlyAppVar.IsNull)
                form = app.Type(Of Form)().ActiveForm
                Thread.Sleep(100)
            End While
    
            Return New FormOperator(form)
    
        End Function
    
        ' 指定画面を見つけて返却
        Public Shared Function FindForm(ByVal app As WindowsAppFriend, ByVal className As String) As FormOperator
    
            Dim result As FormOperator = Nothing
            While True
    
                ' 現在表示中の画面リストを取得して、指定画面を探す
                Dim dItems As DynamicAppVar = CType(app.Type(Of Application)().OpenForms, DynamicAppVar)
                Dim aItems As AppVar = dItems.CodeerFriendlyAppVar
                Dim forms As Enumerate = New Enumerate(aItems)
    
                For Each form As AppVar In forms
    
                    Dim name As String = CType(form("Name")().Core, String)
                    If name = className Then
                        result = New FormOperator(form.Dynamic())
                        Exit For
                    End If
    
                Next
    
                If result IsNot Nothing Then
                    Exit While
                End If
    
                Thread.Sleep(100)
            End While
    
            Return result
    
        End Function
    
        ' メッセージ画面(ネイティブウィンドウ)を見つけて返却
        Public Shared Function FindMessageBox(ByVal app As WindowsAppFriend) As NativeMessageBox
    
            Dim dlgCore As WindowControl = WindowControl.FromZTop(app)
            Dim dlg As New NativeMessageBox(dlgCore)
            Return dlg
    
        End Function
    
    End Class
    

全部をまとめる人(VBFriendly)+キーボード操作する人(KeyboardOperator)+画面キャプチャする人(ScreenCapture)

  •  キーボード操作する人と画面キャプチャする人はメソッドとして提供しています。 画面操作系は、橋渡し役になっており、FormFinder クラスに仕事をお任せしています。

  • Option Strict Off ' Dynamic を使いたいため
    
    Imports Codeer.Friendly
    Imports Codeer.Friendly.Dynamic
    Imports Codeer.Friendly.Windows
    Imports Codeer.Friendly.Windows.NativeStandardControls
    
    Imports System.IO
    Imports System.Drawing
    Imports System.Drawing.Imaging
    Imports System.Windows.Forms
    
    ' NuGet から、FormsStandardControls と NativeStandardControls を追加しています。
    ' 参照の追加から、System.Drawing を追加しています。
    ' UI 操作を簡単にできるようにサポートするユーティリティクラスです。
    Public Class VBFriendly
    
    #Region "メンバー"
    
        Private Shared app As WindowsAppFriend = Nothing
    
    #End Region
    
    #Region "画面取得"
    
        ' アクティブ画面を見つけて返却
        Public Shared Function ActiveForm() As FormOperator
    
            Return FormFinder.ActiveForm(app)
    
        End Function
    
        ' 指定画面を見つけて返却
        Public Shared Function FindForm(ByVal className As String) As FormOperator
    
            Return FormFinder.FindForm(app, className)
    
        End Function
    
        ' メッセージ画面(ネイティブウィンドウ)を見つけて返却
        Public Shared Function FindMessageBox() As NativeMessageBox
    
            Return FormFinder.FindMessageBox(app)
    
        End Function
    
    #End Region
    
    #Region "キーボード操作"
    
        ' 本機能は、「どの相手に対してキー送信するか」という機能はありません。
        ' アクティブな画面、アクティブなコントロールに対して、キー操作を送信します。
        ' つまり、操作対象の画面に対して、操作対象のコントロールに対して、等の指定はできませんので、
        ' あらかじめ、フォーカスを当てておくなどの準備をしておいてください。
    
        ' SendKeys.Send メソッド (String)
        ' https://msdn.microsoft.com/ja-jp/library/system.windows.forms.sendkeys.send(v=vs.110).aspx
    
        Public Shared Sub OnKeyPressF1()
            SendKeys.SendWait("{F1}")
        End Sub
    
        Public Shared Sub OnKeyPressF2()
            SendKeys.SendWait("{F2}")
        End Sub
    
        Public Shared Sub OnKeyPressF3()
            SendKeys.SendWait("{F3}")
        End Sub
    
        Public Shared Sub OnKeyPressF4()
            SendKeys.SendWait("{F4}")
        End Sub
    
        Public Shared Sub OnKeyPressF5()
            SendKeys.SendWait("{F5}")
        End Sub
    
        Public Shared Sub OnKeyPressF6()
            SendKeys.SendWait("{F6}")
        End Sub
    
        Public Shared Sub OnKeyPressF7()
            SendKeys.SendWait("{F7}")
        End Sub
    
        Public Shared Sub OnKeyPressF8()
            SendKeys.SendWait("{F8}")
        End Sub
    
        Public Shared Sub OnKeyPressF9()
            SendKeys.SendWait("{F9}")
        End Sub
    
        Public Shared Sub OnKeyPressF10()
            SendKeys.SendWait("{F10}")
        End Sub
    
        Public Shared Sub OnKeyPressF11()
            SendKeys.SendWait("{F11}")
        End Sub
    
        Public Shared Sub OnKeyPressF12()
            SendKeys.SendWait("{F12}")
        End Sub
    
    
    
        Public Shared Sub OnKeyPressTab()
            SendKeys.SendWait("{TAB}")
        End Sub
    
        Public Shared Sub OnKeyPressShiftTab()
            SendKeys.SendWait("+{TAB}")
        End Sub
    
        Public Shared Sub OnKeyPressEnter()
            SendKeys.SendWait("{ENTER}")
        End Sub
    
        Public Shared Sub OnKeyPressEscape()
            SendKeys.SendWait("{ESC}")
        End Sub
    
        Public Shared Sub OnKeyPressBackSpace()
            SendKeys.SendWait("{BACKSPACE}")
        End Sub
    
        Public Shared Sub OnKeyPressDelete()
            SendKeys.SendWait("{DELETE}")
        End Sub
    
    
    
        Public Shared Sub OnKeyPressUp()
            SendKeys.SendWait("{UP}")
        End Sub
    
        Public Shared Sub OnKeyPressDown()
            SendKeys.SendWait("{DOWN}")
        End Sub
    
        Public Shared Sub OnKeyPressLeft()
            SendKeys.SendWait("{LEFT}")
        End Sub
    
        Public Shared Sub OnKeyPressRight()
            SendKeys.SendWait("{RIGHT}")
        End Sub
    
    
    
        Public Shared Sub OnKeyPressPageUp()
            SendKeys.SendWait("{PGUP}")
        End Sub
    
        Public Shared Sub OnKeyPressPageDown()
            SendKeys.SendWait("{PGDN}")
        End Sub
    
    #End Region
    
    #Region "画面キャプチャ"
    
        Public Shared Sub SaveImage()
    
            Dim imageName As String = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff") + "_画面キャプチャ.png"
            Dim imageFile As String = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, imageName)
            Dim rc As Rectangle = Screen.PrimaryScreen.Bounds
            Dim bmp = New Bitmap(rc.Width, rc.Height, PixelFormat.Format32bppArgb)
            Using g As Graphics = Graphics.FromImage(bmp)
                g.CopyFromScreen(rc.X, rc.Y, 0, 0, rc.Size, CopyPixelOperation.SourceCopy)
            End Using
    
            bmp.Save(imageFile, ImageFormat.Png)
    
        End Sub
    
    #End Region
    
    #Region "その他サポート"
    
        ' 初期化処理
        Public Shared Sub Initialize(p As Process)
    
            If app IsNot Nothing Then
                Dispose()
            End If
    
            app = New WindowsAppFriend(p)
    
        End Sub
    
        ' 破棄処理
        Public Shared Sub Dispose()
    
            ' テストメソッド側で、プロセスごと終了してしまった場合でもエラーにならないように、
            ' 存在チェックしつつプロセス終了する。
            Dim processes As Process() = Process.GetProcesses()
            For Each p As Process In processes
    
                If p.Id = app.ProcessId Then
                    p.CloseMainWindow()
                    Exit For
                End If
    
            Next
    
            app.Dispose()
    
        End Sub
    
        ' 指定画面のメンバーを返却します。
        ' 値取得の場合、Object → DynamicAppVar → AppVar で取得しないといけないのが手間
        'actual = CType(form.TargetForm.Label4.Text, String) ' 本当はこうしたいが、キャストエラーになってしまうので、取得ヘルパー
        Public Shared Function GetData(Of T)(ByVal targetControl As Object, ByVal memberName As String) As T
    
            Dim dForm As DynamicAppVar = CType(targetControl, DynamicAppVar)
            Dim aForm As AppVar = dForm.CodeerFriendlyAppVar
            Dim member As T = CType(aForm(memberName)().Core, T)
            Return member
    
        End Function
    
    #End Region
    
    End Class
    

まとめ

  •  VBFriendly と入力しようとすると、インテリセンスが vbFriday を薦めてきて、ついクセでそれを選んじゃいそうになりますorz。 なので、MyFriend か MyBrother 辺りにクラス名を変えた方がいいか考えています。

  •  人それぞれ嗜好がありますので良い悪いあるかと思いますが、適度な入力サポートがあって、最短操作で UI を操作できる。 そういう課題はクリアできたかなと私的には思っています。なので、良ければ読者様の好みに合わせて改変・改良して UI の自動化を進めていきましょう!

  •  最後まで読んでいただき、ありがとうございました。