【C#】.NET デスクトップアプリ向けE2Eテスト用ライブラリを実装しました

## 背景・動機

.NET デスクトップアプリでE2Eテストを実施したいと思った時、Webを検索してみてたどり着く先は[WinAppDriver](https://github.com/microsoft/WinAppDriver)だと思うのですが、それを使っていざE2Eテスト実装してみると色々面倒だったので、簡単にE2Eテストを導入するためのライブラリを作りました。

[boilersE2E](https://github.com/dhq-boiler/boilersE2E) というライブラリになります。

https://github.com/dhq-boiler/boilersE2E

## 必要機能の洗い出し

E2Eテストを実行するには前提として、C:\Program Files\Windows Application Driver\WinAppDriver.exe が起動されていることが必要です。そしてE2Eテストを実行終了したら、後始末をするべきです。そこで **(1) テスト実行前にC:\Program Files\Windows Application Driver\WinAppDriver.exe を起動** し、**(2) テスト実行後にC:\Program Files\Windows Application Driver\WinAppDriver.exe を終了** するような実装をします。

WinAppDriver を使ってE2Eテストを作る時、C#コードにはAppium.WebDriverというライブラリが必要になります。私はこれをSeleniumの文法に沿ってC#向けにアレンジされたものだと認識しています。つまりSeleniumの方言をそのまま利用できるので、一度でもSeleniumを勉強していればその知識は再利用可能です。

ただし、Appium.WebDriverには要素の検索を実行する際、タイムアウト時間を指定できますが、これはE2Eテスト全体（厳密に言うとWinAppDriver.exe実行単位）に影響を及ぼすので、局所的にタイムアウト時間を短くしたいという要望には答えれていません。そこで、boilersE2Eでは **(3) 個々にタイムアウト時間を指定できる要素の検索メソッド** を実装します。

**(4) Windows Presentation Foundation(WPF) および Windows Forms をサポート** します。

それから自分の開発環境上でE2Eテストを実行する時は何の不便も感じないのですが、これをAzure DevOps のパイプラインでE2Eテストを回そう！というときに大きな障害があります。それはテスト失敗時のデバッグのしにくさです。どうやってデバッグするの？？？という読者の疑問が目に浮かびますが、2つ方法があります。
1つ目はログを記録することです。NLog等のライブラリを使ってテストコード1行ごとに、標準出力や標準エラー出力にログを出力することで、最低どの処理でつまづいたかぐらいは確認することができます。
2つ目はスクリーンショットを取ることです。はて、スクリーンショットを取ったところで、一体その画像ファイルはどこで見れるの？という疑問が生じるかと思いますが、MsTestとNUnitにはテスト結果に任意のファイルを添付することが可能です。（xUnitについてはv3でその機能が実装されるようです。いつv3がリリースされるのか、待ち遠しいですね。詳細は以下のリンクを残しておきます。
[xUnit v3 Roadmap](https://github.com/xunit/xunit/issues/2133)
[Add outgoing metadata to TestContext](https://github.com/xunit/xunit/issues/2457) ）

上記の内容の詳細は1年前に解説してたので、よろしければそちらもご覧ください。

https://qiita.com/dhq_boiler/items/b38d56ab8316eef95b36

boilersE2Eとしてはとりあえず **(5) MsTestとNUnitについてはスクリーンショットの添付が可能** なので、実装しておきます。実装方法が気になる方はソースを覗いてみてください。E2ETestFixtureクラスのTakeScreenshot(string filename)がそれです。

なお、今回はテスト結果への添付の対象としては、スクリーンショット限定としていますが、任意のファイルフォーマットで添付することは可能なので、必要に迫られたら、実装する感じでしょうかね。

## boilersE2E の機能

(1) テスト実行前にC:\Program Files\Windows Application Driver\WinAppDriver.exe を起動
(2) テスト実行後にC:\Program Files\Windows Application Driver\WinAppDriver.exe を終了
(3) 個々にタイムアウト時間を指定できる要素の検索メソッド
(4) WPF および Windows Forms のサポート
(5) （MsTest, NUnit対象）スクリーンショットの添付

## boilersE2E の使い方

3 各種設定については後日加筆します。
お急ぎの方は、詳しくはGitHubのREADME.mdをご覧ください。

https://github.com/dhq-boiler/boilersE2E/blob/dd38c0b7539944af5ce92074cbdfaad4cd43f301/README.md

### すべてのテストフレームワークで必須

1. [WinAppDriver](https://github.com/microsoft/WinAppDriver) をインストールします。

2. E2Eテストを実行するシステムで、boilersE2ETestEnvironmentVariableName に指定した名前の環境変数を作成し、値を true にします。 Azure DevOps pipeline でE2Eテストを実行する場合は、 Windows Application Driver タスクを実行するので false を指定してください。

### MsTestユーザー向け

2. E2E テストプロジェクトを作成し、Nugetで boilersE2E.Core と boilersE2E.MsTest を追加します。

3. 各種設定（未執筆）

4. テスト実装

### NUnitユーザー向け

2. E2E テストプロジェクトを作成し、Nugetで boilersE2E.Core と boilersE2E.NUnit を追加します。

3. 各種設定（未執筆）

4. テスト実装

### xUnitユーザー向け

2. E2E テストプロジェクトを作成し、Nugetで boilersE2E.Core と boilersE2E.xUnit を追加します。


3. 各種設定（未執筆）

4. テスト実装

## サンプルのE2Eテストを実行してみる

下記はE2Eテストの動作確認のために、仮想敵として実装したWPFの電卓アプリとWindows Formsの電卓アプリです。2022/11/28時点でバージョンに差異があり、Windows Forms版だとペーストに対応しています。WPF版だとペーストには対応していません。そのうちに実装したいと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4734/d7bc5f18-9f09-f380-507d-5049c0c9098b.png)

https://github.com/dhq-boiler/boilersE2E/tree/develop/WPFApp

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4734/d7678d13-8d7a-8872-61d4-0d93be3c6d40.png)

https://github.com/dhq-boiler/boilersE2E/tree/develop/WindowsFormsApp

これらについて、テストフレームワーク毎にE2Eテストを実装したものが、下記になります。

#### boilersE2E ユニットテスト（E2Eテスト）のコード

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4734/b0e88b0a-b603-e1e6-ab88-6b7b0c29e3ff.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4734/060becab-ef34-f26f-ccdd-8afb2a751b33.png)

https://github.com/dhq-boiler/boilersE2E/tree/develop/boilersE2E.MsTest.Test

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4734/ac9237bd-c30d-7cad-a714-840b971e05a4.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4734/98b30d7d-7815-0491-98f5-0776e168d247.png)

https://github.com/dhq-boiler/boilersE2E/tree/develop/boilersE2E.NUnit.Test

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4734/2ca5ec4e-af47-7155-ce0e-d38bb3844615.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4734/3107d42b-3733-1c6b-9058-54c11640c471.png)

https://github.com/dhq-boiler/boilersE2E/tree/develop/boilersE2E.xUnit.Test

ご覧の通りですが、WPFとWinFormsの違いはそんなにありません。WPFでいうAutomationID と WinFormsでいうAccessibleName に同じ名前を採用しているので、ほぼ使い回しができるのです。（頭文字が一部大文字になったりはしましたが）

テストフレームワークによるテストの書き方に違いがあるのは当然ですが、boilersE2Eを実装した時は結構差があるのに驚きました。特にxUnitでOneTimeSetUpやOneTimeTearDown相当の機能がインターフェースで実装しなければならないことに驚きました。

投げやりにはなるのですが、E2Eテストの書き方、実装方法の詳細はGitHubのサンプルコードをご確認いただければと思います。

.NET 7.0 の xUnit によるE2Eテストを実施したものが下記になります。ちゃんと動いているでしょう？

![e2etest_xUnit.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4734/2aadeb2f-00b1-0904-3131-6420657b4b3f.gif)

## まとめ

というわけで、boilersE2E の紹介記事を書きました。

みんなに使ってもらえるといいなぁ。。。

### 達成したこと

.NET DesktopでのWinAppDriverを使ったE2Eテストの易記述性に寄与できた。

### 今後

.NET MAUIでのE2Eテストをサポートしたい。