Xamarin
Xamarin.Forms

Xamarin.UITest を試す (Visual Studio で) (Xamarin.Forms で)

Xamarin.UITest を使ってみました。ただし Windows の Visual Studio で、 Xamarin.Forms で、 Android *1 です。だいぶターゲットが狭くなってしまいました。

*1 iOS のテストを実行しようとすると iOS tests are not supported on Windows. というエラーになります。

公式ドキュメント

公式ドキュメントはこのあたりだと思います。

始めるまで

ソリューションに UITest プロジェクトを追加

  1. ソリューションを右クリック > 追加 > プロジェクト
  2. Cross-Platform カテゴリ または Test カテゴリの中から UI Test App (Xamarin.UITest | Cross-Platform) プロジェクトを追加する

UITest プロジェクトからテスト対象となる Droid プロジェクトを参照

  1. UITest プロジェクトの参照を右クリック > 参照を追加
  2. テスト対象となる Droid プロジェクトをチェックして追加

AppInitializer クラスの StartApp メソッドを記述

  1. ConfigureApp.Android()StartApp() の間に InstalledApp("MY_PACKAGE_NAME") を挿入
  2. 同様に EnableLocalScreenshots() も挿入

MY_PACKAGE_NAME は Droid プロジェクトを右クリック > プロパティ > Android Manifest > Package name の文字列です。

なお、公式は InstalledApp() ではなく、 ApkFile() で .apk ファイルを指定する方法を推しているようです。定かではないのですが、 ApkFile() は Release ビルドしか扱えないっぽいのが難点で。 Release ビルドは本番環境的なところにつながってしまうので、 Debug の方でテストしたい場合が多いんですよね... .InstalledApp("MY_PACKAGE_NAME") の代わりに、 .ApkFile("../../../MyApp.Droid/bin/Debug/com.example.myapp.apk") としても大丈夫です。

テスト対象となる Droid プロジェクトを配置する

  • 右クリック > 配置

以後、 Droid プロジェクトを変更したら配置し直します。

最初のテスト実行

  1. テスト エクスプローラーを開きます。(テスト > ウィンドウ > テスト エクスプローラー)

  2. テスト エクスプローラーにテスト項目が出ていない場合はもう一度ソリューションをビルドしてください。

  3. 実行します。

スクリーンショットを撮るだけのテストがデフォルトで用意されているはずです。とりあえずこれを育てていきます。

Tests.cs
[Test]
public void AppLaunches()
{
  app.Screenshot("First screen.");
}

テストを追加するには、新しくメソッドを作り、 [Test] をつけます。テスト プロジェクトをビルドすればテスト エクスプローラーに現れます。

テストの記述

今作っているアプリの全画面を一通り表示させることを目指してみました。

AutomationId 指定

LoginPage.xaml
<Entry
  x:Name="UserName"
  AutomationId="username_entry" />
Tests.cs
app.EnterText("username_entry", "ynakamura@sample.com");

こんなふうにビューに AutomationId を付けて識別することを公式は推奨しているようです。でも、テストのために既存の XAML に手を入れるのはなるべく避けたいと思って...

入力

入力するビューを識別するには、そのページの入力の何番目というふうに指定するのが楽です。

app.ClearText(c => c.TextField().Index(1));
app.EnterText(c => c.TextField().Index(1), "password");

タップ

タップするボタンを識別するには、表示テキストを指定するのが楽ですし、テストのコードを見たときにわかりやすいと思います。

app.Tap(c => c.Text("ログイン"));

タブ移動

タブ移動は app.SwipeRightToLeft(); 等でもいいですし、上の応用で Text() でタブ識別して Tap() でもいけます。

DisplayAlert の OK ボタン

DisplayAlert の OK ボタンのように AutomationId を埋め込めないやつも Text() ならいけるのです。汎用性高い :)

app.Tap(c => c.Text("OK"));

Switch

Switch には専用のメソッドが用意されています。1ページに複数の Switch が並んでいるときは Index() で識別します。

app.Tap(c => c.Switch().Index(0));

Class 指定

テキストを持たないものは Class() でクラス名を指定するという方法があります。

Image をタップします。

app.Tap(c => c.Class("FormsImageView").Index(0)); // Image

SearchBar で検索します。

app.EnterText(c => c.Class("SearchView"), "テスト"); // SearchBar
app.PressEnter();

ListView のアイテムをタップするには Child() を併せて使います。

app.Tap(c => c.Class("ListView").Child().Index(0)); // ListViewItem

Navbar関連

左上のハンバーガー ボタンをタップします。戻る矢印ボタンに変わっても同じコードでタップできます。Android にはせっかくバック ボタンがあるので、戻るのは app.Back(); でもいいですね。 ハンバーガーボタンのクラスが、いつからか分かりませんが、変わったようです。

//app.Tap(c => c.Class("ImageButton").Index(0));  // ハンバーガー ボタン
app.Tap(c => c.Class("AppCompatImageButton").Index(0));

ToolbarItem の secondary をタップするには、リスト展開と項目選択の二段階です。項目の選択には Text() が使えます。

app.Tap(c => c.Class("OverflowMenuButton"));  // 展開
app.Tap(c => c.Text("更新"));

関数化

よく使う処理をまとめて関数にして呼び出すこともできます。

public void Login(string username, string password)
{
  app.ClearText(c => c.TextField().Index(0));
  app.EnterText(c => c.TextField().Index(0), username);
  app.ClearText(c => c.TextField().Index(1));
  app.EnterText(c => c.TextField().Index(1), password);
  app.Tap(c => c.Text("ログイン"));
}

[Test]
public void Hoge()
{
  Login("ynakamura@example.com", "password");
  //...
}

トラブルシューティング

コードを直したはずなのに直ってない

私の場合はたいてい配置を忘れています。

Found 2 connected Android devices. で失敗する

Adb Server を再起動してください。 (ツール > Android > Restart Adb Server)

Class で指定するクラス名が分からない

私はテストに次のようなコードを埋め込んで、表示中の要素を列挙して調べました。

foreach (var item in app.Query(c => c.All()))
  Console.WriteLine(item.Class + "/" + item.Id + "/" + item.Label + "/" + item.Text);

AppQuery は c で受けるの?

c 派と q 派と、あと x 派が少しいるみたいです。

UITest のログファイルに Potential Android SDK location: (No path) - Not set. [ Source: ANDROID_HOME ] と出て失敗する

Windows の場合、システムの詳細設定で次の環境変数を追加し、

変数
ANDROID_HOME C:\Program Files (x86)\Android\android-sdk

Path に次の値を追加します。

  • %ANDROID_HOME%\tools
  • %ANDROID_HOME%\platform-tools