AWS SDKを使いDeviceFarmでのテストを実行する


この投稿の内容に加筆して弊社のブログに投稿しました。
http://tech.vasily.jp/entry/devicefarm_automation

# 概要
AWS SDKを使用してDeviceFarmへAPKとテストコードをアップロードし、Calabashによるテストを実行してみたのでそのメモです。

# APIドキュメントと使用するSDK
ドキュメントは以下を参照しました。

* http://docs.aws.amazon.com/sdkforruby/api/Aws/DeviceFarm.html

また、SDKには[AWS SDK for Ruby - Version 2](http://docs.aws.amazon.com/sdkforruby/api/index.html)を使用します。

# AWS SDK Clientの作成

clientを作成します。IAMでAWSDeviceFarmFullAccessのポリシーをアタッチしたユーザを用意し、そのAccessKeyIdとSecretAccessKeyを使用します。また、regionには`us-west-2`を指定します。

```ruby
equire 'aws-sdk'

devicefarm = Aws::DeviceFarm::Client.new(
    region: 'us-west-2',
    credentials: Aws::Credentials.new("YOUR_ACCESS_KEY_ID", "YOUR_SECRET_ACCESS_KEY"),
)
```

# DeviceFarmのプロジェクト情報を取得
まず試しにDeviceFarmで作成したプロジェクトの情報を取得してみます。プロジェクトを作成していない場合DeviceFarmのコンソールから「Create a new project」をクリックしプロジェクトを作成しておきます。

プロジェクト情報は以下のコードで取得できます。

```ruby
devicefarm.get_project({
    arn: "プロジェクトのARN"
})
```

ここで、プロジェクトのARNは以下のようになります。

```
arn:aws:devicefarm:us-west-2:<ポリシーをアタッチしたユーザーID>:project:<DeviceFarmのプロジェクトID>
```

DeviceFarmのプロジェクトIDは、DeviceFarmでプロジェクトを開いた際のURLの以下の部分を使用します。

```
https://us-west-2.console.aws.amazon.com/devicefarm/home?region=us-west-2#/projects/この部分/runs
```

# DevicePoolの取得
## デバイスプールについて
テストを実行するデバイスの種類は、デバイスプールと呼ばれる単位で管理されています。デバイスプールをAWS SDKから作成することは出来ますが、コンソールから作成した方がわかりやすいです。

コンソールのプロジェクトページに表示される「Create a new run」をクリックすると新しいRun(テスト対象APKやデバイスの種類、テストケースを紐付けたもの)を作成することができますので、一度作成しRunを実行することをおすすめします。[AWSのブログ記事](http://aws.typepad.com/aws_japan/2015/07/aws-device-farm-test-mobile-apps-on-real-devices.html)が実行まで詳細に書いてあるので参考にしてみてください。

## AWS SDKでのDevicePoolの取得
以下のコードでDevicePoolを取得できます。レスポンスにはDevicePoolのARNなどの情報が含まれます。今回はレスポンスに含まれる最初のDevicePoolを使用します。ドキュメントは[こちら](http://docs.aws.amazon.com/sdkforruby/api/Aws/DeviceFarm/Client.html#get_device_pool-instance_method)です。

```ruby
resp = devicefarm.list_device_pools({
    arn: "プロジェクトのARN"
})

# DevicePoolのARN(後ほど使用)
device_pool_arn = resp.device_pools[0].arn
```

# テスト対象APKのアップロード
APKのアップロードは2段階の手順を踏みます。

* devicefarm.create_upload()でのinitialize
* Pre-Signed URLを使用したAPKアップロード

## initialize

以下のコードで初期化し、Pre-Signed URLを取得します。また、アップロードするオブジェクトのARNを取得しておきます(実行をスケジューリングする際に使用します)。ドキュメントは[こちら](http://docs.aws.amazon.com/sdkforruby/api/Aws/DeviceFarm/Client.html#create_upload-instance_method)です。

```ruby
resp = devicefarm.create_upload({
    project_arn: "プロジェクトのARN",
    name: "app-debug.apk",
    type: "ANDROID_APP",
    content_type: "application/octet-stream"
})

# Pre-Signed URLの取得
pre_signed_url = resp.upload.url

# 実行をスケジューリングする際に使用
apk_arn = resp.upload.arn

```

create_uploadのレスポンスのシンタックスは以下のようになっています。ドキュメントは[こちら](http://docs.aws.amazon.com/devicefarm/latest/APIReference/API_CreateUpload.html#API_CreateUpload_ResponseSyntax)です。

```
{
    "Upload": {
        "Arn": "string",
        "ContentType": "string",
        "Created": number,
        "Message": "string",
        "Metadata": "string",
        "Name": "string",
        "Status": "string",
        "Type": "string",
        "Url": "string"
    }
}
```

statusは、`FAILED`、`INITIALIZED`、`PROCESSING`、`SUCCEEDED`の4種類があり、create_uploadを実行した直後は、`INITIALIZED`になり、この状態ではDeviceFarmから利用できません。Pre-Signed URLを利用してAPKをアップロードすることで`PROCESSING`となり、その後`SUCCEEDED`へ変化します。ここで`PROCESSING`の際に不具合が起きると`FAILED`になります。


## upload

[こちら](http://docs.aws.amazon.com/AmazonS3/latest/dev/UploadObjectPreSignedURLRubySDK.html)を参考に、以下のようなコードでAPKをアップロードできます。

```ruby
url = URI.parse(pre_signed_url)
apk = File.open("YOUR_APK_PATH", "rb").read
Net::HTTP.start(url.host) do |http|
    http.send_request("PUT", url.request_uri, apk, {"content-type" => "application/octet-stream"})
end
```


# テストパッケージのアップロード
今回テストには[Calabash](http://calaba.sh/)を使用するので、Calabashのfeaturesをzipに圧縮してアップロードします。アップロードの手順はAPKのアップロードと同じです。create_uploadのtypeに`CALABASH_TEST_PACKAGE`を指定します。

```ruby
resp = devicefarm.create_upload({
    project_arn: "プロジェクトのARN",
    name: "features.zip",
    type: "CALABASH_TEST_PACKAGE",
    content_type: "application/octet-stream"
})

# Pre-Signed URLの取得
pre_signed_url = resp.upload.url

# 実行をスケジューリングする際に使用
calabash_package_arn = resp.upload.arn

url = URI.parse(pre_signed_url)
features = File.open("YOUR_FEATURES_PATH", "rb").read
Net::HTTP.start(url.host) do |http|
    http.send_request("PUT", url.request_uri, features, {"content-type" => "application/octet-stream"})
end
```

# テストの実行

## 実行のスケジューリング

実行のスケジュールは以下のように行います。ドキュメントは[こちら](http://docs.aws.amazon.com/sdkforruby/api/Aws/DeviceFarm/Client.html#schedule_run-instance_method)です。

app_arnには、APKをアップロードした際に取得したARNを、device_pool_arnはDevicePoolの取得で取得したARNを、test_package_arnにはCalabashのfeatures.zipをアップロードした際に取得したARNを使用します。

```ruby
devicefarm.schedule_run({
	project_arn: "プロジェクトのARN",
    app_arn: apk_arn,
    device_pool_arn: device_pool_arn,
    test: {
    	type: 'CALABASH',
       test_package_arn: calabash_package_arn
    }
})
```

## APK、Test packageのアップロード完了を待つ
APK、Test packageをアップロードした直後では、それぞれのstatusが`SUCCEEDED`になっているとは限りません。このため、定期的にstatusが`SUCCEEDED`かどうか確認する手順が必要になります。

get_uploadを使用すると、uploadしたオブジェクトの状態を取得できるので、それを1秒ごとに取得しstatusを確認します。そして、APK、Test package両方のstatusが`SUCCEEDED`ならschedule_runを実行します。

```ruby
10.times do
    resp_apk = devicefarm.get_upload({
        arn: apk_arn
    })

    resp_test_package = devicefarm.get_upload({
        arn: calabash_package_arn
    })

    if resp_apk.upload.status == "SUCCEEDED" &&
    	resp_test_package.upload.status == "SUCCEEDED"

        devicefarm.schedule_run({
            project_arn: "プロジェクトのARN",
            app_arn: apk_arn,
            device_pool_arn: device_pool_arn,
            test: {
                type: 'CALABASH',
                test_package_arn: calabash_package_arn
            }
        })

        exit
    end

    sleep(1)
end
```

これでスケジューリングされ、DeviceFarm上でCalabashのテストが実行されます。

# まとめ
AWS SDKを利用してDeviceFarm上でのテストを実行することができました。今後はCircleCIと連携させGitHubへのPushをトリガーにしてDeviceFarmでのテストを実行できるようにしたいと思います。