UI Automation PowerShell ExtensionsとPesterを使って、電卓アプリのテストを自動化してみた

今回はUIAutomation PowerShell ExtentionとPesterを用いて、電卓アプリの自動化をしてみました。
その内容について、記載したいと思います。

<details><summary>なぜ、これをしようと思ったのか</summary>
デスクトップアプリの自動化といえば、UFTやegplant等の有償なものが多い。
プログラミングベースでデスクトップアプリを触れるものがないかなと思っていたところ、UIAutomationというものがあり、かつPowerShellだけで触れるものがあった。
また、PowerShellにテストフレームワークPesterがあるので組み合わせて使ってみたいと思った。</details>

#UIAutomation PowerShell Extentionとは
Microsoftが提供しているUI AutomationをPowerShellから呼び出せるようにしたものです。

<details><summary>UIAutomationとは</summary>
Microsoft UI Automationは、Microsoft Windows用の新しいアクセシビリティフレームワークで、Windows Presentation Foundation（WPF）をサポートするすべてのOSで利用できます。
UI Automationは、デスクトップ上のほとんどのユーザーインターフェース（UI）要素へのプログラムによるアクセスを提供し、スクリーンリーダーなどの支援技術製品がエンドユーザーにUIに関する情報を提供したり、標準入力以外の手段でUIを操作したりできるようにします。また、UI Automationでは、自動化されたテストスクリプトでUIを操作することもできます。
([UI Automation Overview](https://docs.microsoft.com/en-us/dotnet/framework/ui-automation/ui-automation-overview)から一部をDeepl翻訳したもの)

</details>

#Pesterとは
>Pesterは、PowerShell用のテストおよびモッキングフレームワークです。
Pesterは、テストを書いて実行するためのフレームワークを提供します。Pesterは、ユニットテストや統合テストを書くために最もよく使われますが、それだけに限定されるものではありません。また、環境全体、コンピュータのデプロイメント、データベースの設定などを検証するツールのベースにもなります。
([Pesterの公式ページ](https://pester.dev/)から一部をDeepl翻訳したもの)

#今回実施した環境

| 環境 | Version |
|:--:|:--:|
|Windows10|21H2|
|Powershell|5.1.19041.1320|
|UI Automation PowerShell Extention|UIAutomation.0.8.7B3.NET40|
|Pester|3.4.0(もともとWindowsに入っているものを使用)|
|電卓アプリ(Windows標準)|10.2103.8.0|

## インストール方法
### UI Automation PowerShell Extention
以下のサイトからダウンロードし、任意のフォルダに展開します。

https://www.code-lab.net/?page_id=21648

:::note info
元々はhttps://uiautomation.codeplex.com/のCodeplexに格納されていました。
CodeplexはMicrosoftがオープンソース開発者向けに提供していたサービスなのですが、2017年12月15日をもってサービス終了になり、codeplexのページは閉鎖されました。ソースコード自体はhttps://github.com/apetrovskiy/STUPS/tree/master/UIAにありますが、インストール用のバイナリパッケージは上記のページにしか置かれていません。
:::

私はC:\Program Files\WindowsPowerShell\ModulesにUIAutomationというフォルダを作成し、そこに展開しました。

### Pester
PowerShellのバージョンが5.0以上の方はもともと入っているので、ここは飛ばして問題ありません。
PowerShellのバージョンが5.0未満の方、もしくは最新バージョンを使いたいという方は以下のサイトから、zipファイルをダウンロードし、任意のフォルダに展開してください。

https://github.com/pester/Pester

私はC:\Program Files\WindowsPowerShell\ModulesにPesterというファイルが入っていました。

:::note info
ダウンロードしたzipファイルを右クリック、プロパティでブロックの解除してから適当な場所に展開することをお勧めします。
:::

これで、UI Automation PowerShell ExtentionとPesterのダウンロードは完了です。
とても楽です。

#自動化コードを書いてみる
では、ここから電卓の自動テストコードを書きます。

##Pesterのひな形を作成する
Pesterではコマンドを入力することで、テンプレートを作ることができるため、
今回はそれを使いたいと思います。
まずPowerShellで任意のフォルダまで移動し、以下のコマンドを入力します。

```command:テンプレート作成コマンド
 New-Fixture .\ファイル名(今回はcalcで実行しました。)
```
コマンド入力後、以下の画面が表示され、指定したフォルダに以下の2つのファイルが作成されたらOKです。

```command:作成コマンド実行結果
 ディレクトリ:作成したファイルのディレクトリパス
 
 Mode                 LastWriteTime         Length Name
 ----                 -------------         ------ ----
 -a----        2021/12/04     17:22             25 calc.ps1
 -a----        2021/12/04     17:22            257 calc.Tests.ps1
```

calc.ps1はテスト対象のプログラムファイルです。
このファイルには電卓を自動操作するプログラムを書きます。

```Powershell:ひな形作成直後のcalc.ps1
 function calc {
 
 }
```

calc.Tests.ps1はテスト用のプログラムファイルです。
このファイルに電卓の自動テストを書いていきます。

```Powershell:ひな形作成直後のcalc.Tests.ps1
 $here = Split-Path -Parent $MyInvocation.MyCommand.Path
 $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
 . "$here\$sut"
 
 Describe "calc" {
     It "does something useful" {
         $true | Should Be $false
     }
 }
```

## 電卓のUI要素を取得する
電卓を自動で動かせるようにするには、UI要素を調べる必要があります。
デスクトップアプリの要素取得方法はいろいろありますが、
今回はUIAutomation PowerShell Extentionをダウンロードしたときに、組み込まれている「UIAutomationSpy」を使ってみます。

:::note info
私の環境では、UIAutomation.0.8.7B3.NET40のUIAutomationSpyは動作しなかったため、UIAutomation.0.8.7B3.NET35のUIAutomationSpyを使いました。
:::

1. UIAutomationSpyを起動します。以下の画面が出てきたらOKです。
   ![UIAutomationSpy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1895890/758711f8-460a-4d53-4605-13adbd86545e.png)

1. Startボタンを押下し、取得したい要素にカーソルを合わせます。
　　![UI取得.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1895890/2269fdb1-9921-ea86-f9b0-81b11daaaa01.png)　　
1. STOPボタンを押下する。
この手順を繰り返して、操作したいUI要素を取得していきます。

## コードを書く
今回は電卓の[1]ボタンを押すと、結果画面に「1」が表示することを確認するテストを書いていきます。
![電卓.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1895890/f484825e-2b06-6011-e607-b6d278002670.png)

まず、calc.ps1に電卓の[1]ボタンを押す処理を書いていきます。

```Powershell:calc.ps1
Import-Module "UIAutomation.dllが格納されているパス先を指定" #UI Automationモジュールをインポートする
[UIAutomation.Preferences]::Highlight=$false #自動操作中に表示される赤枠(ハイライト)を非表示にする
function click_numbutton_one {
    $wndw = Get-UiaWindow -Name '電卓' # 対象のアプリを指定する
    $wndw | Get-UiaButton -AutomationId 'num1Button' | Invoke-UiaButtonClick | Out-Null #電卓の[1]ボタンを指定し、クリックする
    $wndw | Get-UiaText -AutomationID 'CalculatorResults' | Read-UIAControlName #電卓の計算結果表示エリアに表示されている数値を取得する
}
```

次に、calc.Tests.ps1にテストコードを書いていきます。

```Powershell:calc.Tests.ps1
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
. "$here\$sut"

Describe "動作確認" {
    Start-Process calc #電卓を起動
    It "電卓ボタン1を押下すると、結果表示が1になること" { #テストケース名を記載
        click_numbutton_one| Should Be "表示は 1 です"　#実行する関数とアサーション処理を記載
    }
    Stop-Process -name Calculator #電卓を終了する
}
```


## Pesterでテストを実行する
テストコードもかけたので、実際に実行してみましょう。
PowerShellを起動して、動かすプログラムファイルがあるディレクトリまで、移動します。
実行コマンドは以下です。

```Command:実行コマンド
Invoke-Pester .\calc.Tests.ps1
```
実行すると電卓が起動し、[1]ボタンを押下します。
![実行結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1895890/f87d86cc-5b08-31d7-20c0-1ef0fe1fe7fd.png)

実行後、期待結果通りであれば、以下がpowershellに表示されます。
![実行結果OK.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1895890/44cc79b2-dc83-20bc-48fc-2e63c966e53d.png)

期待結果と違うと以下がPowershellに表示されます。
![実行結果NG.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1895890/00a27071-a882-2280-55b4-31649bfd61bc.png)

また、xml形式でテスト結果を出力することができます。
テスト実行時に以下のオプションを追加すると良いです。

```Command:実行コマンド
Invoke-Pester .\calc.Tests.ps1 -OutputFormat NUnitXml -OutputFile テスト結果.xml
```

### サンプルコード
上のコードだけでは少し寂しかったので、いろいろと足しました。
まだまだ改善が必要なので、参考程度に見ていただければと思います。

<details><summary>サンプルコード</summary><div>

```powershell:sample.ps1
Import-Module "UIAutomation.dllが格納されているパス先を指定"
[UIAutomation.Preferences]::Highlight=$false
function click_numbutton_one {
    $wndw = Get-UiaWindow -Name '電卓' # 対象のアプリを指定する
    $wndw | Get-UiaButton -AutomationId 'num1Button' | Invoke-UiaButtonClick | Out-Null  #電卓の[1]ボタンを指定し、クリックする
    $wndw | Get-UiaText -AutomationId 'CalculatorResults' | Read-UIAControlName #電卓の計算結果表示エリアに表示されている数値を取得する
}
function click_numbutton([string]$num) {
    $i = 0
    $wndw = Get-UiaWindow -Name '電卓'
    while([String]::IsNullOrEmpty($num[$i]) -eq $false)
    {
        $wndw | Get-UiaButton -AutomationId "num$($num[$i])Button" | Invoke-UiaButtonClick | Out-Null
        $i++
    }
}

function Four_law_calculation_button([string]$key) { # 演算子の+、-、*、/のボタンを押下する
    $calculation = @{
        "sum" = [string]"plusButton"
        "subtract" = [string]"minusButton"
        "multi" = [string]"multiplyButton"
        "div" = [string]"divideButton"
    }
    $selectbutton = $calculation[$key]
    $wndw = Get-UiaWindow -Name '電卓'
    $wndw | Get-UiaButton -AutomationId $selectbutton | Invoke-UiaButtonClick | Out-Null
}

function click_equalbutton { #"="ボタンを押下する
    $wndw = Get-UiaWindow -Name '電卓'
    $wndw | Get-UiaButton -AutomationId "equalButton" | Invoke-UiaButtonClick | Out-Null
}

function reading_result_display_area { #結果表示エリアに表示されている数値を取得する
    $wndw = Get-UiaWindow -Name '電卓'
    $wndw | Get-UiaText -AutomationId 'CalculatorResults' | Read-UIAControlName #電卓の計算結果表示エリアに表示されている数値を取得する
}

```
</div></details>

<details><summary>サンプルテストコード</summary><div>

```powershell:sample.Tests.ps1
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
. "$here\$sut"

Describe "動作確認" {
    Start-Process calc
    It "電卓ボタン1を押下すると、結果表示が1になること" {
        click_numbutton_one| Should Be "表示は 1 です"
    }
    Stop-Process -name Calculator
}
Describe "数字ボタンの動作確認" {
    Start-Process calc
    It "電卓ボタン2を押下すると、結果表示が2になること" {
        click_numbutton "2"
        reading_result_display_area | Should Be "表示は 2 です"
    }
    Stop-Process -name Calculator
}

Describe "足し算" {
    Start-Process calc
    It "10と10を足すと20になること" {
        click_numbutton "10"
        Four_law_calculation_button "sum"
        click_numbutton "10"
        click_equalbutton
        reading_result_display_area | Should Be "表示は 20 です"
    }
    Stop-Process -name Calculator
}

Describe "引き算" {
    Start-Process calc
    It "100から99を引くと1になること" {
        click_numbutton "100"
        Four_law_calculation_button "subtract"
        click_numbutton "99"
        click_equalbutton
        reading_result_display_area | Should Be "表示は 1 です"
    }
    Stop-Process -name Calculator
}


Describe "multi" {
    Start-Process calc
    It "25と100を書けると2500になること" {
        click_numbutton "25"
        Four_law_calculation_button "multi"
        click_numbutton "100"
        click_equalbutton
        reading_result_display_area | Should Be "表示は 2,500 です"
    }
    Stop-Process -name Calculator
}


Describe "div" {
    Start-Process calc
    It "1000000から2を割ると500000になること" {
        click_numbutton "1000000"
        Four_law_calculation_button "div"
        click_numbutton "2"
        click_equalbutton
        reading_result_display_area | Should Be "表示は 500,000 です"
    }
    Stop-Process -name Calculator
}
```
</div></details>

[GitHub](https://github.com/tohru-ymgc/Pester/tree/main/uiautomation)にも置いておきます。

#使ってみた所感
普段、seleniumやplaywrightを使っている人であれば、特に問題なく実装できるのかなと感じました。
ただ、パイプを使うコードはあまり見ないので、最初はやりにくいかもしれないです。
また文中にも述べましたが、UI Automation PowerShell Extentionは開発が終了しており、情報量がとても少ないため、高度なことをすると苦労する可能性があります。
業務で活用するのは厳しいかなというのが正直なところです。

だだ、powershellスクリプトの練習やテスト自動化の練習には活用できると思います。(環境構築も展開するだけでできるので、簡単に始めれると思います。)
webアプリの自動化に飽きたら、たまにはこちらも試してみてはいかがでしょうか。

# 参考資料
- [UI Automation PowerShell Extensionsを使ってみる](https://www.code-lab.net/?p=21128)
- [UI Automation PowerShell ExtensionsのDownload(再配布)](https://www.code-lab.net/?page_id=21648)
- [Pester公式ページ](https://pester.dev/)
- https://github.com/pester/Pester
- [PowerShellでユニットテスト(Pester)](https://qiita.com/Kosen-amai/items/1f36ce59a768e7f9e869)
- [PowerShellでUI Automationを使用する方法](https://www.ochiaimitsuo.com/3066/)
- [PowerShell でRPA（UI Automation PowerShell Extensions活用）](https://qiita.com/maohara/items/82afbccdd3ae62c05dd9)