Xamarin.Forms AndroidプロジェクトでAppCenterへの連携とローカルでUITest

はじめに

Xamarin.UITestをそろそろ行わないといけないかなーと思い、どうせやるならAppCenterで複数デバイスでテストしたいよねとかCIに組み込みたいよねとか夢を膨らませてやってみたら思いのほか設定方法とかで手間取ったので記事にまとめますた。 長いので

の3記事に分けますた。適当に必要なところだけ見てください。

ということでこの記事ではVSでXamarin.Formsプロジェクト作成、そこにAppCenterへAnalytics、Diagnosticsデータを連携するところとUITestを実装するところをやっていきます。 とりあえずAndroidだけです。iOSの方はまためんどくさそうなのでまたの機会に( ^ω^)・・・

Xamarin.Formsプロジェクト作成

まずXamari.Formsプロジェクトを作成します。

Xamarin.FormsアプリケーションでテンプレートはBlankApp、プラットフォームはAndroidと今回は使いませんがiOS、.NET Standardでのコードシェアとしました。

機能を確かめるための簡単なアプリとして次のようなものを作成します。

  • 起動すると機能確認用のボタンが並んだページが表示。
  • TOSECONDボタン:次の画面に遷移。UITestでの動作確認用
  • ONPROGRESSボタン:AppCenterのAnalyticsのEventを送信。
  • RAISE EXボタン:例外を発生させアプリをクラッシュさせる。
  • LOG EXボタン:例外を発生させるがキャッチしてAppCenterのDiagnosticsのErrorを送信。

起動画面

次の画面

ソースはこちらに置いておきます。

AppCenterとのAnalytics/Diagnostics連携

AppCenterアカウント作成

appcenter.msでAppCenterのアカウントを作成します。作成直後は以下のような画面になります。

AppCenter-App作成

AppCenterでアプリを管理するためのAppを作成します。これはプラットフォームごとに作る必要があるので同じ目的のアプリでも別々に作る必要があります。 App nameは適当なもの設定,OS=Android,Platform=XamarinとしてAdd new appを押します。

GetttingStartの画面になります。Xamarin.Formsを選ぶとXamarin.Formsプロジェクトでどこに何を設定したらいいかが出てきます。

ちなみに左上の紫色のボタン?を押すとホーム画面となり管理しているアプリの一覧が見れます。

AppCenterに関するコード追加

Nuget追加

Manage Nuget Package for SolutionよりMicrosoft.AppCenter.CrashesとMicrosoft.AppCenter.Analyticsを追加します。 今回は.NET Standadのプロジェクトでも使うのでAndroidプロジェクトとともにそちらもチェックします。

App.xaml.cs

MainPageは画面遷移ができるよう、NavigationPageでくるみます。 AppCenterのGetting Startedに表示されるコードをコピペしてOnStartに貼り付けます。とりあえずiOSやuwpはつかなわないのでその部分は削除しました。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
using Xamarin.Forms;

namespace XFormCI
{
    public partial class App : Application
    {
        public App ()
        {
            InitializeComponent();

            MainPage = new NavigationPage(new MainPage());
        }

        protected override void OnStart ()
        {
            // Handle when your app starts
            AppCenter.Start("android=16ce8137-548f-43bb-b8fc-4b0b2695419f;",
                typeof(Analytics), typeof(Crashes));
        }

        protected override void OnSleep ()
        {
            // Handle when your app sleeps
        }

        protected override void OnResume ()
        {
            // Handle when your app resumes
        }
    }
}
MainPage.xaml、SecondPage.xaml

後でUITestの時に使うAutomtionIdをMainPageのToSecondBtnとSecondPageのLabelに追加します。

MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
  x:Class="XFormCI.MainPage"
  xmlns="http://xamarin.com/schemas/2014/forms"
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  xmlns:local="clr-namespace:XFormCI"
  BackgroundColor="Orange">
  <StackLayout>
    <Label
      HorizontalOptions="Center"
      Text="Welcome to Xamarin.Forms!"
      VerticalOptions="Center" />
    <Button
      AutomationId="ToSecondBtn"
      Clicked="ToSecondClicked"
      Text="ToSecond" />
    <Button
      BackgroundColor="Red"
      Clicked="OnProgress"
      Text="OnProgress"
      TextColor="White" />
    <Button
      BackgroundColor="DarkOrange"
      Clicked="OnRaiseEx"
      Text="Raise Ex"
      TextColor="White" />
    <Button
      BackgroundColor="CornflowerBlue"
      Clicked="OnLogEx"
      Text="Log Ex"
      TextColor="White" />
  </StackLayout>
</ContentPage>

SecondPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
  x:Class="XFormCI.SecondPage"
  xmlns="http://xamarin.com/schemas/2014/forms"
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  BackgroundColor="SkyBlue">
  <ContentPage.Content>
    <StackLayout>
      <Label
        AutomationId="SecondPageText"
        HorizontalOptions="CenterAndExpand"
        Text="Welcome to Xamarin.Forms!"
        VerticalOptions="CenterAndExpand" />
    </StackLayout>
  </ContentPage.Content>
</ContentPage>
MainPage.xaml.cs

ToSecondClickedでSecondPageに遷移します。 OnProgressでAnalytics.TrackEventを呼んでAppCenterへAnalyticsのEventを送信します。 OnRaiseExで未ハンドルの例外を上げてアプリをクラッシュさせます。これはのちの起動後にAppCenterへCrashとして送信されます。 OnLogExでCrashes.TrackErrorを呼んでAppCenterへDiagnosticsのErrorを送信します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;

namespace XFormCI
{
    public partial class MainPage : ContentPage
    {
        private int _counter;
        private int _logExCounter;

        public MainPage()
        {
            InitializeComponent();
        }

        private void ToSecondClicked(object sender, EventArgs e)
        {
            (Application.Current.MainPage as NavigationPage)?.PushAsync(new SecondPage());
        }

        private void OnProgress(object sender, EventArgs e)
        {
            Analytics.TrackEvent("ReportProgress",
                new Dictionary<string, string>() {{"counter", (_counter++).ToString()}});
        }

        private void OnRaiseEx(object sender, EventArgs e)
        {
            throw new NotImplementedException();
        }

        private void OnLogEx(object sender, EventArgs e)
        {
            try
            {
                throw new ArithmeticException();
            }
            catch (Exception ex)
            {
                Crashes.TrackError(ex,
                    new Dictionary<string, string>() {{"logExCounter", (_logExCounter++).ToString()}});
            }
        }
    }
}

ちなみに自分の環境ではVisualStudio2017の15.6.Xのバグなのかわかりませんが、

<AndroidSdkBuildToolsVersion>23.0.3</AndroidSdkBuildToolsVersion>

をXamarin.Androidの最初のItemGroupに追加しないとDebugでもうまく実機にDeployできない現象が起きるのでここで追加しておきます。

AppCenterにAnalytics/EventやDiagnostics/Crash・Errorが送信されるのを確認

いまAppCenterでAnalyticsやDiagnosticsを見ても何も表示されません。

起動してOnProgress、OnLogEx、OnRaiseExを押します。OnRaiseExを押すとクラッシュしますのでアプリを再起動しましょう。 そうするとAnalyticsのEventややDiagnosticsのCrash、Errorに情報が上がるようになります。 本来は一度行えばすぐ表示されるべきはずな気がしますが、CrashやErrorなどは何度か行い画面を開きなおさないと表示されないなどとなりましたのでうまく反映されない場合は何度か繰り返してみてください。このテスト中Edgeを使っていたのですが、その後もEdgeだと何かうまく動いていない風なところがありましたのでその辺が関係してるのかもしれません。

Analytics/Overview

Analytics/Events

Diagnostics/Crashes

Diagnostics/Errors

またこれらの情報はAnalytics/Log flowで大体リアルタイムで見ることができます。どちらかというとデバッグや動作確認用でしょうか。Log flowはEventを送信した後3-4秒で反映されるのではと思います。ほかの画面はリフレッシュしないと更新されませんかね。 またLogFlowはEdgeだと更新がうまく行われずChromeではうまく言ったなどありましたのでブラウザの違いも影響あるかもしれないので注意してください。 Analytics/Log flow

VSよりUITest実行

AppCenterへの連携ができましたので今度はUITestを行えるようにします。

UITestで使う用のAPK作成

デフォルトでは実機にDeployを行った際にapkファイルがbin/Debugなどに作られていると思いますが、ない場合はArchiveで作るなど何らかの方法でapkファイルを作成してください。 ちなみにDebugで作成したapkをUITestで使いたい場合はAndroid OptionのUse Shared Runtimeをオフにしないと怒られます。

UITestプロジェクト作成

ソリューションにUI Test App(Xamarin.UITest|Cross-Platform)プロジェクトを追加します。

UITest実行できるように設定。

AppInitializer.cs

VSでテスト実行できるよう、ApkFileを指定します。ちなみにこの記述はそのままでAppCenterでのUITestが行えるようです。 EnableLocalScreenShotsも指定します。

using System;
using System.IO;
using System.Linq;
using Xamarin.UITest;
using Xamarin.UITest.Queries;

namespace UITest1
{
    public class AppInitializer
    {
        public static IApp StartApp(Platform platform)
        {
            if (platform == Platform.Android)
            {
                return ConfigureApp
                    .Android
                    .ApkFile(@"D:\MyDocuments\cvs\XFormCI\XFormCI\XFormCI.Android\bin\Debug\com.companyname.XFormCI-Signed.apk")
                    .EnableLocalScreenshots()
                    .StartApp();
            }

            return ConfigureApp
                .iOS
                .StartApp();
        }
    }
}
Tests.cs

TestFixtureのiOSコメントアウトします。 デフォルトでAppLaunchesが作られていると思いますが、そこにapp.Repl()と記述します。

using System;
using System.IO;
using System.Linq;
using NUnit.Framework;
using Xamarin.UITest;
using Xamarin.UITest.Queries;

namespace UITest1
{
    [TestFixture(Platform.Android)]
//    [TestFixture(Platform.iOS)]
    public class Tests
    {
        IApp app;
        Platform platform;

        public Tests(Platform platform)
        {
            this.platform = platform;
        }

        [SetUp]
        public void BeforeEachTest()
        {
            app = AppInitializer.StartApp(platform);
        }

        [Test]
        public void AppLaunches()
        {
            app.Repl();
        }
    }
}
Replの実行

ソリューションエクスプローラーのUITestプロジェクトを右クリックしてRun Unit Testsを押します。

初回はアプリが起動するまで時間がかかるかもしれませんのでVSのOutputウィンドウのTestやUnit Test Sessionsの画面などでRunning状態なのかエラーで止まっているのかなど確認してください。 やがてアプリが起動するとともにReplウィンドウが表示されます。 Replウィンドウが起動後treeを実行してエレメントツリーを表示すると次のようになります。

Replを使ってXamarin.UITestのコマンドのテストを行いつつUITestを記述していくことになるのではと思います。 またスクリーンショットはUITest1/bin/Debugの下に作成されます。複数のテストでスクリーンショットを取った場合全部同じファイル名になってしまう気がするんですが、これは何か買える方法あるんですかね…

画面遷移のテスト

app.Repl()をコメントアウトしてSecondPageに遷移することを確認するテストToSecondを追加します。テスト内容としてはSecondPageに遷移するボタンをタップし、SecondPageの中にあるUI要素がエレメントツリーに現れるのを待つというものです。 コードは次のようになります。

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

        [Test]
        public void ToSecond()
        {
            app.Tap(x => x.Marked("ToSecondBtn"));
            app.WaitForElement(x => x.Marked("SecondPageText"));
            app.Screenshot("Second screen.");
        }

こちらも右クリックからテストを実行して動作を確認します。

終わりに

これで素のXamarin.FormsプロジェクトからAnalytics/DiagnosticsをAppCenterに連携させ、UITestもローカルで実行できるようになりました。 次はAppCenterで今やったUITestを複数のデバイスを使って行えるようにします。->続き