Flash Advent Calendar 2日目 - JavaScriptでのテスト駆動開発 -

テスト駆動で開発するぞ！！っと決意したのはいいのですが
何からやっていいのか分からない状態だったの、とりあえず思いついた事をやってみました。
開発後期なると、それとなく体系化されていたので、きっとやり方は十人十色なんだと思います。
こんな風にやるケースもあるのだなぁっとみてもらえればと思います。

# 目次
1. ディレクトリ構成、テストのファイル名を考える
2. まずは始める
3. テスト実装のルール

# ディレクトリ構成、テストのファイル名を考える

|ディレクトリ|用途|
|:-----------------|:------------------|
|src|実装のディレクトリ|
|test|テストのディレクトリ|

っという感じで分けて、後は中の構成を全く一緒にします。
テストのファイル名は実際の`ファイル名+Test`という感じで命名していきます。

```ディレクトリ構成

src/
├── flash/
│   ├── display/
│   │   ├── DisplayObject.js
│   │   ├── MovieClip.js
│   │   ├── Shape.js etc...
│   ├── text/
│   │   ├── StaticText.js
│   │   ├── TextField.js
├── parser/
│   ├── ByteStream.js
test/
├── flash/
│   ├── display/
│   │   ├── DisplayObjectTest.js
│   │   ├── MovieClipTest.js
│   │   ├── ShapeTest.js etc...
│   ├── text/
│   │   ├── StaticTextTest.js
│   │   ├── TextFieldTest.js
├── parser/
│   ├── ByteStreamTest.js
```

# まずは始める
何から始めたらいいか分からないので、思い付いたことから始めます。
まずは見たらわかる当たり前の事からでもいいので、論理的証明を行うんだ！！
という強い気持ちだけを持って進める事が大切かと思いますw


まずは簡単なクラスから

```src/flash/display/BlendMode.js

/**
 * @constructor
 * @public
 */
const BlendMode = function () {}; 

/**
 * static properties
 */
Object.defineProperties(BlendMode, {
    /**
     * @description 表示オブジェクトの要素カラーの値を背景色に加算し、その際に上限 0xFF を適用します。
     *              Adds the values of the constituent colors of the display object
     *              to the colors of its background, applying a ceiling of 0xFF.
     *
     * @memberof BlendMode#
     * @property string [ADD="add"] 定数/Constant
     * @const
     */
    ADD: {
        /**
         * @return {string}
         */
        get: function () {
            return "add";
        }
    },
    ...
});

```

コンソールで見ると`add`と出力されます。

```JavaScript
console.log(BlendMode.ADD);
```

これを証明していきます。

```test/flash/display/BlendMode.js
describe("BlendMode.js property test", function()
{
    it("ADD test", function () 
    {
        expect(BlendMode.ADD).toBe("add");
    });
}
```

これでテストを動かしてみます。

```linux
gulp test 

...

  BlendMode.js property test
    ✓ ADD test

...

TOTAL: 1 SUCCESS
```

はい！通過しました。
あたり前ですが・・・

すごく、小さいテストですが
これで`BlendMode.ADD`で固定値`add`を取得できる事が確約されました。
もし、誰かが誤ってこの値を変更してもテストでエラーとなるのでバグが発生する前に気づけます。

こういった、小さい証明の積み重ねがあることで、大きな仕様変更などにも思い切って挑戦できます。
また、バグがあった際にバグの改修ケースをテストに入れる事でデグレ防止にもなります。

## 仕様に合わせてテストを追加する

仕様などが決まってきたらそういった所をちょこちょこ追加していきます。

```src/flash/geom/Matrix.js
/**
 * @constructor
 * @public
 */
const Matrix = function (a = 1, b = 0, c = 0, d = 1, tx = 0, ty = 0) {};

省略... 
```


数値を管理すMatrixクラス

仕様：文字列でその文字が数値の場合は数値に変換して扱う


```src/flash/geom/MatrixTest.js
describe("Matrix.js property valid test and clone test", function()
{

    // 通常の想定される挙動
    it("数値を代入したら同じ数値が出力される", function()
    {
        var m = new Matrix();
        m.a   = 1.2;
        m.b   = 0.765;
        m.c   = -0.872;
        m.d   = -1.5;
        m.tx  = 10;
        m.ty  = -10;

        expect(m.a).toBe(1.2);
        expect(m.b).toBe(0.765);
        expect(m.c).toBe(-0.872);
        expect(m.d).toBe(-1.5);
        expect(m.tx).toBe(10);
        expect(m.ty).toBe(-10);
    });

    // クラス単体の特殊な仕様
    // エラーではなく、数値に変換する
    it("文字列を代入したら数値に変換されて出力される", function()
    {
        var m = new Matrix();
        m.a   = "1.2";
        m.b   = "0.765";
        m.c   = "-0.872";
        m.d   = "-1.5";
        m.tx  = "10";
        m.ty  = "-10";

        expect(m.a).toBe(1.2);
        expect(m.b).toBe(0.765);
        expect(m.c).toBe(-0.872);
        expect(m.d).toBe(-1.5);
        expect(m.tx).toBe(10);
        expect(m.ty).toBe(-10);
    });

});
```


## 関数単位のテスト

```src/flash/geom/Matrix.js
/**
 * @constructor
 * @public
 */
const Matrix = function (a = 1.0, b = 0, c = 0, d = 1.0, tx = 0, ty = 0) {};

/**
 * @description Matrix オブジェクトに回転変換を適用します。
 *              Applies a rotation transformation to the Matrix object.
 *
 * @param   {number} rotation
 * @return  {void}
 * @public
 */
Matrix.prototype.rotate = function (rotation)
{
    const a = this.a;
    const b = this.b;
    const c = this.c;
    const d = this.d;

    this.a = a * Math.cos(rotation) - b * Math.sin(rotation);
    this.b = a * Math.sin(rotation) + b * Math.cos(rotation);
    this.c = c * Math.cos(rotation) - d * Math.sin(rotation);
    this.d = c * Math.sin(rotation) + d * Math.cos(rotation);

    const tx = this.tx;
    const ty = this.ty;

    this.tx = tx * Math.cos(rotation) - ty * Math.sin(rotation);
    this.ty = tx * Math.sin(rotation) + ty * Math.cos(rotation);
};
```

## 関数で想定されるテストケースをかく
書きます。想定されるものがあれば重複してもいいので書きます。


```src/flash/geom/MatrixTest.js
describe("Matrix.js rotate test", function()
{
    it("rotate test1", function()
    {
        var m = new Matrix(1, 0, 0, 1, 100, 110);
        m.rotate(45 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=0.7071067811865476, b=0.7071067811865475, c=-0.7071067811865475, d=0.7071067811865476, tx=-7.071067811865461, ty=148.49242404917499)"
        );
    });

    it("rotate test2", function()
    {
        var m = new Matrix(1, 0, 0, -1, 100, 110);
        m.rotate(45 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=0.7071067811865476, b=0.7071067811865475, c=0.7071067811865475, d=-0.7071067811865476, tx=-7.071067811865461, ty=148.49242404917499)"
        );
    });

    it("rotate test3", function()
    {
        var m = new Matrix(-1, 0, 0, 1, 100, 110);
        m.rotate(45 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=-0.7071067811865476, b=-0.7071067811865475, c=-0.7071067811865475, d=0.7071067811865476, tx=-7.071067811865461, ty=148.49242404917499)"
        );
    });

    it("rotate test4", function()
    {
        var m = new Matrix(-1, 0, 0, -1, 100, 110);
        m.rotate(45 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=-0.7071067811865476, b=-0.7071067811865475, c=0.7071067811865475, d=-0.7071067811865476, tx=-7.071067811865461, ty=148.49242404917499)"
        );
    });

    it("rotate test5", function()
    {
        var m = new Matrix(1, 10, 10, 1, 100, 110);
        m.rotate(45 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=-6.363961030678927, b=7.778174593052023, c=6.363961030678928, d=7.7781745930520225, tx=-7.071067811865461, ty=148.49242404917499)"
        );
    });

    it("rotate test6", function()
    {
        var m = new Matrix(1, -10, 10, 1, 100, 110);
        m.rotate(45 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=7.7781745930520225, b=-6.363961030678928, c=6.363961030678928, d=7.7781745930520225, tx=-7.071067811865461, ty=148.49242404917499)"
        );
    });

    it("rotate test7", function()
    {
        var m = new Matrix(1, 10, -10, 1, 100, 110);
        m.rotate(45 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=-6.363961030678927, b=7.778174593052023, c=-7.778174593052023, d=-6.363961030678927, tx=-7.071067811865461, ty=148.49242404917499)"
        );
    });

    it("rotate test8", function()
    {
        var m = new Matrix(1, 10, 10, 1, -100, 110);
        m.rotate(45 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=-6.363961030678927, b=7.778174593052023, c=6.363961030678928, d=7.7781745930520225, tx=-148.49242404917499, ty=7.07106781186549)"
        );
    });

    it("rotate test9", function()
    {
        var m = new Matrix(1, 10, 10, 1, 100, -110);
        m.rotate(45 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=-6.363961030678927, b=7.778174593052023, c=6.363961030678928, d=7.7781745930520225, tx=148.49242404917499, ty=-7.07106781186549)"
        );
    });

    it("rotate test10", function()
    {
        var m = new Matrix(-1, -10, -10, -1, -100, -110);
        m.rotate(45 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=6.363961030678927, b=-7.778174593052023, c=-6.363961030678928, d=-7.7781745930520225, tx=7.071067811865461, ty=-148.49242404917499)"
        );
    });

    it("rotate test11", function()
    {
        var m = new Matrix(-1, -10, -10, -1, -100, -110);
        m.rotate(0.5);
        expect(m.toString()).toBe(
            "(a=3.9166728241516573, b=-9.255251157507931, c=-8.296400080299524, d=-5.671837947932403, tx=-35.02144694257494, ty=-144.47663566836133)"
        );
    });

    it("rotate test12", function()
    {
        var m = new Matrix(-1, -10, -10, -1, -100, -110);
        m.rotate(-0.5);
        expect(m.toString()).toBe(
            "(a=-5.671837947932403, b=-8.296400080299524, c=-9.255251157507931, d=3.9166728241516573, tx=-140.4950654354996, ty=-48.5915279475207)"
        );
    });

    it("rotate test13", function()
    {
        var m = new Matrix(-1, -10, -10, -1, -100, -110);
        m.rotate(90 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=10, b=-1.0000000000000007, c=0.9999999999999993, d=-10, tx=110, ty=-100)"
        );
    });

    it("rotate test14", function()
    {
        var m = new Matrix(-1, -10, -10, -1, -100, -110);
        m.rotate(135 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=7.778174593052023, b=6.363961030678927, c=7.7781745930520225, d=-6.363961030678928, tx=148.49242404917499, ty=7.071067811865461)"
        );
    });

    it("rotate test15", function()
    {
        var m = new Matrix(-1, -10, -10, -1, -100, -110);
        m.rotate(Math.PI);
        expect(m.toString()).toBe(
            "(a=1.0000000000000013, b=10, c=10, d=0.9999999999999988, tx=100.00000000000001, ty=109.99999999999997)"
        );
    });

    it("rotate test16", function()
    {
        var m = new Matrix(-1, -10, -10, -1, -100, -110);
        m.rotate(-45 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=-7.7781745930520225, b=-6.363961030678928, c=-7.778174593052023, d=6.363961030678927, tx=-148.49242404917499, ty=-7.07106781186549)"
        );
    });

    it("rotate test17", function()
    {
        var m = new Matrix(-1, -10, -10, -1, -100, -110);
        m.rotate(-90 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=-10, b=0.9999999999999993, c=-1.0000000000000007, d=10, tx=-110, ty=100)"
        );
    });

    it("rotate test18", function()
    {
        var m = new Matrix(-1, -10, -10, -1, -100, -110);
        m.rotate(-135 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=-6.363961030678928, b=7.7781745930520225, c=6.363961030678927, d=7.778174593052023, tx=-7.07106781186549, ty=148.49242404917499)"
        );
    });

    it("rotate test19", function()
    {
        var m = new Matrix(-1, -10, -10, -1, -100, -110);
        m.rotate(-1 * Math.PI);
        expect(m.toString()).toBe(
            "(a=0.9999999999999988, b=10, c=10, d=1.0000000000000013, tx=99.99999999999999, ty=110.00000000000003)"
        );
    });

    it("rotate test20", function()
    {
        var m = new Matrix(-1, -10, -10, -1, -100, -110);
        m.rotate("a");
        expect(m.toString()).toBe(
            "(a=NaN, b=NaN, c=NaN, d=NaN, tx=NaN, ty=NaN)"
        );
    });

    it("rotate test21", function()
    {
        var m = new Matrix(-1, -10, -10, -1, -100, -110);
        m.a = "a";
        m.rotate(45 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=NaN, b=NaN, c=-6.363961030678928, d=-7.7781745930520225, tx=7.071067811865461, ty=-148.49242404917499)"
        );
    });

    it("rotate test22", function()
    {
        var m = new Matrix(-1, -10, -10, -1, -100, -110);
        m.c = "a";
        m.rotate(45 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=6.363961030678927, b=-7.778174593052023, c=NaN, d=NaN, tx=7.071067811865461, ty=-148.49242404917499)"
        );
    });

    it("rotate test23", function()
    {
        var m = new Matrix(-1, -10, -10, -1, -100, -110);
        m.tx = "a";
        m.rotate(45 / 180 * Math.PI);
        expect(m.toString()).toBe(
            "(a=6.363961030678927, b=-7.778174593052023, c=-6.363961030678928, d=-7.7781745930520225, tx=NaN, ty=NaN)"
        );
    });
});
```

この辺りになってくると、命名とかすごく雑になってきます。
が、テストする事が目的なので気にしません！！

そして、テストは誰が書いてもいいんです。
テストをして困る人はいません。


# テスト実装のルール

* `src`にファイルを追加したら必ず`test`にもテストファイルも作る
* どれだけ汚くても要件が満たせていればOK！！
* 仕様変更などでテスト結果が変わらない限りは後から修正しない、基本は追加オンリー

色々とご意見があると思いますが
テスト書かないとなぁ・・・っと思ってる方の参考になればと

明日は自分の苦手なバイナリ入門を書こうと思います。