旧形式のsalesforceプロジェクトをHeroku CI上でテストを回す


# はじめに

[Heroku CI](https://devcenter.heroku.com/articles/heroku-ci)はHerokuが提供するCIのためのテスト自動化サービスで、世に出て半年くらい経ちますでしょうか。
様々な言語に対応しており、さらには自分でbuildpackを用意すれば公式で対応してない言語でも利用できるようになる、ということでsalesforceのプロジェクトでも利用できるようにしたいと思って、試してみたところになります。

なお、今回はsfdx形式のプロジェクトではなく、Metadata APIを使った形式(ひとまず、"旧形式"と呼びます 他意はありませんw)のプロジェクトを対象にしています。

なぜか？
単にまだまだsfdxに移行できてない、という大人ではない子供の事情です...

さておき、構成の概要はこんな感じです。

![summary.png](https://qiita-image-store.s3.amazonaws.com/0/1840/e92f31fe-ee25-6b83-bb34-a05af9deb5e6.png "summary.png")


# Sandbox環境の準備

最近ではSandbox環境を20個くらいは作れるようになったので(~~一昔前はデフォルトで1個だけ作れて買い足すなどの(以下ry)~~)、dev環境、CI環境、Staging環境などお好きに準備しましょう。

通常は、以下のパターンが多いと思いますが、開発規模や開発プロセスによっては変わることもあるかもしれません。

|種別|個数|
|:--|:--|
|dev|複数|
|CI|1|
|Staging|1|


# Heroku CIの準備

Heroku CIは、Herokuのアプリケーションライフサイクル管理機能であるHeroku Pipelinesと組み合わせて使用します。
HerokuのダッシュボードからPipelineを作成します。

![heroku_dashuboard_menu.png](https://qiita-image-store.s3.amazonaws.com/0/1840/0021f299-35c0-818c-eb71-9ff650eeb6b6.png "heroku_dashuboard_menu.png")


Pipeline内のメニュータブに```Tests```というメニューがありますので、そこからHeroku CI機能を参照できます。

![heroku_pipeline_menutab.png](https://qiita-image-store.s3.amazonaws.com/0/1840/08c22a5d-312f-7740-3e80-bc4519a4f3ac.png "heroku_pipeline_menutab.png")

その他、基本的な流れは、[Heroku CIを使ってみた](https://qiita.com/mokamoto/items/ecd4d5bb52523b779b02)という中の方が投稿した記事が参考になると思います。
併せて、公式の記事[Pipelines](https://devcenter.heroku.com/articles/pipelines#github-sync)も確認してみてください。

今回はGithubとの連携も設定していますので、こちらの公式記事[GitHub Integration(Heroku GitHub Deploys)](https://devcenter.heroku.com/articles/github-integration)も確認されると良いと思います。

# テスト実行に向けた設定(app.jsonによる設定)

### buildpackを指定する

Heroku CIによるテスト実行は、2種類の方法で定義できるようです。

1. BuildPack内でtestコマンドとして記述する
2. ```app.json```ファイル内にテストスクリプトを記述する

もともと用意されている言語は、以下の10種類でsalesforce向けには用意がありません。

* Go
* Node
* Java
* PHP
* Python
* Ruby
* Gradle
* Scala
* Clojure
* Elixir

したがって、```app.json```を用意することになります。

今回は、sfdxを使うことにしていますので、sfdxが使えるように設定が必要となります。
ということで、@WadeWegnerさんが作成した[salesforce-cli-buildpack](https://elements.heroku.com/buildpacks/wadewegner/salesforce-cli-buildpack)を使うことにします。
[(github上のリポジトリはこちら)](https://github.com/wadewegner/salesforce-cli-buildpack)

![heruoku_buildpack_search_salesforce.png](https://qiita-image-store.s3.amazonaws.com/0/1840/97b7fc49-8c3d-66af-7a77-275a50849603.png "heruoku_buildpack_search_salesforce.png")

このbuildpackの中身を見てみると[/bin/compile](https://github.com/wadewegner/salesforce-cli-buildpack/blob/master/bin/compile)にてsfdxをインストールしているようなので、これを追加のbuildpackとして指定すればsfdxが使えるようになる模様です。
[あわせて読みたい: buildpack API](https://devcenter.heroku.com/articles/buildpack-api)

ここまでで、```app.json```の中身はこんな感じになります。

```app.json
{
  "buildpacks": [
    {
      "url": "https://github.com/wadewegner/salesforce-cli-buildpack"
    }
  ]
}
```

### テストスクリプトを用意する

sfdxを使って、テスト実行するまでの処理手順としては以下の流れを想定します。

1. 特定のSandbox環境へのアクセストークンを取得する
2. 旧形式のプロジェクトを、そのSandbox環境にMetadataAPI経由でデプロイする
3. Sandbox環境上で全ローカルテストを実行させる

sfdxコマンドにもMetadata APIを使うためのコマンドが用意されています。
今回は、Metadata API経由でデプロイ&ローカルテスト実行ということで、[force:mdapi:deploy](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference_force_mdapi.htm#cli_reference_deploy)コマンドを利用します。

また、デプロイ先を指定する上で、上記コマンド実行前に[force:auth Commands](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference_force_auth.htm#cli_reference_force_auth)でアクセストークンを取得しておきます。
今回は、3種類あるうちの[force:auth:sfdxurl:store](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference_force_auth.htm#cli_reference_sfdxurl_store)を利用してみます。([force:auth:jwt:grant](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference_force_auth.htm#cli_reference_jwt_grant)でも問題ありません。こちらを使う場合は、[こちらの記事](https://qiita.com/stomita/items/4542ce1b48e5fa849ef1)を参考にしてください。)

上記コマンドを利用する場合には、refresh tokenが必要になります。取得方法については、[jsforce: Access Token Request](https://jsforce.github.io/document/#access-token-request)か、[REST API開発者ガイド:認証について](https://developer.salesforce.com/docs/atlas.ja-jp.api_rest.meta/api_rest/intro_understanding_authentication.htm)を参考にすると良いかと思います。(すみませんが割愛します...)
取得したrefresh tokenをリファレンスにある通り、

```
force://<clientId>:<clientSecret>:<refreshToken>@<instanceUrl>
```

という文字列だけのファイル(今回は```sfdxAuthUrl```というファイル名にしています)を作成します。

一応、念のためにローカルで問題なくトークンが取得できるかどうか確認してみましょう。

```bash
$ sfdx force:auth:sfdxurl:store -f ./sfdxAuthUrl -a herokuci
```

```
Successfully authorized (ユーザID) with org ID (組織ID)
```

が返ってくれば成功です。

最終的に、```app.json```への設定としては以下のようになります。

1. ```test-setup``` にてアクセストークンを取得する
2. ```test``` にてデプロイ&テスト実行する

```app.json
{
    "buildpacks": [
      {
        "url": "https://github.com/wadewegner/salesforce-cli-buildpack"
      }
    ],
  "environments": {
    "test": {
      "scripts": {
        "test-setup": "./.local/share/sfdx/cli/bin/sfdx force:auth:sfdxurl:store -f ./sfdxAuthUrl -a herokuci",
        "test": "./.local/share/sfdx/cli/bin/sfdx force:mdapi:deploy -d ./src/ -u herokuci -l RunLocalTests -w 60"
      }
    }
  }
}
```

# テストを回す

ひとまず、```app.json```や、refresh tokenを格納したファイルをgithubにpushします。
すると、Heroku CIが反応して```test-setup```->```test```と実行されていきます。
(テスト自体は落ちてますが...テストセットアップは正常に完了し、デプロイ&ローカルテスト実行していることが確認できます)

![heroku_ci_test.png](https://qiita-image-store.s3.amazonaws.com/0/1840/c532e6e2-1d3c-3fdb-7e0f-9c587ae75c94.png "heroku_ci_test.png")

# 後記

* refresh tokenの保管方法としては、Herokuの環境変数にセットしておいて利用するなど、もう少し工夫したいところです。
* CI環境でのテストが成功したら、Staging環境にデプロイするような設定も加えたいところです。
* パッケージをインストールした上でのカスタマイズなど、ScracthOrgを作成するだけだと、デプロイしにくい...という事情がある場合で、1つのSandbox環境を使い回すようなパターンについて、今回考えてみた次第でした。
* Herokuからも[salesforce-buildpack](https://github.com/heroku/salesforce-buildpack)というbuildpackが用意されていますが、[/bin/detect](https://github.com/heroku/salesforce-buildpack/blob/master/bin/detect)をみると```sfdx-workspace.json```というファイルが存在するかどうかでbuildpackに対応したプロジェクトかどうか判定しています。sfdx形式のプロジェクトでは```sfdx-project.json```を作成するので、正しく判定できないし、うまく動かなかったところもあるので今のところは使えなかったです...
* Metadata API経由だと、Apexクラスの削除にも対応するには、なかなか複雑になりそうですが、旧形式のプロジェクトからCI回す上での課題ですね。


より良いsfdxを使った開発ライフをお楽しみください。