頭の整理

JavaScript, Ruby, RSpec, Node.js, Rails TDDなどに興味があるWeb系SEが学んだことを整理していきます

【Ruby】AWS S3へのクライアント側暗号化用のコマンドを作成しました

AWS SDK for Rubyを使ってS3へファイルをクライアント側で暗号化(Client Side Encryption)してアップロード,暗号化したファイルを復号化してダウンロードするためのコマンドを作りました.使う際はRubyGemsからインストールしてください. 前提として理解しておいてほしい情報と簡単にですが使い方をまとめました.

前提条件

Credentialsの設定

access_key_idsecret_access_key_idなどのAWSのCredentialsはEC2インスタンスのロールや,環境変数などにすでに与えられている必要があります.

この辺を参考にしてください. http://docs.aws.amazon.com/AWSSdkDocsRuby/latest//DeveloperGuide/prog-basics-creds.html http://docs.aws.amazon.com/AWSSdkDocsRuby/latest/DeveloperGuide/ruby-dg-roles.html

S3へのアクセスコントロールの制御

アクセス制限などを担保するものではないため,そこはバケットポリシーやIAMへの権限付与などで適切に行なってください.あくまでもS3へアップロードする際にそのデータを暗号化してアップロードしてそれをダウンロードするためのものです.

S3のアクセスコントロールに関してはこちらのエントリにわかりやすくまとめられています. S3のアクセスコントロールまとめ

Client Side Encryptionに関して

AWS SDK for RubyだとこちらのドキュメントのClient Side Encryptionの部分が参考になります. 簡単に流れを説明すると

  1. 暗号化のためのAES鍵を作成
  2. アップロードの際にその鍵で暗号化してアップロード
  3. ダウンロードの時も同じ鍵を使って復号化してダウンロード

といった感じです.

インストール方法,使い方など

GithubのREADMEを参考にしてください. https://github.com/nabewata07/encrypted_s3_copy/blob/master/README.md

インストール後の使い方を簡単に説明すると

  1. (AES鍵がすでにある場合は2.へ) aes_key_genコマンドでAES鍵を作成.その際に-kオプションで鍵ファイルのパスを指定
  2. encrypt_s3_copyコマンドで-sオプションでアップロードしたいローカルファイルのパスを指定 -dオプションでアップロード先のS3のパスを指定 バケット名は省略できません
  3. ダウンロードするときは2.と同様のコマンドで-sにS3のファイルパス,-dにダウンロード先のローカルパスを指定してください

といった流れです.--recursiveオプションも用意しています.

RubyGemsのページはこちら https://rubygems.org/gems/encrypted_s3_copy

【Ruby】if, unless 修飾子をつなげる

if, unless修飾子でネストを浅くしたりしますが,書き方で知らないことがあったのでメモ.

hoge = 'hoge' if true #=> 'hoge'
moge = 'moge' if true unless false #=> 'moge'
foo = 'foo' if true unless false if true #=> 'foo'
bar = 'bar' if true unless true #=> nil

一行目のような書き方だけかと思ってましたが,if, unlessを繋げられるんですね.

【JavaScript】プロトタイプベースのオブジェクト指向について

※Object.createの使い方に関して修正しました.
プロトタイプベースのオブジェクト指向と,JavaScriptでのその扱い方について混乱していたので整理しました.コンストラクタ,プロトタイプ,インスタンスの用語の整理とprototypeプロパティの説明,継承の実現方法などについてサンプルコードを含めて書きました.

コンストラクタ,プロトタイプ,インスタンス
var str = new String;
str.slice; // => function
str.slice();

コンストラクタ(JavaScriptではFunctionオブジェクト)はオブジェクトを生成するもの(上記コードのString)です.コンストラクタから生成されたオブジェクトをインスタンスと呼びます(上記コードのstr).

コンストラクタはプロトタイプを保持します.プロトタイプはオブジェクトです.コンストラクタから生成したインスタンスはプロトタイプにある関数を使用することができます.
例えば,上記コードではstrが保持しているプロパティにsliceがない場合はコンストラクタStringのプロトタイプにあるsliceプロパティの値を返します.

3行目の処理の順序は以下のような流れです.

  1. strオブジェクトのsliceプロパティを参照する
  2. Stringのプロトタイプにあるslice(Functionのオブジェクト)を返す
  3. 返されたsliceを実行する
プロトタイプチェーン

プロトタイプはプロパティを参照するときにクラス継承のルートに向かって探索していきます.
例えば,StringはObjectを継承しているため,Objectコンストラクタのプロトタイプを参照できます.

var str = new String;
str.toString; //=> function
サンプルコード

クラスの定義と継承をコードで表現すると以下のようになります.
これ以降の節で説明していきます.

(function () {
  var profile = {
        age: {value: 26},
        favorite_food: {value: 'orange'}
      }
    , aMan
  ;

  function Human () {}
  Human.prototype.eat = function () {
    console.log('yummy ' + this.favorite_food + '!');
  }

  function Man () {}

  Man.prototype = Object.create(Human.prototype);
  Man.prototype.dance = function () {
    console.log('put your hands up!');
  }

  aMan = Object.create(Man.prototype, profile);

 //プロパティの確認
  console.log(aMan.age);
  
  //constructor関数 [Function: Man]にはならない
  console.log(aMan.constructor); //=> [Function: Human]

  //関数実行の確認
  aMan.eat();
  aMan.dance();

  console.log(Man.eat);
  console.log(Man.prototype.eat);
}());
prototypeプロパティ

Functionオブジェクトはprototypeというプロパティを持っています.これが冒頭で説明したプロトタイプであり,実体はObjectオブジェクトです.prototypeのプロパティに設定した関数はインスタンスから参照可能です.
サンプルコードでは下記のように関数宣言の後に,Humanのプロトタイプにeat関数を,Manのプロトタイプにdance関数を追加しています.

  Human.prototype.eat = function () {
    console.log('yummy ' + this.favorite_food + '!');
  }
  Man.prototype.dance = function () {
    console.log('put your hands up!');
  }

コンストラクタはObject.createまたはnew式を適用してインスタンスを生成します.
サンプルコードでは下記のように使用されています.

  aMan = Object.create(Man.prototype, profile);

Functionでないオブジェクトにはprototypeプロパティはありませんが,内部プロパティの[[Prototype]]はすべてのオブジェクトが持っています.
コンストラクタから生成されたインスタンスは内部プロパティ[[Prototype]]によってコンストラクタのprototypeへの参照を実現しています.

状態の保持

コンストラクタはオブジェクトを生成しますが,生成するオブジェクトはthisという名前でコンストラクタ関数本体から参照できます.
また,thisにプロパティを設定することで生成したインスタンスのプロパティとして状態を保持できます.
Object.createの第二引数に下記のような形のオブジェクトを渡すことで,生成するインスタンスのage, favorite_foodプロパティに値をセットできます.

  var profile = {
        age: {value: 26},
        favorite_food: {value: 'orange'}
      }

関数内で宣言された変数はそのスコープのみで参照可能となるため,コンストラクタ内で宣言された変数は生成したオブジェクト内では参照できません.

function MyScope () {
  var pri = 'private';
  this.pub = 'public';
}

var scope = new MyScope;
scope.pub; //=> 'public'
scope.pri; //=> undefined
new演算子を付け忘れた場合
function Constructor () {
  this.hoge = 1;
}
var hoge = Constructor();

上記の場合,関数はグローバルオブジェクトを起点として実行されるため.
上記のhogeはグローバルオブジェクトにセットされるため,意図しない値がグローバルに確保されることになります.

継承

サンプルコードではManがHumanを継承していることを表現しています.

  function Human () {}
  Human.prototype.eat = function () {
    console.log('yummy ' + this.favorite_food + '!');
  }

  function Man () {}

  Man.prototype = Object.create(Human.prototype);
  Man.prototype.dance = function () {
    console.log('put your hands up!');
  }

  aMan = Object.create(Man.prototype, profile);

下記のようにManインスタンスはHumanのプロトタイプがもつ関数とManのプロトタイプがもつ関数両方を実行できます.

  aMan.eat(); //=> yummy orrange!
  aMan.dance(); //=> put your hands up!
JavaScriptにおけるクラス

JavaScriptにはクラス定義構文は(ES5には)ありませんが,分類としてのクラスは実現されています.
ですが,同じクラスであるということに対する考え方がJavaScriptとその他のプロトタイプベースでない言語とは異なっています.

Javaなどのクラスベースのオブジェクト指向ではおなじAPI群を持っているものを同一のクラスとみなしますが,JavaScriptなどのプロトタイプベース(インスタンスベース)のオブジェクト指向では,同一のプロトタイプを参照するかどうかによって同一のクラスとみなします.
これによってクラスベースでは1つのインスタンスのために新たにクラスを定義する必要がありますが,JavaScriptなどのプロトタイプベースではそのインスタンスのみに処理を追加すればよいという考え方の違いがあります.

power-assert使ってみた - 東京Node学園 10時限目に参加してきました

東京Node学園 10時限目に参加してきました.
東京Node学園 10時限目 - connpass
そのなかで@t_wadaさんが紹介されていて個人的に興味があったJavaScriptのテスト用ライブラリpower-assertを使ってみました.
具体的にはNode.jsのテストフレームワークmocha用に書いたコードをpower-assertで実行してみました.

power-assert作成の背景

@t_wadaさんが発表でおっしゃっていたpower-assert作成の背景を自分なりにまとめてみます.

テストライブラリを選ぶときは常に書きやすさと失敗時の情報量を天秤にかける必要があります.
例えば,以下のようなテストコードを書いたとき.

 var assert = require('power-assert');

 describe('Array', function(){
   beforeEach(function(){
     this.ary = [1, 2, 3];
   });
   describe('#indexOf()', function(){
     it('should return index when the value is present', function(){
       var zero = 0, two = 2;
       assert(this.ary.indexOf(zero) === two);
     })
   })
 });

このテストを実行すると以下のような結果が得られます.

$ mocha test/power_assert_test.js

  ․

  0 passing (4 ms)
  1 failing

  1) Array #indexOf() should return index when the value is present:
     AssertionError: false == true
(以下略)

「AssertionError: false == true」という出力がされますが,これだけではなぜ失敗したのかがわかりにくいです.また,assertメソッドの引数はどちらが期待値でどちらが実際値を入れるべきなのか間違えやすいですし,テストフレームワークによって違ったりもします.
じゃあRspec風の「expect( ).to ...」のような書き方ができればいいのではと考えると,Matcherとか書き方とかを覚えるのが大変です.

そこで,assertionの出力の情報量が多ければ暗記量が少なくて書きやすくなり,失敗時の情報量も多くなってわかりやすくなるのではという発想でpower-assertを作成したとのことでした.

つまり,書きやすさと失敗時の情報量の多さどちらも得られるのがpower-assertの特徴ということになります.

power-assertの準備

実際にpower-assertを使うための準備をします.

package.jsonを準備

プロジェクトを新規で作る場合は下記のコマンドを実行していくつかの設定項目を入力すると自動でpackage.jsonを生成してくれます.

$ npm init

または,package.jsonを以下の内容で保存します.

{}

package.jsonがないと次の手順で npm install したときにエラーになってしまいます.

npm install

プロジェクトのルートディレクトリで以下のコマンドを実行します.

$ npm install power-assert grunt-espower --save-dev

すると,package.jsonは以下のように追記されるはずです.

{
  "devDependencies": {
    "power-assert": "~0.1.5",
    "grunt": "~0.4.1",
    "grunt-espower": "~0.1.5"
  }
}

Gruntfile.jsの準備

gruntのtaskによってpower-assert用のファイルを生成するため,Gruntfile.jsを以下のように記述します.

module.exports = function(grunt) {
  var pkg = grunt.file.readJSON('package.json')
    , taskName
  ;

 grunt.initConfig({
   espower: {
     test: {
       files: [
         {
           expand: true, //Enable dynamic expansion,
           cwd: 'test/', //Src matches are relative to this path
           src: ['**/*.js'], //Actual pattern(s) to match
           dest: 'espowered/', //Destination path prefix
           ext: '.js' //Dest filepaths will have this extension
         }
       ]
     }
   }
 });

 for (taskName in pkg.devDependencies) {
   if (taskName.substr(0, 6) == 'grunt-') {
     grunt.loadNpmTasks(taskName);
   }
 }
};

これによって,testディレクトリ以下のテストファイルを基にしてespoweredディレクトリにpower-assert用のテストファイルが作成されるように設定できます.

テストを書く

出力の比較のために,power-assert作成の背景の節で記したテストコードをtestディレクトリにpower_assert_example-test.jsという名前で保存します.

以上で準備は完了です.

テストファイル生成と実行

gruntタスクを走らせてテストコードを生成します.

$ grunt espower:test
Running "espower:test" (espower) task

Done, without errors.

上記のように表示されればタスク実行が正常に完了しています.

テストを走らせてみます.

$ mocha espowered/power_assert_example-test.js

  ․

  0 passing (6 ms)
  1 failing

  1) Array #indexOf() should return index when the value is present:
     AssertionError: # (プロジェクトのルートディレクトリ)/test/power_assert_example-test.js:10

      assert(this.ary.indexOf(zero) === two);
                  |   |       |     |   |
                  |   |       |     |   2
                  |   -1      0     false
                  [1,2,3]
(以下略)

assertの中身の情報が出力されていて,[1, 2, 3]というaryで呼び出したindexOfというメソッドの結果が-1になっていて右辺の2とは一致せずに失敗していることが分かります.

power-assertが何をやっているか

gruntのtaskでは以下のようなことをやっているみたいです.
記述したテストコード(今回ではtestディレクトリ以下に保存したファイルの中身)をパースしてAST(abstract syntax tree, 抽象構文木)にする.次にASTからpower-assert用のテストメソッドが使える形のテストコードに変更・生成する.

そして,生成されたテストコードを実行することで先述のような出力が得られるという仕組みだと思います.

※注意
READMEにも書いていますが,この記事の執筆時点ではpower-assertはまだα版なので使い方や動作などは変わる可能性があります.

grunt0.4.1インストールしてみた

grunt使ったことなかったのでインストールしてみました.
対象のgruntのバージョンは0.4.1です.

gruntとは

Grunt: The JavaScript Task Runnerによるとgruntは「The JavaScript Task Runner」と書かれています.
よく「JavaScript用のビルドツール」と紹介されていますが,ビルドツールにも使えるだけでその他のこともできます.
僕のビルドという言葉の理解が間違ってるだけかもしれませんが.

gruntでできること

JavaScriptファイルのminifyや結合, CoffeeScriptなどのコンパイル,UnitTestの実行, Lintをかける, etc.
プラグインを追加することでその他にも自分のやりたいことを実行させることができます.
Plugins - Grunt: The JavaScript Task Runner
Cucumberを実行するプラグインなんかもあります.
grunt-rcukes

インストール

npmでインストールします.
0.4.xからgrunt-cliでgruntコマンドを実行するようになりました.
なので,grunt0.3が入っている人はnpm uninstallしましょう.

npm uninstall -g grunt

grunt-cliをインストールします.

npm install -g grunt-cli

npmのバージョンが古いとエラーが出てインストールできないことがあるみたいです.
grunt-cliインストール時にTypeError: Arguments to path.resolve must be stringsと出た | ノイズビン

設定や具体的な使い方は別のエントリにします.
早く使いたい方は以下が参考になると思います.
Getting started - Grunt: The JavaScript Task Runner

RSpec Mocks 2.14で導入されたSpyとRSpec2.14の新記法について

RSpec 2.14 で新記法が導入されてRSpec Mocks 2.14でSpyが導入されていたので整理してみました.
stub()とshould_receive()の代わりにallow().to receive()とexpect().to receive()が使えるようになっていたり,Spyが導入されたことでRSpecを用いたテストの記述が柔軟になり,わかりやすく書けるようになりました.今までのRSpec Mocksで使ってきたMock ObjectとTest Spyの違いについても少し整理してみました.

Myron Marston » RSpec 2.14 is released!
このリリースノートのサンプルコードをいくつか引用して説明します.

Rspec Mocksのmessage expectationに新しい記法が追加されました.
mailer = double("Mailer")

# old syntax:
mailer.stub(:deliver_welcome_email)
mailer.should_receive(:deliver_welcome_email).with(an_instance_of(User))

# new syntax
allow(mailer).to receive(:deliver_welcome_email)
expect(mailer).to receive(:deliver_welcome_email).with(an_instance_of(User))

stub()の代わりにallow().to receive()が,should_receive()の代わりにexpect().to receive()が使えるようになりました.

RSpec MocksにSpyが追加されました.

※注意:下記のコードではUserCreationService.new()の中でdeliver_welcome_emailが呼び出されるという前提があるみたいです
Spyを使わない場合

mailer = double("Mailer")
expect(mailer).to receive(:deliver_welcome_email).with(an_instance_of(User))
UserCreationService.new(mailer).create_user(params)


Spyを使う場合

mailer = double("Mailer", deliver_welcome_email: nil)
UserCreationService.new(mailer).create_user(params)
expect(mailer).to have_received(:deliver_welcome_email).with(an_instance_of(User))

違いは,deliver_welcome_emailが実行された後にhave_receivedを使ってメソッド呼び出しや引数などを検査できるところです.
テストのセットアップ→テスト対象の動作の実行→結果の検査という流れが書きやすくなりました.

上記のリリースノートには,double().as_null_objectを使っても同じような書き方ができると書かれていました.

mailer = double("Mailer").as_null_object
UserCreationService.new(mailer).create_user(params)
expect(mailer).to have_received(:deliver_welcome_email).with(an_instance_of(User))
補足 Mock ObjectとTest Spyについて

Test Spyの説明は以下のエントリが参考になると思います.
xUnit Test PatternsのTest Doubleパターン(Mock、Stub、Fake、Dummy等の定義) - 千里霧中
特に下記の部分がわかりやすいと思います.

 なおMock ObjectとTest Spyは両方とも間接出力を検証するためのTest Doubleです。ただ「Mock ObjectはMock Object内で間接出力結果を評価する」のに対し、「Test Spyは間接出力を保持するだけで、間接出力結果の評価は後からテストコード上で行う」という違いがあります。

間接出力という言葉に関してはエントリ内で説明されています.
RSpec Mocksにおいてはテストコード上での評価は行えるので,Spyの機能を使う旨みは記述の順番を自然に書けるということかなと個人的には思いました.

参考ページ

RSpec MocksのSpyに関しては下記も参考になります.
Spies - RSpec Mocks - RSpec - Relish

RSpec 2.14での変更点は他にも何点かあります.
詳しくは冒頭にもあげた下記のリリースノートが参考になります.
Myron Marston » RSpec 2.14 is released!

TDDeXchange in Tokyo 参加レポート - 主にTDDの自殺の所感 #tddex

先月末の話ですが,TDDeXchange in Tokyoに参加してきました.
内容は@kyon_mmさんの「TDDの自殺」という題の発表と,参加者がTDDできる環境のPCを持ち寄って実際にやってみるというものでした.発表を聞いて質問してみてTDDの自殺の意味と危険性,テストダブルを使う時の心得などを自分なりに整理することができたのでとても有意義な時間でした.

当日の流れや概要は以下のリンクでわかると思います.
TDDeXchange in Tokyo #TDDeX - Togetter
TDDeXchange in Tokyo - connpass

参加の経緯としては,RubyRspecJavaScriptとmochaなどでTDDをやったことはあるのですが,社外の方がTDDに関してどういう考えを持ってるかとかどうやって実践してるかが気になっていてなにか得られればいいなーということで行ってみました.

TDDの自殺

一つ目のコンテンツとして, @kyonn_mm さんの「TDDの自殺」という題の発表がありました.スライドは以下のリンクから見ることができます.
TDDの自殺 #TDDeX
以下のエントリも合わせて読むとわかりやすくなるかもしれません.
TDDの自殺 #kyon_mmAdvent - うさぎ組

ここからは自分なりの理解をまとめますが,きょんさんが仰ったこととは違う解釈になってしまってる可能性もあります.つっこみとか質問があればして欲しいです.

アプリケーションドメインとソリューションドメインという言葉が出てきていますが,自分なりに以下の様な解釈をしました.

  • アプリケーションドメイン
    要求仕様で表現される領域とその記述
  • ソリューションドメイン
    アプリケーションドメインを実現するためのロジック.プログラミング言語による記述やライブラリの組み合わせなど

この話の前提としてTDDは開発者を支援するフレームワークであるという定義と,「アプリケーションドメインの記述」=「ソリューションドメインの記述」となるコードが理想だという定義をしています.

前者の定義に関しては@t_wadaさんのTDD BootCampでのお話がわかりやすいと思うので興味がある人はどうぞ.
Ustream.tv: ユーザー brtriver: tddbc tokyo 1.5 基調講演, 日本でのTDD第一人者である 和田 卓人こと、@t_wada さんの講演. コンピュータ...
TDD Boot Camp

後者の定義に関しては形式手法とかDDD(ドメイン駆動設計)とかDSL(ドメイン特化言語)とかDSM(ドメイン固有モデリング)とかのどれか一つでも知っている人であれば納得できると思います.

で,やっと本題の内容(といっても僕の解釈)を書きます.

TDDとドメインの関係

TDDではまずテストコードに達成したいことを表現します.つまり最初にテストコードにアプリケーションドメインを記述します.

その後プロダクトコード(ソリューションドメイン)を実装します.更にその後,プロダクトコードをリファクタリングします.

具体的にはプロダクトコードをわかりやすい表現にしたりメソッド分割をしたりしますが,これはプロダクトコードにアプリケーションドメインの記述を埋め込んでいっていることになります.

TDDの自殺の解釈

TDDをやっているとテストダブル(モックやスタブ)を使う場面がたくさんあると思います.例えば,複数のテストでモジュールAの振る舞いをテストダブルに置き換えて記述するとします.それ自体には問題はないと思います.

ですがテストダブルに本来ないような振る舞いをさせてしまったり,テストダブルにしたモジュールのテストが不十分だった場合はプロダクトコードに目的の振る舞いが含まれなかったり意図しない振る舞いを埋め込んだりすることにつながります.

そういう事態をTDDは引き起こしやすくて,それが起こってしまうとTDDが自殺している状況になるのだと思います.他にもいろいろな例があるんだと思いますが,テストダブルの例は自分も経験があるし個人的にわかりやすかったです.

解決策

発表はTDDの自殺の問題提起まででした.テストダブルに関しては,テストダブルにしたモジュールやクラスのテストをしっかりするしかないのかなと思っていて,そういう質問をしてみたんですがやっぱり現時点ではそうするしかないっていう結論になりました.
ですが,きょんさんはモックやスタブを使わないテストフレームワークを作成中と仰っていたので個人的にはとても興味がわきました.


主催の@kyon_mmさん,会場を提供してくださった株式会社ドリコムの皆様,当日の参加者の皆様,貴重な機会を提供していただいきありがとうございました.

当日やったTDDの課題に関しても書きたかったのですが,長くなったので別のエントリにします.
発表を聞いて議論できたことで当初の目的はほぼ達成できたので書きたいことは書いてしまったかもしれませんが.