『テスト駆動開発』第1部をJavaScript(Node.js + AVA)で実習する

書籍『テスト駆動開発』の第1部には、テスト駆動開発で多国通貨を実装するサンプルが載っています。このサンプルは丁寧に書かれていて参考になりますが、Java + JUnitなので、自分の馴染みのある言語+テスティングフレームワークではどうなるのか気になります。

そこで、本記事では、JavaScript(Node.js + AVA)で『テスト駆動開発』の第1部を実習します。

なお、本記事のコードはGitHubの下記リポジトリでも公開しています。
https://github.com/ryo-utsunomiya/tdd-js-ava

## 環境構築

Node.jsがインストール済みであることを前提にします。私の手元の環境は以下の通りです。

- Node.js v8.7.0
- npm 5.5.1

### AVAのインストール

テスティングフレームワークは[AVA](https://github.com/avajs/ava)を使用します。AVAはシンプルなAPIとモダンな機能を備えています。

```
npm init
npm install ava --save-dev
./node_modules/.bin/ava --init
```

AVAの動作確認として、1つテストを書いてみましょう。AVAはBabelによるトランスパイル機能を内蔵しているので、テストはES2015+で書くことができます。

```js
// test.js
import test from 'ava';

test('foo', (t) => {
  t.pass();
});
```

実行は`npm test`です。以下のような結果が表示されればAVAのセットアップは完了です。

<img width="434" alt="setup ava" src="https://qiita-image-store.s3.amazonaws.com/0/33450/2b0b20a3-769f-e650-23c8-cf91b3c652ce.png">

## Babel設定

テストがES Modulesなのにコードの方はCommonJSなのは気持ち悪いので、コードの方もBabelでトランスパイルするよう設定します。package.jsonに以下を追記しましょう。

```json
  "ava": {
    "require": [
      "babel-register"
    ]
  },
  "babel": {
    "presets": [
      "@ava/stage-4"
    ]
  }
```

次に、モジュールを読み込んで動作させるテストを書きます。

```js
// test.js
import test from 'ava';
import foo from './foo';

test('test foo()', t => {
  t.is(foo(), 'foo');
});
```

モジュールの方も書きます。

```js
export default () => 'foo';
```

もう一度実行してみましょう。今度は`npm t`という短縮コマンドを使ってみるとよいでしょう。

「1 passed」と表示されればセットアップ完了です！

## 第1章

はじめにテストを書きます。ファイル名を「XX.test.js」とすると、AVAはそのファイルをテストとみなします。

```js
// Money.test.js
import test from 'ava';

test('test multiplication', t => {
  const five = new Dollar(5);
  five.times(2);
  t.is(10, five.amount);
});
```

まだ `Dollar` オブジェクトは定義されていないので、ReferenceErrorが発生してテストは失敗します。

ReferenceErrorを解消するための最小限の実装をしてみます。

```js
// Dollar.js
export default class Dollar {
  constructor(amount) {
    this.amount = amount;
  }

  times(multiplier) {

  }
}
```

次に、テストでこのファイルを読み込みます。

```js
import test from 'ava';
import Dollar from './Dollar';

test('test multiplication', t => {
  const five = new Dollar(5);
  five.times(2);
  t.is(10, five.amount);
});
```

今度は、エラーではなくアサーションが失敗します。出力を見てみましょう。

<img width="432" alt="スクリーンショット 2017-10-15 15.08.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/33450/656c635d-2138-4256-c389-2638455597de.png">

どこで失敗しているか、わかりやすく表示してくれます。AVAのアサーションには、『テスト駆動開発』訳者のt-wadaさん作の[power-assert](https://github.com/power-assert-js/power-assert)が使われています。

コードを編集するたびに `npm t` を実行するのは面倒なので、`npm t -- --watch` で、コードの変更に応じてテストが実行されるようにしておくと便利です。

この辺で下準備が終わったので、以降は各章の進捗をまとめて載せていきます。詳細なステップは書籍を参照してください。

第1章完了時点では以下のようになります。

```js
import test from 'ava';
import Dollar from './Dollar';

test('test multiplication', (t) => {
  const five = new Dollar(5);
  five.times(2);
  t.is(10, five.amount);
});
```

```js
export default class Dollar {
  constructor(amount) {
    this.amount = amount;
  }

  times(multiplier) {
    this.amount *= multiplier;
  }
}
```

## 第2章

`Dollar.times()` のテストを増やし、テストをパスするように実装します。

```js
import test from 'ava';
import Dollar from './Dollar';

test('test multiplication', (t) => {
  const five = new Dollar(5);
  t.is(10, five.times(2).amount);
  t.is(15, five.times(3).amount);
});
```

```js
export default class Dollar {
  constructor(amount) {
    this.amount = amount;
  }

  times(multiplier) {
    return new Dollar(this.amount * multiplier);
  }
}
```

## 第3章

DollarをValueObjectにするために、オブジェクトの等価性を比較できるようにします。テストケースが複数になったので、`async`を導入して並列実行できるようにしましょう。

```js
import test from 'ava';
import Dollar from './Dollar';

test('multiplication', async (t) => {
  const five = new Dollar(5);
  t.is(10, five.times(2).amount);
  t.is(15, five.times(3).amount);
});

test('equality', async (t) => {
  t.true(new Dollar(5).equals(new Dollar(5)));
});
```

```js
export default class Dollar {
  constructor(amount) {
    this.amount = amount;
  }

  times(multiplier) {
    return new Dollar(this.amount * multiplier);
  }

  equals(object) {
    return this.amount === object.amount;
  }
}
```

## 第4章

この章では、オブジェクト同士を比較するよう、テストを改善します。しかし、JavaScriptには、Javaの`Object.equals`のようなオブジェクトの等価性比較をオーバーライドするためのAPIはありません。そのため、本章のリファクタリングは適用できません。

## 第5章

新しくFrancオブジェクトを追加します。この時点では、テストも実装もコピペです。

```js
import test from 'ava';
import Dollar from './Dollar';
import Franc from './Franc';

test('multiplication', async (t) => {
  const five = new Dollar(5);
  t.true(new Dollar(10).equals(five.times(2)));
  t.true(new Dollar(15).equals(five.times(3)));
});

test('franc multiplication', async (t) => {
  const five = new Franc(5);
  t.true(new Franc(10).equals(five.times(2)));
  t.true(new Franc(15).equals(five.times(3)));
});

test('equality', async (t) => {
  t.true(new Dollar(5).equals(new Dollar(5)));
});
```

```js
export default class Franc {
  constructor(amount) {
    this.amount = amount;
  }

  times(multiplier) {
    return new Franc(this.amount * multiplier);
  }

  equals(object) {
    return this.amount === object.amount;
  }
}
```

## 第6章

ここからは、DollarとFrancの共通部分をMoneyに引き上げていきます。

```js
test('equality', async (t) => {
  t.true(new Dollar(5).equals(new Dollar(5)));
  t.false(new Dollar(5).equals(new Dollar(6)));
  t.true(new Franc(5).equals(new Franc(5))); // テストケース追加
  t.false(new Franc(5).equals(new Franc(6))); // テストケース追加
});
```

```js
export default class Money {
  constructor(amount) {
    this.amount = amount;
  }

  equals(money) {
    return this.amount === money.amount;
  }
}
```

```js
import Money from './Money';

export default class Dollar extends Money {
  times(multiplier) {
    return new Dollar(this.amount * multiplier);
  }
}
```

```js
import Money from './Money';

export default class Franc extends Money {
  times(multiplier) {
    return new Franc(this.amount * multiplier);
  }
}
```

## 第7章

DollarとFrancが等しくなってしまうバグがあるので、修正します。JavaScriptにはクラスはないので、コンストラクタの名前を比較することで代用しています。イマイチな実装ですが、後でリファクタリングするので…。

```js
test('equality', async (t) => {
  t.true(new Dollar(5).equals(new Dollar(5)));
  t.false(new Dollar(5).equals(new Dollar(6)));
  t.true(new Franc(5).equals(new Franc(5)));
  t.false(new Franc(5).equals(new Franc(6)));
  t.false(new Franc(5).equals(new Dollar(5))); // テストケース追加
});
```

```js
export default class Money {
  constructor(amount) {
    this.amount = amount;
  }

  equals(money) {
    return this.amount === money.amount
      && this.constructor.name === money.constructor.name;
  }
}
```

## 第8章

ここでは、timesメソッドをMoneyに引き上げる前準備として、DollarとFrancのFactory MethodをMoneyに作成します。

ここで問題発生。以下のような複数の循環参照があるモジュールでは、参照を解決できないのです。

```js
import Dollar from './Dollar';
import Franc from './Franc';

export default class Money {
  static dollar() {
    return new Dollar();
  }
  static franc() {
    return new Franc();
  }
}
```
```js
import Money from './Money';

export default class Dollar extends Money {}
```
```js
import Money from './Money';

export default class Franc extends Money {}
```

全てのクラスを同一ファイル内に含めれば正常に実行できるので、これはモジュールのインポートの問題だと思います。

今後DollarとFrancは削除する予定なので、ひとまず、Money.jsにDollarとFrancを含めるようにして対応します。

```js
import test from 'ava';
import Money from './Money';

test('multiplication', (t) => {
  const five = Money.dollar(5);
  t.true(Money.dollar(10).equals(five.times(2)));
  t.true(Money.dollar(15).equals(five.times(3)));
});

test('franc multiplication', (t) => {
  const five = Money.franc(5);
  t.true(Money.franc(10).equals(five.times(2)));
  t.true(Money.franc(15).equals(five.times(3)));
});

test('equality', (t) => {
  t.true(Money.dollar(5).equals(Money.dollar(5)));
  t.false(Money.dollar(5).equals(Money.dollar(6)));
  t.true(Money.franc(5).equals(Money.franc(5)));
  t.false(Money.franc(5).equals(Money.franc(6)));
  t.false(Money.franc(5).equals(Money.dollar(5)));
});
```

```js
export default class Money {
  constructor(amount) {
    this.amount = amount;
  }

  equals(money) {
    return this.amount === money.amount
      && this.constructor.name === money.constructor.name;
  }

  static dollar(amount) {
    return new Dollar(amount);
  }

  static franc(amount) {
    return new Franc(amount);
  }
}

class Franc extends Money {
  times(multiplier) {
    return Money.franc(this.amount * multiplier);
  }
}

class Dollar extends Money {
  times(multiplier) {
    return Money.dollar(this.amount * multiplier);
  }
}
```

## 第9章

ここでは、通貨(currency)の概念を導入します。Javaでは Money.currency() はメソッド、 Money.currency はフィールドで併存可能ですが、JavaScriptでは併存できないので、Money.currencyはメソッドではなくプロパティにしています。

```js
// currencyのテストを追加
test('currency', (t) => {
  t.is('USD', Money.dollar(1).currency);
  t.is('CHF', Money.franc(1).currency);
});
```

```js
export default class Money {
  constructor(amount, currency) {
    this.amount = amount;
    this.currency = currency;
  }

  equals(money) {
    return this.amount === money.amount
      && this.constructor.name === money.constructor.name;
  }

  static dollar(amount) {
    return new Dollar(amount, 'USD');
  }

  static franc(amount) {
    return new Franc(amount, 'CHF');
  }
}

class Franc extends Money {
  times(multiplier) {
    return Money.franc(this.amount * multiplier);
  }
}

class Dollar extends Money {
  times(multiplier) {
    return Money.dollar(this.amount * multiplier);
  }
}
```

## 第10章

times() メソッドをMoneyクラスに引き上げ、同じ通貨かの比較はcurrencyプロパティを使うようにします。DollarとFrancを消す準備ができました。

```js
import test from 'ava';
import { Money, Franc } from './Money';

test('multiplication', (t) => {
  const five = Money.dollar(5);
  t.true(Money.dollar(10).equals(five.times(2)));
  t.true(Money.dollar(15).equals(five.times(3)));
});

test('franc multiplication', (t) => {
  const five = Money.franc(5);
  t.true(Money.franc(10).equals(five.times(2)));
  t.true(Money.franc(15).equals(five.times(3)));
});

test('equality', (t) => {
  t.true(Money.dollar(5).equals(Money.dollar(5)));
  t.false(Money.dollar(5).equals(Money.dollar(6)));
  t.true(Money.franc(5).equals(Money.franc(5)));
  t.false(Money.franc(5).equals(Money.franc(6)));
  t.false(Money.franc(5).equals(Money.dollar(5)));
});

test('currency', (t) => {
  t.is(Money.dollar(1).currency, 'USD');
  t.is(Money.franc(1).currency, 'CHF');
});

test('different class equality', (t) => {
  t.true(new Money(10, 'CHF').equals(new Franc(10, 'CHF')));
});

```

```js
export class Money {
  constructor(amount, currency) {
    this.amount = amount;
    this.currency = currency;
  }

  equals(money) {
    return this.amount === money.amount
      && this.currency === money.currency;
  }

  times(multiplier) {
    return new Money(this.amount * multiplier, this.currency);
  }


  static dollar(amount) {
    return new Money(amount, 'USD');
  }

  static franc(amount) {
    return new Money(amount, 'CHF');
  }
}

export class Franc extends Money {
}

export class Dollar extends Money {
}

```

## 第11章

DollarとFrancはもう不要です。消してしまいましょう。

```js
import test from 'ava';
import Money from './Money';

test('multiplication', (t) => {
  const five = Money.dollar(5);
  t.true(Money.dollar(10).equals(five.times(2)));
  t.true(Money.dollar(15).equals(five.times(3)));
});

test('equality', (t) => {
  t.true(Money.dollar(5).equals(Money.dollar(5)));
  t.false(Money.dollar(5).equals(Money.dollar(6)));
  t.false(Money.franc(5).equals(Money.dollar(5)));
});

test('currency', (t) => {
  t.is(Money.dollar(1).currency, 'USD');
  t.is(Money.franc(1).currency, 'CHF');
});
```

```js
export default class Money {
  constructor(amount, currency) {
    this.amount = amount;
    this.currency = currency;
  }

  equals(money) {
    return this.amount === money.amount
      && this.currency === money.currency;
  }

  times(multiplier) {
    return new Money(this.amount * multiplier, this.currency);
  }


  static dollar(amount) {
    return new Money(amount, 'USD');
  }

  static franc(amount) {
    return new Money(amount, 'CHF');
  }
}
```

## 第12章

ここからは、異なる通貨の加算を実装します。まず、同一通貨の加算のテストを書きます。

```js
import test from 'ava';
import Money from './Money';
import Bank from './Bank';

test('multiplication', (t) => {
  const five = Money.dollar(5);
  t.true(Money.dollar(10).equals(five.times(2)));
  t.true(Money.dollar(15).equals(five.times(3)));
});

test('equality', (t) => {
  t.true(Money.dollar(5).equals(Money.dollar(5)));
  t.false(Money.dollar(5).equals(Money.dollar(6)));
  t.false(Money.franc(5).equals(Money.dollar(5)));
});

test('currency', (t) => {
  t.is(Money.dollar(1).currency, 'USD');
  t.is(Money.franc(1).currency, 'CHF');
});

test('simple addition', (t) => {
  const five = Money.dollar(5);
  const sum = five.plus(five);
  const bank = new Bank();
  const reduced = bank.reduce(sum, 'USD');
  t.true(reduced.equals(Money.dollar(10)));
});

```

書籍では、ここでExpressionというインタフェースを導入しています。しかし、JavaScriptにはインタフェースはありません。クラスで代用するのも違和感があったのと、この時点ではExpressionインタフェースは何の仕事もしないので、ひとまずExpressionは無視してBankだけ仮実装します。

```js
export default class Money {
  constructor(amount, currency) {
    this.amount = amount;
    this.currency = currency;
  }

  equals(money) {
    return this.amount === money.amount
      && this.currency === money.currency;
  }

  times(multiplier) {
    return new Money(this.amount * multiplier, this.currency);
  }

  plus(addend) {
    return new Money(this.amount + addend.amount, this.currency);
  }

  static dollar(amount) {
    return new Money(amount, 'USD');
  }

  static franc(amount) {
    return new Money(amount, 'CHF');
  }
}
```

```js
import Money from './Money';

export default class Bank {
  reduce(source, to) {
    return Money.dollar(10);
  }
}
```

## 第13章

加算処理を表すクラスを追加していきます。

```js
// テスト追加
test('plus returns Sum', (t) => {
  const five = Money.dollar(5);
  const sum = five.plus(five);
  t.is(five, sum.augend);
  t.is(five, sum.addend);
});

test('reduce sum', (t) => {
  const sum = new Sum(Money.dollar(3), Money.dollar(4));
  const bank = new Bank();
  const result = bank.reduce(sum, 'USD');
  t.true(Money.dollar(7).equals(result));
});

test('reduce money', (t) => {
  const bank = new Bank();
  const result = bank.reduce(Money.dollar(1), 'USD');
  t.true(result.equals(Money.dollar(1)));
});

```

本章の主役、Sumクラスを追加します。

```js
import Money from './Money';

export default class Sum {
  constructor(augend, addend) {
    this.augend = augend;
    this.addend = addend;
  }

  reduce(to) {
    const amount = this.augend.amount + this.addend.amount;
    return new Money(amount, to);
  }
}
```

Bank.reduce()は渡されたもののreduce()メソッドを呼ぶようにします。ここには、SumまたはMoneyを渡す想定です。

```js
import Money from './Money';

export default class Bank {
  reduce(source, to) {
    return source.reduce(to);
  }
}
```

Money.reduce() を追加します。

```js
import Sum from './Sum';

export default class Money {
  constructor(amount, currency) {
    this.amount = amount;
    this.currency = currency;
  }

  equals(money) {
    return this.amount === money.amount
      && this.currency === money.currency;
  }

  times(multiplier) {
    return new Money(this.amount * multiplier, this.currency);
  }

  plus(addend) {
    return new Sum(this, addend);
  }

  reduce() {
    return this;
  }

  static dollar(amount) {
    return new Money(amount, 'USD');
  }

  static franc(amount) {
    return new Money(amount, 'CHF');
  }
}
```

## 第14章

通貨の交換レートを実装していきます。

```js
// テスト追加
test('reduce money different currency', (t) => {
  const bank = new Bank();
  bank.addRate('CHF', 'USD', 2);
  const result = bank.reduce(Money.dollar(1), 'USD');
  t.true(result.equals(Money.dollar(1)));
});

test('identity rate', (t) => {
  t.is(new Bank().rate('USD', 'USD'), 1);
});
```

レートの取得処理はいったんオブジェクトで実装します。あとでMap等に変更するかもしれません。

```js
export default class Bank {
  constructor() {
    this.rates = {};
  }

  reduce(source, to) {
    return source.reduce(this, to);
  }

  addRate(from, to, rate) {
    this.rates[from + to] = rate;
  }

  rate(from, to) {
    if (from === to) return 1;
    return this.rates[from + to];
  }
}
```

reduceメソッドの第1引数にBankオブジェクトを渡して、交換レートを取得できるようにします。

```js
import Sum from './Sum';

export default class Money {
  constructor(amount, currency) {
    this.amount = amount;
    this.currency = currency;
  }

  equals(money) {
    return this.amount === money.amount
      && this.currency === money.currency;
  }

  times(multiplier) {
    return new Money(this.amount * multiplier, this.currency);
  }

  plus(addend) {
    return new Sum(this, addend);
  }

  reduce(bank, to) {
    const rate = bank.rate(this.currency, to);
    return new Money(this.amount / rate, to);
  }

  static dollar(amount) {
    return new Money(amount, 'USD');
  }

  static franc(amount) {
    return new Money(amount, 'CHF');
  }
}
```

```js
import Money from './Money';

export default class Sum {
  constructor(augend, addend) {
    this.augend = augend;
    this.addend = addend;
  }

  reduce(bank, to) {
    const amount = this.augend.amount + this.addend.amount;
    return new Money(amount, to);
  }
}
```

## 第15章

本章では、異なる通貨の加算処理を完成させ、Expressionインタフェースの拡充を行っています。が、本記事ではここまでExpressionインタフェース相当のものを実装しておらず、必要性も感じないので、このまま進みます。

```js
// テストケース追加
test('mixed addition', (t) => {
  const fiveBucks = Money.dollar(5);
  const tenFrancs = Money.franc(10);
  const bank = new Bank();
  bank.addRate('CHF', 'USD', 2);
  const result = bank.reduce(fiveBucks.plus(tenFrancs), 'USD');
  t.true(result.equals(Money.dollar(10)));
});
```

```js
import Money from './Money';

export default class Sum {
  constructor(augend, addend) {
    this.augend = augend;
    this.addend = addend;
  }

  reduce(bank, to) {
    const amount = this.augend.reduce(bank, to).amount +
      this.addend.reduce(bank, to).amount;
    return new Money(amount, to);
  }
}
```

## 第16章

Sum.times()メソッドを実装して仕上げです。また、テストの`async`が途中から消えていたので、改めてつけ直しています。

```js
import test from 'ava';
import Money from './Money';
import Bank from './Bank';
import Sum from './Sum';

test('multiplication', async (t) => {
  const five = Money.dollar(5);
  t.true(Money.dollar(10).equals(five.times(2)));
  t.true(Money.dollar(15).equals(five.times(3)));
});

test('equality', async (t) => {
  t.true(Money.dollar(5).equals(Money.dollar(5)));
  t.false(Money.dollar(5).equals(Money.dollar(6)));
  t.false(Money.franc(5).equals(Money.dollar(5)));
});

test('currency', async (t) => {
  t.is(Money.dollar(1).currency, 'USD');
  t.is(Money.franc(1).currency, 'CHF');
});

test('simple addition', async (t) => {
  const five = Money.dollar(5);
  const sum = five.plus(five);
  const bank = new Bank();
  const reduced = bank.reduce(sum, 'USD');
  t.true(reduced.equals(Money.dollar(10)));
});

test('plus returns Sum', async (t) => {
  const five = Money.dollar(5);
  const sum = five.plus(five);
  t.is(five, sum.augend);
  t.is(five, sum.addend);
});

test('reduce sum', async (t) => {
  const sum = new Sum(Money.dollar(3), Money.dollar(4));
  const bank = new Bank();
  const result = bank.reduce(sum, 'USD');
  t.true(Money.dollar(7).equals(result));
});

test('reduce money', async (t) => {
  const bank = new Bank();
  const result = bank.reduce(Money.dollar(1), 'USD');
  t.true(result.equals(Money.dollar(1)));
});

test('reduce money different currency', async (t) => {
  const bank = new Bank();
  bank.addRate('CHF', 'USD', 2);
  const result = bank.reduce(Money.dollar(1), 'USD');
  t.true(result.equals(Money.dollar(1)));
});

test('identity rate', async (t) => {
  t.is(new Bank().rate('USD', 'USD'), 1);
});

test('mixed addition', async (t) => {
  const fiveBucks = Money.dollar(5);
  const tenFrancs = Money.franc(10);
  const bank = new Bank();
  bank.addRate('CHF', 'USD', 2);
  const result = bank.reduce(fiveBucks.plus(tenFrancs), 'USD');
  t.true(result.equals(Money.dollar(10)));
});

test('sum plus money', async (t) => {
  const fiveBucks = Money.dollar(5);
  const tenFrancs = Money.franc(10);
  const bank = new Bank();
  bank.addRate('CHF', 'USD', 2);
  const sum = new Sum(fiveBucks, tenFrancs).plus(fiveBucks);
  const result = bank.reduce(sum, 'USD');
  t.true(result.equals(Money.dollar(15)));
});

test('sum times', async (t) => {
  const fiveBucks = Money.dollar(5);
  const tenFrancs = Money.franc(10);
  const bank = new Bank();
  bank.addRate('CHF', 'USD', 2);
  const sum = new Sum(fiveBucks, tenFrancs).times(2);
  const result = bank.reduce(sum, 'USD');
  t.true(result.equals(Money.dollar(20)));
});
```

最終形なので、変更のないMoneyとBankも再掲します。

```js
import Sum from './Sum';

export default class Money {
  constructor(amount, currency) {
    this.amount = amount;
    this.currency = currency;
  }

  equals(money) {
    return this.amount === money.amount
      && this.currency === money.currency;
  }

  times(multiplier) {
    return new Money(this.amount * multiplier, this.currency);
  }

  plus(addend) {
    return new Sum(this, addend);
  }

  reduce(bank, to) {
    const rate = bank.rate(this.currency, to);
    return new Money(this.amount / rate, to);
  }

  static dollar(amount) {
    return new Money(amount, 'USD');
  }

  static franc(amount) {
    return new Money(amount, 'CHF');
  }
}
```

```js
export default class Bank {
  constructor() {
    this.rates = {};
  }

  reduce(source, to) {
    return source.reduce(this, to);
  }

  addRate(from, to, rate) {
    this.rates[from + to] = rate;
  }

  rate(from, to) {
    if (from === to) return 1;
    return this.rates[from + to];
  }
}
```

```js
import Money from './Money';

export default class Sum {
  constructor(augend, addend) {
    this.augend = augend;
    this.addend = addend;
  }

  reduce(bank, to) {
    const amount = this.augend.reduce(bank, to).amount +
      this.addend.reduce(bank, to).amount;
    return new Money(amount, to);
  }

  plus(addend) {
    return new Sum(this, addend);
  }

  times(multiplier) {
    return new Sum(this.augend.times(multiplier), this.addend.times(multiplier));
  }
}
```

## 感想

Node + AVAだと手軽にテストが書けるので、『テスト駆動開発』の実習にはもってこいの環境でした。設計面で、Expressionインタフェースの扱いをどうするか迷いましたが、素のJavaScriptにインタフェースはないので、ダックタイピング的な設計でいくのがJavaScriptらしいかなと思いました。

元がJavaのコードなだけあり、最終形はクラス3つ、単体の関数はゼロという構成になっています。これはこれで良いですが、特定のオブジェクトに属さない関数が全然登場しないのは、JavaScriptっぽくないです。別の設計方針を考えてみるのも面白いかもしれません。