概要

前回のつづき。

 

Node.js って何? に答えるシリーズ

  • とりあえずこれを言えれば格好はつくぜリスト --> 前の記事に収録
  • これを言えれば Node.js のパッケージ管理システムについては格好がつくぜリスト --> 前の記事に収録
  • これを言えれば Node.js のモジュールシステムについては格好がつくぜリスト --> 前の記事に収録
  • これを言えれば Node.js の非同期処理については格好がつくぜリスト --> 前の記事に収録
  • これを言えれば Node.js のユニットテストについては格好がつくぜリスト --> この記事に収録
  • これを言えれば Node.js のコンパイルについては格好がつくぜリスト --> この記事に収録
  • Node.js の構文でオモロかったところリスト --> この記事に収録

 

これを言えれば Node.js のユニットテストについては格好がつくぜリスト

  • ユニットテストはソフトウェアテストの中で最も投資効率が高い。
  • Node.js でやるなら、
    • フレームワークには Mocha、
    • アサーションには assert, Chai、
    • テストダブルには Sinon.JS, testdouble.js、
    • カバレッジには Istanbul、
    • それらすべての機能をもつフルスタックフレームワーク: Jest、
    • ……を使う。
  • テストダブルっていうのが緑さんにとっては目新しいな。ひとつユニットテストを書くとして、ユニットテストの依存先がすでに十分テストされたものである場合、テスト済みのものに対して重複したテストを書くことになってしまう。テストダブルを使って重複を排除しよう。
  • Jest は人気。テストを並列に実行するので高速であることと、フルスタックなので手順や設定が少なくて済むこと。もうこれ一択じゃん。
  • Mocha との違いは次のとおり。
    • テストの関数名が it に加え test も使える、
    • before -> beforeAll, after -> afterAll という関数名で提供される、
    • describe() 外に書いたフックは、 Mocha では全テストファイルに対するグローバルなフックとなるが、 Jest ではそのファイル内の全テストケースに対するフックとして機能する、
    • Mocha みたいな assert.sameDeepMembers() がないので、配列を比較するときはソートしないといけない、
    • Jest のテストダブルは Sinon でいうところのスタブであるが、名称はモックである、
    • ……などなど。
  • Web API をテストするときは、 Mocha にも Jest にもその機能がない。そのときは supertest モジュールを使う。
  • デバッグのためにあちらこちらに console.log() 書くのは非効率的。 Node.js のインスペクタ機能を使え。

 

これを言えれば Node.js のコンパイルについては格好がつくぜリスト

  • ガンバって ES2015 (ES6) JavaScript を書いたとしても、実行環境が ES5 止まりのヘボ環境だったら動かない。そこで、コンパイルして ES5 コードに変換しよう。 Babel ツールを使う。
  • Babel はもともと "6to5" という名前だった。もちろん、 ES6 コードを ES5 に変換するという意味だ。
  • あと TypeScript で書いてしまった場合も JavaScript へ変換する必要がある。それは TypeScript のコンパイラがやる。
    • TypeScript は Microsoft が2012年に始めた言語だ。
npm install -D typescript @types/node

# typescript をインストールすると tsc コマンドが有効になる。
# これで tsconfig.json ができる。
npx tsc --init
  • コンパイルだとか変換だとか軽く言っているけれど実際何をしているんだ?
    • ソースコードから Abstract Syntax Tree (AST、抽象構文木) を生成 (パースという) して、
    • AST に変更を加え (変換) て、
    • AST からソースコードを生成する、
    • ……という手順である。
  • 具体的には、たとえば ES2015 のアロー関数は ES2015 未対応の環境では動かないので、次のように変換しなければならん。
    • [1, 2].map(n => n * 2) -> [1, 2].map(function (n) { return n * 2 })
  • これは AST でいうと……
    • ArrowFunctionExpression -> FunctionExpression に変更して、
    • FunctionExpression の body に BlockStatement, ReturnStatement を追加、
    • ……することである。
  • あとファイルの先頭に 'use strict' つけたり import 文を require() に変換したりする。

 

Node.js の構文でオモロかったところリスト

関数について。

// 知らなかった関数定義の方法。
const add = function addFn(a, b) { return  a + b };
add.name;  // --> 'addFn' と出る。知らなかった。 add じゃないのか。

// ES2015 で追加されたアロー関数が導入された。意味は↑と同じ。
// ヤメようや、いくつもやり方を作るのは……。
const add1 = (a, b) => { return a + b };
const add2 = (a, b) => a + b;
const add3 = a => a + 1;

// ぼくが個人的によく使う関数定義。
// これは "関数宣言" ではなくて "関数式" っていうらしい。
const add = function(a, b) { return a + b };

// こっちが "関数宣言"。
// 関数宣言では hoisting (巻き上げ) が発生する。宣言前に参照できる現象だ。
function add(a, b) { return a + b };

スプレッド構文について。

// オブジェクトのコピーとか、コピーしつつ要素を追加できるやつか。これは良いね。
const obj2 = { ...obj1, propC: 3 };

// 配列にも使えるヨ。
const arr3 = ['a', ...arr2, 'b'];

レスト構文について。

// obj2 から propA が削除されたものが obj3 に格納される……
// 何これクッソ読みづらくない? ヤメようや。
const { propA, ...obj3 } = obj2;

// 配列にも使えるヨ。いや、レスト構文は読みづらすぎるのでヤメます。
const [head1, head2, ...arr4] = arr2;

クラスについて。 JavaScript のクラスなんて見たことないわ。

// クラスに定義したメソッド、コンストラクタ、 getter, setter は prototype に追加される。
// どういうことかっていうとこういう↓ことだ。
fooInstance.__proto__ === Foo.prototype;

// 実は instanceof は __proto__ と prototype を比較している。ほー、なるほど。

ジェネレータについて。へー、 JavaScript にもジェネレータあるんだ。

function* generatorFunc() {
    yield 1
    yield 2
}
const generator = generatorFunc();
generator.next();  // --> { value: 1, done: false }
generator.next();
generator.next();  // --> { value: undefined, done: true }
generator.next(true);  // --> カウンタがリセットされる。えぇwマジかw

「2GBのファイルを読み込んで別ファイルに書き込もうとしたら、メモリが圧迫されて大変だったってことあるやろ?」ねえよ。「そういうときは全部読み込んでから書き込むんじゃなくて、ストリームを使って、読み込んだはしから書き込んでいこう!」ねえよそんなコト。

fs.createReadStream(src)
  // 下の方にも on error があるけど、ここにも挟まないとエラーが伝播しちゃう。
  .on('error', err => console.info('エラーイベント', err.message))
  // 読み込んだやつを暗号化してみる。
  .pipe(crypto.createHash('sha256'))
  // 読み込みストリームから書き込みストリームへ pipe する。
  .pipe(fs.createWriteStream(dest))
  .on('finish', () => console.info('コピー完了!'))
  // エラーにも備えるぜ!
  .on('error', err => console.info('エラーイベント', err.message))

// でもこっちでいいです。最初に言え。
stream.pipeline(
  // pipe() したい2つ以上のストリーム。
  fs.createReadStream('no-such-file.txt'),
  fs.createWriteStream('dest.txt'),
  // コールバック。
  err => err
    ? console.error('エラー発生', err.message)
    : console.info('正常終了')
);