🧪

LocalStack で S3・SNS のテストを書いてみた

2023/07/20に公開

はじめに

ポートでPHPエンジニアをしている @sawada です。

先日、担当しているプロジェクトにて、localstack を導入しましたので、そちらについて書いていきたいと思います。

localstack について

localstack は、AWS のクラウドサービスシュミレータです。
ローカル開発環境、CI 環境にて、AWSと同等の環境を構築することが可能となり、より本番に近い環境での開発、テストが可能となります。
動作確認の為に、実際にAWSへデプロイする必要も無くなるので、コスト削減にも繋がります。
また、Docker イメージも提供されているのでプロダクトへの導入も非常に簡単です。

全ての機能を利用することはできませんが、非常に多くの機能が提供されています。
実装機能の詳細は、こちらをご確認下さい。

Community(無償)版・Pro(有償)版の違い

localstack には、Community (無償)版と Pro (有償)版が存在します。
Community 版と Pro 版で色々と異なりますが、特に注意しておきたい事は以下の2点です。

  • Community 版はデータが永続化されない
  • 利用できるサービス・API が制限される

Community 版はデータが永続化されない

Community 版ではデータの永続化が行われません。
Docker を使用している場合は、コンテナが落ちた段階で設定データ含め、全て初期状態に戻ってしまうので注意が必要です。
初期設定の操作が多く、煩雑な場合は shell にまとめる、などしておくと無難でしょう。

利用できるサービス・API が制限される

Community版では、一部のサービス・APIのみ利用可能となっています。

今回はアプリケーション側から使用されることが多いであろう主要なサービスのみ表にまとめました。

サービス Community版・Pro版 備考
S3 Community版から利用可能 提供されている機能は全て利用可能
SNS Community版から利用可能 提供されている機能は全て利用可能
SQS Community版から利用可能 提供されている機能は全て利用可能
Lambda Community版から利用可能 提供されている機能は全て利用可能
API Gateway Community版から使用可能 一部 API は Pro版のみ
DynamoDB Community版から使用可能 一部 API は Pro版のみ
ElastiCache Pro版のみ -
Cognito Pro版のみ -

全てのサービス・API を確認する場合は下記をご確認下さい。
(下記のページにて、(Pro) と記述されたサービス・API が Pro版でのみ使用可能となります。)
https://docs.localstack.cloud/user-guide/aws/feature-coverage/

使ってみた

では、早速使っていきたいと思います。

今回はPHPアプリケーションで、S3・SNSを利用しているものを想定し、各種設定方法、テストコードを記述していきます。

localstack の設定

localstack を使用する場合は、ローカルマシンにインストールするか、Docker を利用することとなります。
今回は、Community版を Docker を利用して構築していきたいと思います。

docker-compose.yml は、以下の通りです。

docker-compose.yml
version: '3'

services:
  app:
    image: php:latest
    ports:
      - 8080:80
    volumes:
      - './:/app'
    tty: true
    working_dir: '/app'

  localstack:
    image: localstack/localstack:2.0.2
    ports:
      - 4566:4566
    environment:
      SERVICES: 'S3,SNS'
      AWS_ACCESS_KEY_ID: "localstack-test"
      AWS_SECRET_ACCESS_KEY: "localstack-test"
      AWS_DEFAULT_REGION: "ap-northeast-1"

今回は、ポート 4566 のみ公開しています。
(以前のバージョンでは、ポート番号毎にサービスが割り当てられていたようですが、現状は単一のポートから全て利用できるようになったみたいです。)

次に、localstack コンテナ内の aws cli を利用し、S3, SNS の各種設定を行っていきます。

S3 の設定

aws cli を利用し、テスト用のS3バケットを作成します。
このとき、--endpoint-url として、localstack のエンドポイントを指定する必要があります。

docker compose exec localstack aws --endpoint-url=http://localhost:4566 \
    s3 mb s3://test-bucket

以下のコマンドで、バケット一覧を確認できます。

docker compose exec localstack aws --endpoint-url=http://localhost:4566 \
    s3 ls

S3バケットが作成されたことが確認できれば完了です。

SNS の設定

同じく、aws cli を利用し、

  • テスト用のトピック
  • プラットフォームアプリケーション(ios, android 共に)
  • プラットフォームエンドポイント(ios, androind 共に)

を作成します。

s3 の時と同様に--endpoint-urlを指定します。

トピックの作成

docker compose exec localstack aws --endpoint-url=http://localhost:4566 \
    sns create-topic --name localstack-test

トピック一覧の確認

docker compose exec localstack aws --endpoint-url=http://localhost:4566 \
    sns list-topics

プラットフォームアプリケーション

docker compose exec localstack aws --endpoint=http://localhost:4566 \
  sns create-platform-application --name app-test --platform APNS --attributes {}

docker compose exec localstack aws --endpoint=http://localhost:4566 \
  sns create-platform-application --name app-test --platform GCM --attributes {}

プラットフォームアプリケーション一覧の確認

docker compose exec localstack aws --endpoint=http://localhost:4566 sns list-platform-applications

プラットフォームエンドポイント

docker compose exec localstack aws --endpoint-url=http://localhost:4566 \
  sns create-platform-endpoint \
  --platform-application-arn "arn:aws:sns:ap-northeast-1:000000000000:app/APNS/app-test" --token my-fake-token-apns

docker compose exec localstack  aws --endpoint-url=http://localhost:4566 \
  sns create-platform-endpoint \
  --platform-application-arn "arn:aws:sns:ap-northeast-1:000000000000:app/GCM/app-test" --token my-fake-token-gcm

プラットフォームエンドポイント確認

docker compose exec localstack aws --endpoint-url=http://localstack:4566 \
  sns list-endpoints-by-platform-application \
  --platform-application-arn="arn:aws:sns:ap-northeast-1:000000000000:app/APNS/app-test"

docker compose exec localstack aws --endpoint-url=http://localstack:4566 \
  sns list-endpoints-by-platform-application \
  --platform-application-arn="arn:aws:sns:ap-northeast-1:000000000000:app/GCM/app-test"

SNS の設定は以上で完了となります。


なお、localstack の提供している awsLocal を使用すると--endpoint-urlの記述を省くことができますが、今回は長くなるので割愛します。
詳細は、以下をご確認下さい。
https://docs.localstack.cloud/user-guide/integrations/aws-cli/#localstack-aws-cli-awslocal

次に、PHPアプリケーション側のAWSのクライアントの設定を行います。

PHPアプリケーションの AWS コンフィグの設定

各種 AWSClint 作成時に必要な値を設定していきます。

config.prod.php
// 本番環境用
$config = [
    'credentials' => new \Aws\Credentials\Credentials(
        '...',
        '...'
    ),
    'region' => 'ap-northeast-1',
    'version' => 'latest',
];
config.test.php
// localstack用
$config = [
    'credentials' => new \Aws\Credentials\Credentials(
        'localstack-test',
        'localstack-test'
    ),
    'region' => 'ap-northeast-1',
    'version' => 'latest',
    'endpoint' => 'http://localstack:4566',
    'use_path_style_endpoint' => true, // endpoint を path 形式とする
];

本番環境とlocalstack環境との違いは、

  • endpoint の指定
  • パススタイルのエンドポイントを使用するようにしている

という2点です。
引数の詳細は、以下から確認して下さい。
https://docs.aws.amazon.com/aws-sdk-php/v3/api/class-Aws.S3.S3Client.html#___construct

次は、テストコードを書いていきます。

S3 に依存するクラスのテストコード

ImageService というクラスが存在し、saveメソッドの呼び出しで引数に指定したパスの画像が S3 に保存される、という事を想定し、
それを確認するテストコードを書いていきます。

public function test_save()
{
  // 準備
  $dummyFilePath = $this->createDummyFile(); // テストファイルを作成
  $dummyFileContents = get_file_contents($dummyFilePath);
  $config = $this->get_config(); // テスト用の config を取得する
  $s3Client = new S3Client($config);
  $sut = new ImageServise();

  // 実行
  $result = $sut->save($testFile);

  // 検証
  $s3Object = $s3Client->getObject([
    'Bucket' => 'test-bucket',
    'Key' => $result['key'],
  ]);

  $this->assertEquals($dummyFileContents, $s3Object['Body']);
}

テスト対象メソッド呼び出し後、s3クライアントでS3オブジェクトを取得できること、取得したコンテンツが保存したものと同様であることを確認しています。

SNS に依存するクラスのテストコード

SNSService というクラスが存在し、 subscribe メソッドの呼び出しで、endpointが有効化される、という事を想定し、
それを確認するテストコードを書いてきます。

public function test_subscribe()
{
    $config = $this->get_config(); // テスト用の config を取得する
    $snsClient = new SnsClient($config);
    $testDeviceToken = uniqid();

    $sut = new SNSService();

    // 実行
    $result = $sut->subscribe($testDeviceToken);

    // 検証
    $snsEndpoint = $snsClient->getEndpointAttributes([
        'EndpointArn' => $result->endpointArn,
    ]);

    $this->assertEquals('true', $snsEndpoint['Attributes']['Enabled']);
}

こちらも、テスト対象メソッド呼び出し後、SNSクライアントから endpoint を取得し、有効化されているか(getEndpointAttributes の戻り値のEnabled'true'かどうか)確認しています。

最後に

以上となります。 いかがでしたでしょうか?
今回は、localstack を使用し、S3, SNS に依存するクラスのテストを書いてみました。
localstack を使用することで、より本番環境に近い状態で開発・テストを行え、開発体験も向上すると思われます。
今後も積極的に利用していきたいと思っています!

ご参考になれば幸いです。

参考

https://docs.localstack.cloud/overview/
https://docs.localstack.cloud/user-guide/aws/feature-coverage/
https://localstack.cloud/pricing/
https://iga-ninja.hatenablog.com/entry/2020/10/24/011800
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-services-sns.html
https://aws.amazon.com/jp/sdk-for-php/

Discussion