echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【Serverless Framework】Serverless FrameworkからAWS Lambdaのテストを実施する

前回の更新が2019/10/12...ずいぶん間が空いてしまったなあ。
実際のところ、前回から

  • 本番リリース
  • 遅い夏休み(お遍路結願)
  • 謎の体調不良(1か月ほど)

で、全然ブログもスキルアップ活動も勉強会参加もできませんでした。

が、ようやく回復してきたので、リハビリがてら。

概要

  • Serverless Framework(以下SF)を使って、クラウド上のソースをテストする。
  • 今回はAWS Lambdaのみ
  • 前提として、事前にSFを使用してAWS Lambda(などのAWSリソース)デプロイをしておく。

SFについては、こちらを参照

AWS上のLambdaを実行する

とりあえず、最低限のコマンドがこちら。
※「funcName」には、SFの「functions」セクションで定義したLambda関数の定義名が入ります。

> sls invoke --function funcName

※必須のパラメータは「--function」だけですが、それ以外にも以下のような任意パラメータがあります。(僕が良く使うもの)

オプション名 説明 備考
--stage 実施したい環境 省略時はdev
--region 実施するAWSのリージョン 省略時は--stageの環境に紐づくリージョン(?)
--path イベントハンドラのevent引数に設定するJSONオブジェクトを定義したjsonファイルの相対パス 今回はは割愛したが、--dataだとテキスト形式で指定できる
--log CloudWatchのログをターミナルに出力させるかどうか --logを指定すると、CloudWatchのログをターミナルに出力できる。(これがすごい便利)

--pathオプションについて

--pathオプションに指定するjsonファイルには、ハンドラの「event」引数の中身を定義することができます。

Lambdaに渡す値は、この「event」引数の「queryStringParameters」「body」キーなどに格納されているので、このjsonファイルの中身を変えることで、いろいろな値を指定してテストを行うことができます。

なお、event引数自体にはたくさんのキー&値がありますが、テストする場合は最低限のキー&値を定義すればOKです。

// 例えば、このLambda関数をテストする場合...
exports.handler = async (event, context, callback) => {

  console.log({event});

  let greeting = "No Name";
  if (event && event.queryStringParameters && event.queryStringParameters.greeting) {
    greeting = `${event.queryStringParameters.greeting} Makky12`;
  } 

  let resBody = {
    greeting: greeting,
  }

  const response = {
    "statusCode": 200,
    "headers": {
      "Access-Control-Allow-Origin" : "*"
    },

    "body": JSON.stringify(resBody)
  }

  return response;
}
{
    // jsonファイルは、最低限下記定義さえあればOK。
    "queryStringParameters": {
        "greeting": "Guten"
    },
}
# 下記コマンドでテストすると、以下のようなレスポンスが表示される。  
# (event.jsonに上記json定義があるとする)
> sls invoke --function funcName --path ./event.json 
  
{
    "statusCode": 200,
    "headers": {
        "Access-Control-Allow-Origin": "*"
    },
    "body": "{\"greeting\":\"Guten Makky12\"}"
}

--logオプションについて

通常、テストを実行した場合にターミナルに表示されるのはレスポンスの内容だけですが、「--log」オプションを付けることで、CloudWatchの内容を追加で表示できます。

個人的にはこれがかなり便利な機能だと思っていて、いちいちAWSにアクセスしなくても、エラーの原因がある程度わかりますので、デバッグにもかなり役立ちます。

# --logオプションありだと、レスポンスの下にCloudWatchのログが表示される。  
# ※これがクッソ便利!
> sls invoke --function funcName --path ./event.json --log
  
{
    "statusCode": 200,
    "headers": {
        "Access-Control-Allow-Origin": "*"
    },
    "body": "{\"greeting\":\"Guten Makky12\"}"
}

START RequestId:xxx Version: $LATEST  
2019-12-28 09:07:34.901 (+09:00)   INFO    { event: { queryStringParameters: { greeting: 'Guten' } } }  
END xxx  
REPORT xxx  Duration: 110.36 ms  

ローカルのLambda関数を実行する

先程までは実際にAWS上のLambdaに接続しましたが、ローカルのLambdaをテストすることもできます。

ローカルのLambdaを実行するコマンドはこちら。(「local」が追加されただけですが)

> sls invoke local --function funcName

オプションもAWS上で実行する場合とほとんど同じですが、下記が追加されてます。

オプション名 説明 備考
--env name=value形式で、環境変数を指定できる serverless.ymlで「${opt:env}」(コマンド引数envの値を参照する)定義があるとエラーになる。
この場合省略形(-e)を使用する。
--docker Docker上での動作を可能にする node.js, Ruby, Python, Javaのみ
--docker-arg Dockerで実行する際、Dockerに渡す引数を指定できる

envオプションについて

上記に記載の通り、--envオプションで、Lambdaに定義した環境変数の値を変更できます。

たとえば、先述のLambda関数のresBody.greetingの値に下記の環境変数を定義した場合、下記のような差があります。

変数名
MY_SAMPLE_ENV hogehoge
# AWSのLambda関数を実行した場合
# この場合、--envの指定は無視され、Lambdaで定義した「hogehoge」になる。
> sls invoke --function funcName --path ./event.json --log --env MY_SAMPLE_ENV=fugafuga
  
{
    "statusCode": 200,
    "headers": {
        "Access-Control-Allow-Origin": "*"
    },
    "body": "{\"greeting\":\"hogehoge\"}"
}
START RequestId:xxx Version: $LATEST  
2019-12-28 09:07:34.901 (+09:00)   INFO    { event: { queryStringParameters: { greeting: 'Guten' } } }  
END xxx  
REPORT xxx  Duration: 110.36 ms  
  
# ローカルのLambda関数を実行した場合
# この場合、--envの指定が適用され、--envで指定した「fugafuga」になる。
# なおローカルの場合、--logオプションは無視される
> sls invoke --function funcName --path ./event.json --log --env MY_SAMPLE_ENV=fugafuga
  
{
    "statusCode": 200,
    "headers": {
        "Access-Control-Allow-Origin": "*"
    },
    "body": "{\"greeting\":\"fugafuga\"}"
}

AWS上での直接実行とローカル実行の比較

実行方法 メリット デメリット
直接実行 ・実運用に即したテストができる
・エラー原因が特定しやすい(ログを出力できるので)
・通信費がローカル実行よりかかる
・修正後の動作確認時に、いちいちデプロイや更新を行う必要がある
ローカル実行 ・通信費が直接実行より安い(※)
・修正後の動作確認が簡単
・エラーの特定が直接実行より難しい(ログが出力できないので)
・実運用に即したという点で、直接実行には劣る

※最初「ローカル実行だから、実際のAWSリソース(S3やDynamoDBとか)にはアクセスできない(=エラーになる)」と考えていましたが、実際は普通にSecretManagerやAuroraの値を取得できました。
pluginでローカル実行用の(Mock)プラグインの定義をしなかった場合、普通にリソースの値はAWSに接続して取得するのか?(この辺りは、要調査です)

まとめ

結論としては、結構使いやすいと思います。
やはり「--log」によるログ出力機能が便利だなあ、と感じました。

「ローカル実行時でも、(実運用時と同様に)ハンドラ関数呼び出しによるブラックボックステストしか行えない(privateな内部関数のテストが行えない)」点はありますが、それを差し引いても便利だなあと感じます。
その辺は、Jestなどと併用して解決していけばよいと思います。

体調不良やらいろいろありましたが、何とか年内にブログを書けて良かった。

それでは、良いお年を。