はじめに

先日, CloudWatch Logs から Amazon Elasticsearch Service にログを転送する Lambda ファンクションの Node.js で実装されたスクリプト (Stream to Amazon Elasticsearch Service を設定する自動で生成されるやつ) を改修する機会があり, 意図した通りの修正になっているか不安になったので, ユニットテストの環境を整えて, テストしてみたら少し不安が解消したので, 改めてユニットテストを書く練習をしてみる.

尚, 練習にあたり, 以下のブロク記事を目一杯参考にさせて頂いた.

実践AWS Lambdaで個人的に一番嬉しかった記述が、ローカルテストの節だったりします。読みっぱなしだと身につかないので、サンプルコードを触ってみました。Node.jsをローカルで動かす書籍のサンプルコードをちょっとアップデートさせてみたやつ。index.jsexports.ha...

api.wp-kyoto.net

有難うございました.

今回は Node.js で

ということで, 今回は Node.js でユニットテストを書いてみる. 普段から自分は Lambda ファンクションを書く際は Python を利用しており, 今回の改修でほぼ初めて Node.js を触れる為, 認識に誤りがあったり, 用語の誤用等があるかもしれない.

また, 練習では特に AWS の各サービスとの連携については考慮しておらず, 以下のようにシンプルに JSON を返すような Lambda ファンクションを想定している.

# Lambda ファンクションをローカルで呼ぶ JavaScript
root@b0a0e3b252d2:/app/sample# cat sample.js 
const lambda = require('./handler.js');
const event = "foo";
const context = "";
function callback(error, response) {
  console.log(response);
}
lambda.handler(event, context, callback);

# ローカルで実行してみる
root@b0a0e3b252d2:/app/sample# nodejs sample.js 
{"message":"hello lambda!","input":"foo"}

環境

JavaScript でテストを書くために必要な諸々

まず, JavaScript でテストを書くにあたって, 必要な諸々については以下の記事が参考になった.

この記事は、はてなエンジニアアドベントカレンダー2016の5日目の記事です。こんにちは、はてなでアプリケーションエンジニアをしている id:shiba_yu36 です。先日、buildersconにおいて、現在所属しているプロジェクトでJavaScriptのユニットテストを導入した知見に...

developer.hatenastaff.com

有難うございます.

どんなツールが存在していて, どんな役割があるのか… 上記のブログ内で紹介されている資料内で一目瞭然のページがあったので, 以下に掲載させて頂く.

speakerdeck.com

テストフレームワーク (Ruby で言うところの minitest とか Rspec になるのかしら) として Mocha とか jasmine というのがあったり, アサーションライブラリとして power-assertchai といのがあったり…, まだまだ, 勉強する必要がありそう.

ところで, Mocha だの Jasmine だの chai だの, いちいち名前がシャレオツなのは何なんだろう…

今回は Mocha + power-assert で

ということで, 今回はテストフレームワークとしては Mocha を power-assert を利用して進める.

インストールは以下のように.

npm install -D mocha power-assert

また, 前後してしまうが, Node.js の環境は Docker イメージを利用.

root@b0a0e3b252d2:/# node --version
v9.5.0

普段から Serverless Framework を利用してデプロイしているので, 一応, Serverless Framework もインストールしておく.

npm install -g serverless

実践

sls create でシンプルなアプリケーションを

早速, 以下のようにアプリケーションの雛形を作る.

sls create --template aws-nodejs --path ./sample

実行すると, 以下のように出力される.

Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/app/sample3"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.26.0
 -------'

Serverless: Successfully generated boilerplate for template: "aws-nodejs"

で, 以下のようなファイル構成となっている.

root@b0a0e3b252d2:/app# tree sample
sample
|-- handler.js
`-- serverless.yml

0 directories, 2 files

handler.js を修正

handler.js の中身は以下のようになっているけど…

'use strict';

module.exports.hello = (event, context, callback) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Go Serverless v1.0! Your function executed successfully!',
      input: event,
    }),
  };

  callback(null, response);

  // Use this code if you don't use the http event with the LAMBDA-PROXY integration
  // callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event });
};

以下のように修正する.

'use strict';

exports.handler = (event, context, callback) => {
  const response = JSON.stringify({ message: 'hello lambda!', input: event });
  callback(null, response);
};

event に入っている内容を inputーの値としてセットして JSON として出力するだけのシンプルなやつ.

テストを書く

テストは以下のようなケースを想定して実装する.

  • event に何も指定されていない場合
  • event に ‘sample’ という文字が指定されている場合, 返却される JSON は {"message":"hello lambda!","input":"sample"} となること
  • event に ‘foo’ という文字が指定されている場合, 返却される JSON は {"message":"hello lambda!","input":"sample"} にならないこと

以下のように実装した.

'use strict';
const assert = require('assert');
const lambda = require('../handler.js');

describe('Lambda Test', () => {
  let event;
  let context;

  it('event が指定されていない', () => {
      lambda.handler(event, context, (error, result) => {
        assert.equal(result, '{"message":"hello lambda!"}');
      });
  });

  it('event に sample という文字列が指定されている', () => {
      event = 'sample';
      lambda.handler(event, context, (error, result) => {
        assert.equal(result, '{"message":"hello lambda!","input":"sample"}');
      });
  });

  it('event に foo という文字列が指定されている', () => {
      event = 'foo';
      lambda.handler(event, context, (error, result) => {
        assert.notEqual(result, '{"message":"hello lambda!","input":"sample"}');
      });
  });

});

最後の event に foo という文字列が指定されている のテストは必要無いかもしれないけど, assert.notEqual を使ってみたくて追加した.

尚, 実装したテストは以下のように tests というディレクトリを作成し, その直下に Lambda ファンクションと同じファイル名で保存しておいた.

root@b0a0e3b252d2:/app# tree sample
sample
|-- handler.js
|-- package.json
|-- sample.js
|-- serverless.yml
`-- tests
    `-- handler.js

1 directory, 5 files

この命名規則で正しいかはわからないけど, もしかしたら, テストファイルは handler.test.js というようにひと目で用途が解るようにしておいた方がいいのかもしれない.

テストを実行する

実際にテストを実行してみる.

root@b0a0e3b252d2:/app/sample# /app/node_modules/mocha/bin/mocha tests/handler


  Lambda Test
    ✓ event が指定されていない
    ✓ event に sample という文字列が指定されている
    ✓ event に foo という文字列が指定されている


  3 passing (7ms)

ひとまず, 全てのテストが Pass したことを拝めた.

ちなみに, package.json というファイルに, 以下のように書いておくことで npm test と叩くだけでテストが実行される. Ruby で言うところ rake とかになるのかしら.

{
  "scripts": {
    "test": "/app/node_modules/mocha/bin/mocha tests/handler.js"
  },
  "devDependencies": {
    "mocha": "^5.0.0"
  }
}

改めて, npm test を実行してみる.

root@b0a0e3b252d2:/app/sample# npm test

> @ test /app/sample
> /app/node_modules/mocha/bin/mocha tests/handler.js



  Lambda Test
    ✓ event が指定されていない
    ✓ event に sample という文字列が指定されている
    ✓ event に foo という文字列が指定されている


  3 passing (8ms)

ほうほう.

ちょっと Fail も体験する

以下のようにテストコードを弄る.

...
  it('event に foo という文字列が指定されている', () => {
      event = 'foo';
      lambda.handler(event, context, (error, result) => {
        assert.equal(result, '{"message":"hello lambda!","input":"sample"}');
      });
  });
...

テストを走らせてみる.

root@b0a0e3b252d2:/app/sample# npm test

> @ test /app/sample
> /app/node_modules/mocha/bin/mocha tests/handler.js



  Lambda Test
    ✓ event が指定されていない
    ✓ event に sample という文字列が指定されている
    1) event に foo という文字列が指定されている


  2 passing (9ms)
  1 failing

  1) Lambda Test
       event に foo という文字列が指定されている:

      AssertionError [ERR_ASSERTION]: '{"message":"hello lambda!","input":"foo"}' == '{"message":"hello lambda!","input":"sample"}'
      + expected - actual

      -{"message":"hello lambda!","input":"foo"}
      +{"message":"hello lambda!","input":"sample"}
      
      at lambda.handler (tests/handler.js:26:16)
      at Object.exports.handler (handler.js:5:3)
      at Context.it (tests/handler.js:24:14)



npm ERR! Test failed.  See above for more details.

見事に Fail された.

以上

Lambda でもテストは普通に書ける

今回, 初体験の Node.js (JavaScript) で Mocha や powert-assert を利用することで, 思ったよりも簡単にテストを書くことが出来た気がする (当然, テスト対象がシンプルなので簡単っちゃー簡単). 今回は触れることが出来なかったけど, ここに AWS のサービスを絡めるとなると, 毎回 AWS SDK を利用して API をコールするのではなく, スタブ化してダミーの値を返して云々が必要になってくるんだと思う. この辺りも追々で練習していきたい.

ということで, 総じてテストは楽しい.

テストを書くことを意識づけたい

ということで…AWS Lambda が登場してから, インフラエンジニアとされる人たちでも, 呼吸をするように Python や Node.js を使ってプログラミングする (しなければいけない) 時代になってきたと思っている. 自分自身, シェルスクリプトの感覚で書き捨てた (つもりだった) Lambda ファンクションの山を築いてきたけど, それらに対して本当に意図した通りに動いているのか, 今後, これらを改修することになった場合に, デグレせずに改修出来るのか…新しく Lambda ファンクションを書く度に, そんな不安が募ってきている.

実際に, 何度かデグレ事案を目の当たりにして, テストを習慣づけるというのは, もう, 待ったなしになってきているのではと考えているので, ユニットテストの「ユ」の字も理解出来ていないけど, これからは書き捨てであっても, どんなに小さいスクリプトでも, テストを書くことを意識していきたい…と考えている.

以上.

元記事はこちら

ユニットテストの「ユ」の字も解ってないけど, AWS Lambda (今回は Node.js) でユニットテストを書く練習をする