これは何?

AndroidアプリをCircleCIでCIする。」のUnit Testを説明した記事です。 サンプルアプリのUnit Testについて解説していきます。

概要

CIの流れでUnit Testが実行されている箇所

AndroidアプリをCircleCIでCIする。」のCIの流れでは (2) Build で実行されています。

ここで使うコードは以下の通りです。

公式ドキュメントはこれです。

ポイント、条件など

Unit Testについて(AndroidアプリをCircleCIでCIする。)にも書いていますがポイントは以下です。

  • MVP(Model-View-Presenter)のアーキテクチャに対してのUnit Testを実行する
  • JUnitを使ってUnit Testを実行する
  • MockitoでViewをモックする
  • HTTPでのアクセスをモックする
  • テストする場所は以下の図の Point for Unit Testing

アーキテクチャはMVPに限っている訳ではありませんが、Interfaceを定義しているとテストを書くのが楽になることから ここではMVPを採用しています。

テストのシナリオ

アプリの機能としては無駄に実装されたJSONPlaceholder のREST APIに接続をしてJSONを取得し、Viewに反映させる部分の動作に関してテストを実施します。
具体的に説明すると上図の Data Interaction をモックしてJSONを固定し、UI Behavior をモックして 指定されている振る舞いを行うか、を確認します。
よって、今回はこのシナリオを確認する為に以下の2つのテストのケースを用意しました。

  • HTTPレスポンスコード200でJSONを正しく受け取った場合の値の確認とViewに対する動作確認
  • HTTPレスポンスコード500を受け取った場合のViewに対する動作確認

準備

build.gradleに追記、org.mockito.plugins.MockMaker の作成の2つが必要です。

build.gradleに追記

app/build.gradle に以下を追記します。

....
dependencies {
....
    // Unit and UI Test
    testImplementation 'junit:junit:4.12'
    testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
    testImplementation "com.android.support:support-annotations:${android.defaultConfig.targetSdkVersion}"
    testImplementation 'org.robolectric:robolectric:4.0'
    testImplementation 'com.nhaarman:mockito-kotlin-kt1.1:1.5.0'
    testImplementation 'com.squareup.retrofit2:converter-moshi:2.2.0'
    testImplementation 'com.squareup.retrofit2:retrofit-mock:2.3.0'
    testImplementation 'com.squareup.okhttp3:mockwebserver:3.10.0'
....
}
....

build.gradle に以下が書かれていることが前提です。

....
buildscript {
....
    ext.kotlin_version = '1.3.31'
....
}
....

org.mockito.plugins.MockMaker を作成

app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker を新規で作成して、以下の1行を書き込みます。

mock-maker-inline

Unit Testを書く

> REST APIをモックして出力を固定する

Data Interaction の部分をモックして出力を固定します。具体的にはJSONPlaceholderのREST APIの出力を固定する為に HTTPのクライアントOkHttpをモックします。 扱いやすいように、 Unit Test本体 と、 モック を1つのファイルにまとめちゃっています。

MockServerDispatcherのクラスがそれになります。
ここでは、Resp200Resp400Resp500 の3つのClassを定義することで、以下の用に出力を固定します。

  • Resp200:正常系の場合で、JSONを返す
  • Resp400:異常系で、HTTP Statusの400を返す
  • Resp500:異常系で、HTTP Statusの500を返す

> Unit Testを書く

2つのテストケースのうちの「HTTPレスポンスコード200でJSONを正しく受け取った場合の値の確認とViewに対する動作の確認」 の説明を、以下にインラインで行います。。 以下は、

class MainActivityUnitTest {
    private var mMockTestUtils = UnitTestUtils()

    @Test
    fun sampleUnitDataFetchSuccessTest() {
        // vvv 正常系でJSONを返すよう指定
        mMockTestUtils.mockServerBehaviorSwitcher = {
            MockServerDispatcher().Resp200()
        }
        val expectedResponse = MockServerDispatcher().mockedResponse

        var mMainActivityPresenter = MainActivityPresenter()
        // vvv Viewに対する動作の確認の為にMainActivityViewContract()のClassをモック
        var mMainActivityViewContract = mock<MainActivityViewContract>()

        // vvv PresentorにモックしたMainActivityViewContract()を叩かせるようにセット
        mMainActivityPresenter.setView(mMainActivityViewContract)

        // vvv 非同期での処理を同期で動作させるように変更
        mMockTestUtils.prepareRxForTesting()

        // vvv ここでMockしたサーバを起動する
        mMockTestUtils.startMockServer()

        // vvv 以下の2行で、サンプルアプリの接続先をMockしたサーバに入れ替える
        var mApiConnection = mMockTestUtils.setupMockServer()
        setConnection(mApiConnection)

        // vvv PresenterのMethodを叩いて、動作させる
        mMainActivityPresenter.getJsonSampleResponse()
        // vvv MainActivityViewContractのhandleSuccess()が叩かれれ、
        // 固定したデータ(JSON)の取得ができているかを確認
        argumentCaptor<Array<SinglePostResponse>>().apply {
            verify(mMainActivityViewContract, times(1)).handleSuccess(capture())

            for (i in 0 until expectedResponse.size) {
                val expected = expectedResponse[i]
                val actual = this.firstValue[i]
                mMockTestUtils.assertDataClass(expected, actual)
            }

        }
        // vvv ここでMockしたサーバを停止
        mMockTestUtils.shutdownMockServer()
    }
....
}

実行してみる

実行方法には「コマンドラインから」、また「Android Studioから」の2つがあります。

> コマンドラインから実行

Unit Testの全てを実行する

$ ./gradlew testDebugUnitTest;

指定したClass、MethodのUnit Testを実行る

// Classを指定
// --tests のパラメータとして [Package Name].[Class Name] を指定
$ ./gradlew testDebugUnitTest --tests com.example.uitestsample.MainActivityUnitTest;

// ClassとMethodを指定
// --tests のパラメータとして [Package Name].[Class Name].[Method Name] を指定
$ ./gradlew testDebugUnitTest --tests com.example.uitestsample.MainActivityUnitTest.sampleUnitDataFetchSuccessTest;

> Android Studioから

@Testのアノテーションかを書くとTestとして認識されます。 するとAndroid Studioだと下図の赤丸のように、その左側に ▶︎(再生マーク)が表示されるので、 それをクリックすると実行することが可能です。

おわりに

「Cloneしたらすぐに試せる」を目標に書きましたので、興味を持たれた方は試してみていただえると嬉しいです。
間違ってる!とかありましたらPR、またご指摘ください。

これは何?

AndroidアプリのCI(継続的インテグレーション)環境を作って運用をしたときの経験を書き出したものです。 2019年7月時点でのものになりますので、時間経過によっては動作しない可能性もありますので予めご了承ください。

更新履歴

概要

AndroidアプリのGitHubへのPushから、CircleCIでビルド、単体テスト、UIテスト(UI Animator & Espresso on Firebase Test Lab)、 そしてDeployGateへアプリをデプロイする、までの一連の流れの雛形のようなものだと考えてください。
また記事の内容は以下のように分割して書いていく予定です。

この記事は「テストケース、詳細ははともあれ、CircleCIでCIを回してみる」についてとなります。
動作に必須な設定等の説明をして、ローカル環境(手元のPC)からコマンドを使って手動で動作させ正常に動作するかすかの確認を行い、 その後に一連のビルドのプロセスをCircleCIで動かす、というの流れで説明します。

ビルドの流れとインフラ

以下の図の流れでビルドからデプロイまで行います。

  1. 開発者がGitHubにコードをCommitしPushする
  2. CircleCIでビルドのプロセスが開始され、Unitテストが実行される
  3. UIテスト実行の為、CicleCIがFirebaseに向けてアプリ、テストケースを配信しUIテストを実行する
  4. Firebase Test LabでUIテストが実行が完了したらレポートをCircleCIに配信する
  5. DeployGateに向けてアプリを配信する

利用するインフラ

上記のプロセスを実行する為に、以下のインフラを利用します。おなじみの名前ばかりかもしれませんが・・・

  • GitHub

    Gitで操作するリポジトリを提供するクラウドサービス

  • CircleCI

    CIを行ってくれるクラウドサービス

  • Firebase Test Lab

    Google社が提供するmBaaSが提供するサービスの1つで、UIテスト(Espresso、UI Automator 2.0、XCTest)をクラウドで行うサービス

  • Deploy Gate

    ストア(Google Play、App Store)を通さないアプリ配布を実現するサービス(ベータテスト等に利用できる)

今回利用するAndroidのサンプルアプリ

> サンプルアプリのコード

Unit Test、UI Testを行う為に強引に実装している部分があります。

> 機能の説明

サンプルアプリの機能は以下の通りです。

  • 画面右下の赤色のFabをタップするとデバイスへのファイル書き込みの許可を求められる。
  • デバイスへのファイル書き込みの許可の状態に関係なく、画面中央の文字列「Hello World!!」が「1」に変化する
  • 更に、画面右下の赤色のFabをタップすると、タップ毎に1つづつインクリメントされた数字が表示される
  • 画面右上の3点リーダをタップすると「Reset Counter」のボタンが出現し、タップするとカウンタが「0」に変化する

こんな↓動作をするアプリです。

サンプルアプリの実装のアーキテクチャ

サンプルアプリのアーキテクチャはMVP(Model-View-Presenter)で構成されていて、Activityは1つです。
また今回のサンプルアプリの仕様(上記)ですと、Model(DB、API等のデータソース)が必要のないアプリになってしまいますが、 Unit Testの為、外部のREST APIへ接続を行い、データを取得しConsole出力をするロジックが無駄に実装しています。
アーキテクチャは図にすると以下のような構成です。図内では、Classが1つのブロックになっていて、ブロックの上部に白文字はInterfaceです。 例えば、MainActivityPresenterはMainActivityPresenterContractのInterfaceで構成されたClass、 よって、MainActivityInteractorはInterfaceを規定していないClassであることを表しています。

それでは、できるだけサクッとCircleCIでCIを回してみましょう。

事前準備:リポジトリを作成する

実際に動作させる場合は、上記のURLのコードをFork等をして自前で専用のリポジトリGitHubにご用意ください。

Unit Testについて

(関連記事「AndroidアプリでのUnit Testについての解説」)

JUnitを使ってUnit Testを実行します。AndroidでのUnit Testの定番です。 モックはMockitoを使います。 サンプルアプリのUnit TestはPresenterとやり取りを横取りする形で行います。 上の図の 「Point for Unit Testing」 と書かれた矢印のポイントがそこです。

Unit Testの概要

コードはこのディレクトリに配置しています。

Unit Testのスクリプトはこのファイルです。

共通で使うであろう機能をMethod化して集めたClassがこちら。

テストとしては、先程説明したModelにアプリの動作に対しては無駄に実装したREST APIへ接続するロジックを使います。 REST APIからデータを取得し、取得したデータを元に正しくViewに反映される動作をするかの確認を行うのが目的です。
サンプルアプリの接続先REST APIはJSONPlaceholderです。接続するURLは/comments?postId=1で、postIdが同一であれば常に同じ値のJSONを返してくれます。常に同じJSONを返してくれるとはいえ、Unit Testではより確実性を高めたい、つまり、相手のサーバの状態に関係なく確実に同じJSONを取得できることを保証したいです。ですので、このUnit TestではMockitoを使ってJSONPlaceholderのAPIをMock(モック)することで確実に同一のJSONを受け取れるようにしています。

テストのケースは2つです。その内容は以下の通りです。

  • HTTPレスポンスコード200でJSONを正しく受け取った場合の値の確認とViewに対する動作の確認
  • HTTPレスポンスコード500を受け取った場合のViewに対する動作確認

なお、Unit Testの書き方(お作法)、テストケース詳細は別エントリのAndroidアプリでのUnit Testについての解説で説明しています。

Unit Testをローカル環境で動作させてみる

手元で動作させてみましょう。Terminalでコードのトップに移動して以下のコマンドを実行すると、こんな出力が出てくるはずです。

$ https://ryoyakawai.com/blog/gradlew :app:testDebugUnitTest;

> Task :app:testDebugUnitTest
com.example.uitestsample.MainActivityUnitTest > sampleUnit500ServerErrorTest PASSED
com.example.uitestsample.MainActivityUnitTest > sampleUnitDataFetchSuccessTest PASSED
com.example.uitestsample.MainActivityUnitTest > sampleUnit400BadRequestTest PASSED

> Task :app:testReleaseUnitTest
com.example.uitestsample.MainActivityUnitTest > sampleUnit500ServerErrorTest PASSED
com.example.uitestsample.MainActivityUnitTest > sampleUnitDataFetchSuccessTest PASSED
com.example.uitestsample.MainActivityUnitTest > sampleUnit400BadRequestTest PASSED

BUILD SUCCESSFUL in 8s
40 actionable tasks: 10 executed, 30 up-to-date

「BUILD SUCCESSFUL in XXs」 が出たら Unit TestはテストケースをすべてSuccessで終了した という意味になります。また、ここでWarning等のメッセージが出た場合、できる限り修正してメッセージが表示されないようにすることをオススメします。
これでUnit Testの準備は完了です。

UI Testについて

EspressoUI Automatorを使っています。それぞれの特徴は以下の通りです。

Espresso

「to write concise, beautiful, and reliable Android UI tests」と公式サイトには説明されています。特定のアプリのUIに対してのスクリプトで動作をさせることを可能にするテストフレームワークです。単一のアプリの操作を自動化する場合に使うとよいでしょう。Google社が開発していますので、Anroidの公式のテストツールと言ってよいでしょう。

UI Automator

「suitable for cross-app functional UI testing across system and installed apps.」と公式サイトに説明されている通りで、Espressoと比べると、よりAndroidのOSに近い側に位置しているテストフレームワークで、複数アプリを行き来するよう動作をスクリプトで定義することの可能です。Espressoとは違い、複数のアプリの操作を自動化する場合に使うとよいでしょう。こちらもEspressoと同じくGoogle社が開発していますので、Anroidの公式のテストツールと言ってよいでしょう。

UI Testの概要

コードはこのディレクトリに配置しています。

UI Testのスクリプトはこのファイルです。

共通で使うであろう機能をMethod化して集めたClassがこちら。

テストのケースは3つです。その内容は以下の通りです。

  • パッケージ名を確認する
  • アプリ起動時の画面の文字列の確認をする
  • アプリ起動後、各ボタンが正しく機能し、画面表示が仕様通り更新されるかを確認する

なお、UI Test(Espresso、UI AUtomator)の書き方(お作法)、テストケース詳細は別エントリの「Espresso, UI Automator„ÅßAndroid„ÅÆUI Test„ÇíÊõ∏„Åè」で説明していますので合わせて御覧ください。

UI Testをローカル環境で動作させてみる

手元で動作させてみましょう。Terminalのコマンドラインからコードのトップディレクトリに移動して以下のコマンドを実行すると、こんな↓が出力が出てくるはずです。

$ ./gradlew :app:connectedAndroidTest; // ← ./gradlew cAT でもOK

> Task :app:connectedDebugAndroidTest
01:52:09 V/ddms: execute: running am get-config
01:52:09 V/ddms: execute 'am get-config' on 'emulator-5554' : EOF hit. Read: -1
01:52:09 V/ddms: execute: returning
01:52:09 D/app-debug.apk: Uploading app-debug.apk onto device 'emulator-5554'
01:52:09 D/Device: Uploading file onto device 'emulator-5554'
....
01:52:13 V/ddms: execute: running pm install -r -t "/data/local/tmp/test-services-1.0.2.apk"
01:52:13 V/ddms: execute 'pm install -r -t "/data/local/tmp/test-services-1.0.2.apk"' on 'emulator-5554' : EOF hit. Read: -1
01:52:13 V/ddms: execute: returning
01:52:13 V/ddms: execute: running rm "/data/local/tmp/test-services-1.0.2.apk"
01:52:13 V/ddms: execute 'rm "/data/local/tmp/test-services-1.0.2.apk"' on 'emulator-5554' : EOF hit. Read: -1
01:52:13 V/ddms: execute: returning
01:52:13 D/app-debug-androidTest.apk: Uploading app-debug-androidTest.apk onto device 'emulator-5554'
01:52:13 D/Device: Uploading file onto device 'emulator-5554'
....
androidx.test.internal.runner.junit3.DelegatingFilterableTestSuite > [API_27_Pixel_2(AVD) - 8.1.0] SKIPPED
01:52:20 V/InstrumentationResultParser: INSTRUMENTATION_STATUS_CODE: -3
01:52:21 V/InstrumentationResultParser: INSTRUMENTATION_STATUS: class=androidx.test.internal.runner.junit3.DelegatingTestSuite
01:52:21 V/InstrumentationResultParser: INSTRUMENTATION_STATUS: current=3
01:52:21 V/InstrumentationResultParser: INSTRUMENTATION_STATUS: id=AndroidJUnitRunner
01:52:21 V/InstrumentationResultParser: INSTRUMENTATION_STATUS: numtests=8
....
01:53:07 V/InstrumentationResultParser: Time: 48.21
01:53:07 V/InstrumentationResultParser:
01:53:07 V/InstrumentationResultParser: OK (3 tests)
....
01:53:07 V/ddms: execute: returning
01:53:07 V/ddms: execute: running pm uninstall com.example.uitestsample.test
01:53:07 V/ddms: execute 'pm uninstall com.example.uitestsample.test' on 'emulator-5554' : EOF hit. Read: -1
01:53:07 V/ddms: execute: returning
01:53:07 V/ddms: execute: running pm uninstall com.example.uitestsample
01:53:07 V/ddms: execute 'pm uninstall com.example.uitestsample' on 'emulator-5554' : EOF hit. Read: -1
01:53:07 V/ddms: execute: returning

BUILD SUCCESSFUL in 1m 1s
51 actionable tasks: 10 executed, 41 up-to-date

Unit Testのときと同じく、「BUILD SUCCESSFUL in XXs」 が出たら UI TestはテストケースをすべてSuccessで終了した という意味になります。また、ここもUnit Testのときと同様にWarning等のメッセージが出てきたら、でいる限り修正することをオススメします。

Firebase Test Labでテストを行う

Firebase Test Labとは

Firebase Test LabはFirebaseがの1つのサービスとして提供されているクラウドでUI Testを行うプラットフォームです。Android(Espresso、UI Automator)、iOS(XCTest)で書かれたテストの実行に対応しています。操作は以下の2つの方法が提供されています。

どちらもの方法を使っても結果はブラウザ上で動作しているFirebaseのConsole(下図↓)から閲覧が可能になります。

ここではGoogle Cloud SDKのCLIからCloud Testing APIを使う方法で実行します。 Google Cloud SDKのCLIから使う場合は以下の2つのAPIを有効にする必須です。必ず以下のリンクから利用するプロジェクトで有効にしてください。

(Firebase Test Labの設定については「Firebase Test LabでUI Testを実行する」で詳しく説明しています。)

Firebase Test Labをローカル環境で動作させてみる

FirebaseでのProjectの設定、GCPのアカウントの準備、Google Cloud SDKのCLIは準備は済んでいると仮定します。
また既にローカルでUIテストの動作確認も済んでいますので、早速UIテストをFirebase Test Labで動かしてみます。
Firebase Test LabでUI Testを実行する」でも詳しく説明しています。)

// Cloud Testing APIを有効にしたアカウントでログインし、CLIの向き先Projectを切替える
$ gcloud auth;
$ gcloud config set project [PROJECT ID];

続いて、Cloud Testing APIでUIテストを実行する為に以下をコンソールで実行します。
環境変数で変数を指定して実行していますが、ここではテキストを入力してもOKです。 CircleCIでは環境変数で指定することが望ましいのでそれに習っています。 この記事内の他の項目でも環境変数を使う理由は同一です。

$ TIME=$(date "+%Y%m%d_%H%M");
$ BK_OBJ_NAME="[保存するバケットのディレクトリ名]/${TIME}[今回のテストを保存するディレクトリ]";
$ GOOGLE_PROJECT_ID="[PROJECT ID]";

// コマンドを改行するときは末尾のスペースを忘れずに入れてください。
$ gcloud firebase test android run \
 --type instrumentation \
 --app ./app/build/outputs/apk/debug/app-debug.apk \
 --test ./app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk \
 --test-targets "class com.example.uitestsample.MainActivityInstrumentedTest" \
 --results-dir $BK_OBJ_NAME \
 --results-bucket cloud-test-${GOOGLE_PROJECT_ID} \
 --directories-to-pull /sdcard/uitest/ \
 --device model=Pixel2,version=26,locale=en_US,orientation=portrait \
 --use-orchestrator \
 --timeout 120s;

実行開始が成功するとFirebaseのConsoleにこんな形↓で1行追加されます。

テストが終了しするとこのような出力がコンソールにされます。

Have questions, feedback, or issues? Get support by visiting:
  https://firebase.google.com/support/

Uploading [./app/build/outputs/apk/debug/app-debug.apk] to Firebase Test Lab...
Uploading [./app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk] to Firebase Test Lab...
Raw results will be stored in your GCS bucket at [https://console.developers.google.com/storage/browser/[PROJECT ID]/cloud-test-uitest-sample-android/20190705-xxxxxx02/]

Test [matrix-3dp8juo0wx533] has been created in the Google Cloud.
Firebase Test Lab will execute your instrumentation test on 1 device(s).
Creating individual test executions...done.

Test results will be streamed to [https://console.firebase.google.com/project/[PROJECT ID]/testlab/histories/bh.xxxxxxxxx/matrices/918190477175429xxxx].
16:25:19 Test is Pending
16:25:40 Starting attempt 1.
16:25:40 Test is Running
16:26:42 Started logcat recording.
16:26:42 Preparing device.
16:27:15 Logging in to Google account on device.
16:27:15 Installing apps.
16:27:28 Retrieving Pre-Test Package Stats information from the device.
16:27:28 Retrieving Performance Environment information from the device.
16:27:28 Started crash detection.
16:27:28 Started crash monitoring.
16:27:28 Started performance monitoring.
16:27:42 Started video recording.
16:27:42 Starting instrumentation test.
16:28:21 Completed instrumentation test.
16:28:34 Stopped performance monitoring.
16:28:41 Stopped crash monitoring.
16:28:47 Stopped logcat recording.
16:28:47 Retrieving Post-test Package Stats information from the device.
16:28:47 Logging out of Google account on device.
16:28:53 Done. Test time = 51 (secs)
16:28:53 Starting results processing. Attempt: 1
16:29:00 Completed results processing. Time taken = 7 (secs)
16:29:00 Test is Finished

Instrumentation testing complete.

More details are available at [https://console.firebase.google.com/project/[PROJECT ID]/testlab/histories/bh.xxxxxxxxx/matrices/918190477175429xxxx].
┌─────────┬──────────────────────────┬─────────────────────┐
│ OUTCOME │     TEST_AXIS_VALUE      │     TEST_DETAILS    │
├─────────┼──────────────────────────┼─────────────────────┤
│ Passed  │ Pixel2-26-en_US-portrait │ 3 test cases passed │
└─────────┴──────────────────────────┴─────────────────────┘

出力されている GCS bucketMore details are availabl at として表示されているURLにアクセスするとテスト結果が書き出されているはずです。閲覧はブラウザから可能です。

DeployGateを準備する

ここを参考に、サインアップアプリをアップロード まで済ませましょう。

DeployGateにローカル環境から配信してみる

DeplotGateのAPI keyを取得

DeployGateのサイトにログインをしてhttps://deploygate.com/settingsの最下段に表示されています。

これを環境変数として設定します。 ついでにユーザID(DeployGateのユーザーID)とAPKへのPathも環境変数に設定してしまいましょう。

$ DEPLOYGATE_API_KEY="[取得したAPI key]";
$ USERNAME="[DeployGateのユーザID]";
$ APK_PATH=app/build/outputs/apk/debug/app-debug.apk;

DeployGateに配信する

以下のコマンドで配信します。メッセージをリッチにするために環境変数を少々追加しています。

$ TIME=$(date "+%Y/%m/%d %H:%M");
$ COMMIT_HASH=$(git log --format="%H" -n 1 | cut -c 1-8);
$ curl -F "[email protected]${APK_PATH}" -F "token=${DEPLOYGATE_API_KEY}" -F "message=Build by CircleCI <${COMMIT_HASH}> (${TIME})" https://deploygate.com/api/users/${USERNAME}/apps

このような出力されるはずです。"error":falseと表示されていたら配信成功です。

{"error":false,"results":{"name":"UITest Sample App","package_name":"com.example.uitestsample","labels":{},"os_name":"Android",..../secure.gravatar.com/avatar/410d1a2cc20ac9675664df7de253156b?s=218\u0026d=mm"}}}

DeployGateのウェブ管理コンソール(URLはhttps://deploygate.com/users/[DeployGateのユーザID]/apps/[アプリのPackage名])では、以下のように赤四角のリストにアイテムが追加されているはずです。


ビルドのプロセスをローカル環境から手動で回すことの確認まで行いましたので、CircleCI上で動かしてみます。

CircleCIの設定をする

.circleci/config.ymlが設定ファイルになります。
動作させるには環境変数としてDEPLOYGATE_API_KEYGCLOUD_SERVICE_KEYGOOGLE_PROJECT_IDの設定が必須です。 それぞれの値の取得方法は以下になります。

DEPLOYGATE_API_KEY

DeployGateのサイトにログインをしてhttps://deploygate.com/settingsの最下段に表示されています。 (上記 「DeplotGateのAPI keyを取得」 の項目で説明しているAPI keyと同じです)

GOOGLE_PROJECT_IDGCLOUD_SERVICE_KEY

GOOGLE_PROJECT_IDはJSON形式のファイルの内容をbase64にした値です。

  1. 下↓の左図のように(1)でPROJECT_IDを選択し(ここで選択した文字列がGOOGLE_PROJECT_IDとなります)、(2)のように [IAM & admin] > [Service account] を選択してアカウントを作成します
  2. 次に[IAM & admin]を表示し、下↓の右図のように先程作成したアカウントの右側の鉛筆マークをクリックして、Firebase Test Lab Admin を追加します
  3. 再度[IAM & admin] > [Service account]を表示して、作成したアカウントの右側にある3点リーダをクリックしてJSONフォーマットのキーを作成しダウンロードします

そしてダウンロードしたJSONフォーマットのキーをbase64形式に書き出します。この文字列をCircleCIに環境変数GCLOUD_SERVICE_KEYとして登録してください。

$ base64 -i [PATH TO JSON FILE];

CicleCIに環境変数として登録

取得した3つの値をDEPLOYGATE_API_KEYGOOGLE_PROJECT_IDGCLOUD_SERVICE_KEYを以下の図のように登録します。
対象のプロジェクトを選択し[Settings]を表示して、左ペインのメニューから[BUILD SETTINGS] > [Environment Variables]に保存します。完了すると以下の図のようになります。 画面上は指定した値の最後の4文字のみ表示された状態となります。

リポジトリにPushしてCircleCI上でBuildを動かす

設定は完了しましたので、準備したリポジトリのMasterブランチにコードをPushします。
PushするとCircleCI上でBuildのプロセスが動き始めます。今後はMasterにPush、またはMergeするとBuildプロセスが走るようになります。

実行結果のレポートを閲覧する

ビルドの結果はCircleCIのサイトから確認することが可能です。成功すると以下のようになるでしょう。
また、Unit Testの結果は Artifacts のタブから確認することが可能です。(下図)

UI Testのも確認は可能です。Successの場合はそれでよいのですが、Failしている場合の詳細結果は下図のようにFirebaseのConsoleから確認してください。また、下図の赤丸内の Test Result をクリックするとその他のデータが閲覧可能となっています。

DeployGateへの配信を確認

「DeployGateに配信する」 の項目での結果のようにリストにアイテムが追加されているはずです。

おわりに

AndroidアプリをCircleCI上でCIする流れを説明してきました。この流れでCIを回していきます。長くなってしまいましたが、いかがでしたでしょうか?
この流れを作るのに多くのサイトにお世話になりました。この記事をご覧になっている方々がサクっとCI環境を作成することができることで、 世界を変えるであろう素晴らしいアプリの開発に時間を注ぐことに少しでもお力になれたら嬉しいです。
なお、今回のUI Testはネットワークアクセスに依存する部分が少なかったのですが、そうでない場合は結果が不安定になりがちですので、通信中なのか、通信は完了しているのかについての何らかの目印をつける、だったりその他の工夫が必要になります。そういったところも何らかの形で共有していきたいと思っています。

それでは、引き続き詳細な解説についてもがんばって書いていきますのでよろしくお願いします。

アメリカン中華の代表格かな?って思ってるバーボンチキン。微妙に焦がすのがポイントな料理。たまに無性に食べたくなるので作ってみました。
もとネタのレシピから調味料(特に、砂糖の量!)をアレンジしています。
もしよかったら作ってみてください!白いご飯に載せて食べると美味しいです😋

バーボンチキン(アメリカン中華?)


2019年5月7〜9日まで、毎年Google社が米国カリフォルニア州で開催している開発者会議「Google I/O」に参加してきました。

その中でも「これは!!」と感じたセッションを紹介していこうと思います。

第一弾はSonic Boom! Audio Programming on Android and Chromeです。
「Step by StepでAndroid、Webでのコーディング方法を追うことができる」という意味で、開発を行うのには非常に参考になると思っています。
デジタルハーツ所属としての参加なのに、アプリケーションテストの内容でなくてごめんなさい😅)

セッションの動画は↓です。

セッションの概要

端的に説明すると、UIパーツはそれぞれのプラットフォーム(AndroidとWeb)で開発するものの、オーディオエンジンはC++で書かれたコードを共有して同じアプリを ライブコーディングで作ってしまう、という内容です。
C++で書かれたオーディオエンジンを走らせる部分を技術用語で表現すると、

で実行することになります。

それでは、内容を説明してきます。(動画のタイムラインと前後する部分があります)

プレゼンター

背景など

AndroidとChromeの話を同時にするのは少し奇妙だが以下のような同じ構造だったり、背景があるとのことです。

  • システム:Callbackを基本としたオーディオを再生する仕組みを持っている
  • 遅延:Audio Latency(往復の)が約20ミリ秒(Pixelデバイス)
  • MIDIサポート:AndroidはMidiManagerAPI、またAndroid QからはハイパフォーマンスなAMidiが搭載され、Chromeでは2015年からWeb MIDI APIでサポートされている
  • Activeユーザー:AndroidもChromeもそれぞれ20億人以上

そしてこれらの利点を活かしている1つの良い例がBandLabというマルチプラットフォームで提供されている音楽制作アプリっとのことで 紹介されています。(2:49)
BandLabはSocial Music Platformで、クロスプラットフォーム(スマホ、Web向け)に提供しており、録音、編集の作業を物理的な場所、プラットフォームを問わず どこでも行うことが可能。そして作成したデータはクラウドに保存される仕組みです。現在700万のユーザが登録しており、200万の曲が毎月がこのプラットフォームに 保存されているそうです。
BandLabが目指す世界は、どこにいても物理的場所、デバイス、時間等の境界を考えることなく音楽を作ることのできる環境を提供し、 音楽制作を更に民主化すること、とのこと。

ライブコーディング

実装する対象の条件はは以下の通りです。

  • オーディオエンジンはC++のコードOboeを利用
  • MIDIで操作できるように実装

また、プラットフォームはAndroidとWeb(Chrome)です。

実装完了しているコードではありませんが、コードがGitHubに公開されていますので、 細かく説明するよりもコードを片手に動画を見たほうがわかりやすいと思うので時間を記録していきます。

* Android側のコーディング

オーディオエンジンを実装 (7:05 - )

MIDIデバイスと接続 (27:27 - )

* ブラウザ側の説明とコーディング

ここでブラウザとC++で何が起こるのか?についての説明が入ります。(12:15 - )
先程Android側で実装したSynthesizer.hを使うけど、これをLLVMベースのコンパイラであるEmscriptenにコンパイルしWeb Assemblyのsynthesizer.wasm.jsを生成する。 このsynthesizer.wasm.jsSynthesizer.hで公開メソッドとして持っていたsynth.noteOn(), synth.noteOff(), synth.render()を同じく 公開メソッドとしてして持つことになるが、残念ながらここだけは自動では生成してくれないのでEmscriptenでAPIをバインドするC++のコードを1つ追加することになります。

続いてブラウザ側で鳴らす方法、それはJavaSciptからWeb Audio APIでAudioレンダリングを可能にするAudio Workletを使います。 なお、このAudio Workletはブラウザのメインスレッド上で動作はしないが、メインスレッドと同調して動作するスレッドです。
具体的には以下の流れで動作をさせることになります。

  • synthesizer.wasm.jsをAudio Worklet ProcessorでWrap
  • Audioスレッドで動作している↑とメインスレッドとはPortと呼ばれる仕組みでデータのやり取りを行いブラウザで発声


ここからがコーディング。

出来上がったところでセッション

同じオーディオエンジンで作製したAndroidとWebのアプリでのセッション。(32:05 - )

事例の紹介

物理的場所を問わず、どこからでも、誰でも、また共有前提として利用できるプラットフォームであるWeb、 そしてそのプラットフォーム上の音楽制作ツール、とてつもなく大きなコミュニティを持つAndroidが音楽という国境のないツールに対して 音楽制作、またハイエンド・オーディオの分野でどれだけのポテンシャルを持っているか、の裏付けの説明が主でした。

・Propellerhead

25年間、音楽向けのアプリを作っている会社でReasonがもっとも知られているソフトウェア。WindowsとmacOS向けのソフトウェアがあり、このソフトウェアシンセサイザをWeb Assenblyを使ってブラウザ上で動作するように移植した。 ブラウザで動くことによって、より多くの人が音を使って自らを表現する機会が増えるといいな、というのが希望がPropellerheadにはあるとのこと。

・IZOTOPR

信号処理のプラグインを開発する会社であるが、Spire Studioというモバイルの録音デバイスを発売した。 このデバイス向けのAndroidアプリも公開したところ40%売上が上がった。この事実からもAndroidユーザがハイエンドのオーディオハードウェアを手にすることを 求めていることが分かると思う。

・Music World Media

数年前にパリで生まれたとても小さい(tiny)Startupで、いろいろなアプリを作っている。 その結果として過去12ヶ月間で235カ国の4500万のAndroidユーザに新規でリーチすることができた。 このことからもAndroidには音楽業界においても多くのユーザにリーチできるポテンシャルを持っていることが証明できていると思う。

・Ableton

Web上での音楽制作ツールは増えてきている。そしてAbletonは音楽制作の教育ツールを公開している。 ここから市販の制作ツールであるAbleton Liveも学ぶことができる。

終わりに

まとめです。

  • AndroidもWeb(Chrome)の両方がリアルタイムオーディオレンダリング、そしてMIDIサポートが可能
  • AndroidとChromeで同じソースコードを使うことが可能
  • 商業的にも、Android、そしてWebから多くのユーザにリーチすることが可能
  • 音楽には国境はない。どこにいようとも、文化背景・経済状態がどうであろうとも境界はなく、だからこそ人々が境界なく対話することが可能
  • 皆さんのプログラミングスキルでどんな音楽を作ることだってサポートすることが可能

ということから、聞くだけでなく、それを使ってコミュニケーションをすることをしてきましょう!と締めています。

個人的な感想

C++のコードからWeb Assemblyに移植するのにはヒトテマ必要であることはライブコーディングからは分かりました。 そして「Web Audioでは音楽アプリはレイテンシーが・・・」と指摘されている部分にWeb Assemblyは一石を投じるのも遠い未来ではないように感じる、 同時にWeb Assemblyを使うことは、そこまで難しいことでもないことを感じることができたセッションであった、 という意味では非常に意味のあるセッションだったと感じています。

さてセッションの主役のWeb Assenbly、Web Audio/MIDIの仕様を決めるW3C、その年次会議であるTPACの2019年開催は日本の福岡です。 その前後にいろいろなイベントが日本で開催されるかもで、今からワクワクしちゃいますね!

ガパオごはんを作ったときに「この味で野菜炒めにしても美味しいんじゃないかな?」と思い立って作ったらやっぱり美味しかったので、Cookpadにメモりました。 ちょっとエスニックな野菜炒め、という感じかな。

「もやしと豆苗のガパオ風炒め」のレシピはこちらです。

番外編

ここからはCookpadには載せていませんが、、、
米国で蕎麦を炒めた料理とかあったけどだいたい微妙な味だけど「この野菜炒めならイケるんじゃないか?」と思って混ぜたのがこちら↓。

味は、、、おすすめ出来ませんww
食べてて、「蕎麦を炒める」だなんてなんて米国的発想をしてるんだ、、、と自分にビックリでした😅