git-flowを利用したAnsibleロールのテスト駆動開発の流れ

以前、[AnsibleロールのユニットテストからTravis CIまで](http://qiita.com/tumf/items/ac055b0dbe9c3398a1dc)という記事を書きました。
この方法にgit-flowを加えた**テスト駆動開発(TDD)**がいい感じに出来るようになったのでまとめます。

新規`Ansible`ロールの作り方からTDDしながら機能追加していく流れを、
[docker-machine](https://github.com/docker/machine)のインストールを例に説明します。

Ansibleロール開発プロジェクトを始める
---------

ロールを開発するgitリポジトリ名を`ansible-role-docker-machine`とします。
このような名前にする理由は、将来`Ansible Galaxy`に登録される時に`ansible-role-`部分は無視されるからです。(`Ansible Galaxy`ではロール名が`ユーザー名.docker-machine`となります)

### ロールの雛形ファイルを生成する

以下のコマンドでロールの雛形を自動生成します。

```
ansible-galaxy init ansible-role-docker-machine
```

`git`リポジトリを作ってコミットします


```
cd ansible-role-docker-machine
git init
git add .
git commit -a -m 'Generated by `ansible-galaxy init ansible-role-docker-machine`'
```


[AnsibleロールのユニットテストからTravis CIまで](http://qiita.com/tumf/items/ac055b0dbe9c3398a1dc)に書いた
[セットアップ手順を自動化するスクリプト](https://github.com/tumf/ansible-unit-test)でユニットテストをインストールします。

```
curl -sL https://raw.githubusercontent.com/tumf/ansible-unit-test/master/ansible-role-test-init |bash
git add .
git commit -a -m 'Generated by `ansible-role-test-init`'
```


以上で雛形ファイルの準備は完了です。


### `docker-machine`のインストールを実装する

`docker-machine`は[公式のドキュメント](https://github.com/docker/machine/releases)にある通り、以下のコマンドでインストールできます。


```bash
$ curl -L https://github.com/docker/machine/releases/download/v0.5.5/docker-machine_linux-amd64 >/usr/local/bin/docker-machine && \
  chmod +x /usr/local/bin/docker-machine
```

つまり、`https://github.com/docker/machine/releases/download/v0.5.5/docker-machine_linux-amd64`から`/usr/local/bin/docker-machine`をダウンロードして実行権限をつける...ということが当面の目的(Spec)となります。

テストを**テストファーストで(!!重要!!)**書きます。ここではテストケース名を単に`install`とします。
テストケースは以下のようにして`tests/cases/install`以下に作成します。

```bash
mkdir -p tests/cases/install/usr/local/bin
curl -L https://github.com/docker/machine/releases/download/v0.5.5/docker-machine_linux-amd64 >tests/cases/install/usr/local/bin/docker-machine
chmod +x tests/cases/install/usr/local/bin/docker-machine
```

またこのテストケースを実現するためのプレイブックを`tests/test.yml`に記述します。(雛形のままです)

```yaml:tests/test.yml
---
- hosts: 127.0.0.1
  connection: local
  tags:
    - install
  roles:
    - role: ../..
  tasks:
    - debug: msg="The test case is succeeded."
```

これでテストの準備完了です。早速テストを実行してみます。


```bash
    $ ./tests/run
    * Test: install

    PLAY [127.0.0.1] **************************************************************

    GATHERING FACTS ***************************************************************
    ok: [127.0.0.1]

    TASK: [debug msg="The test case is succeeded."] *******************************
    ok: [127.0.0.1] => {
        "msg": "The test case is succeeded."
    }

    PLAY RECAP ********************************************************************
    127.0.0.1                  : ok=2    changed=0    unreachable=0    failed=0

    Binary files tests/cases/install/usr/local/bin/docker-machine and tests/outputs/install/usr/local/bin/docker-machine differ
    TestCase: install ERROR
    1 error(s)
```

テストファーストなので期待通り失敗しました。これを成功させることが当面の目標となります。


### ロールを実装する

ロールを実装します。`tasks/main.yml`にタスクを記述します。

```yaml:task/main.yml
- name: Create Install Directory
  file: path="{{ ansible_unit_test_prefix_dir }}/usr/local/bin" state=directory recurse=yes
- name: Install Docker Machine
  get_url: >
    url="https://github.com/docker/machine/releases/download/v0.5.5/docker-machine_linux-amd64"
    dest="{{ ansible_unit_test_prefix_dir }}/usr/local/bin/docker-machine"
    mode="755"
```

簡単に上記のようにしました、ポイントは出力先をテスト先に向けるための`ansible_unit_test_prefix_dir`の記述です。

テストすると以下のように無事テストがパスするようになりました。(DockerMachineをダウンロードするため時間がかかります)

```bash
    -> % ./tests/run
    * Test: install

    PLAY [127.0.0.1] **************************************************************

    GATHERING FACTS ***************************************************************
    ok: [127.0.0.1]

    TASK: [../.. | Create Install Directory] **************************************
    changed: [127.0.0.1]

    TASK: [../.. | Install Docker Machine] ****************************************
    changed: [127.0.0.1]

    TASK: [debug msg="The test case is succeeded."] *******************************
    ok: [127.0.0.1] => {
        "msg": "The test case is succeeded."
    }

    PLAY RECAP ********************************************************************
    127.0.0.1                  : ok=4    changed=2    unreachable=0    failed=0

    TestCase: install OK
    1 test case(s) passed
```

ここまで一度gitにコミットしておきましょう

### git-flowでfeatureを追加

粗くではありますが動くようになりましたので、git-flowでfeatureを追加していきます。

まず、`git-flow`を全てデフォルト設定で初期化します

```bash
git flow init -d
```

さて、やりたいことは沢山ありますが、まずはTDDを快適に回せるようにテスト時に`DockerMachine`を毎回ダウンロードするのを抑制したいと思います。

実現方法としてダウンロードURLを`docker_machine_url`のような変数として、
テスト時はこれを上書きするようにします。

まず、`git-flow`でfeatureを開始します

```
git flow feature start add_docker_machine_url
```

ブランチが`features/add_docker_machine_url`に切り替わります。
次にtaskに直書きしているURLを変数に書き換えます。

```yaml:task/main.yml
- name: Install Docker Machine
  get_url: >
    url="{{ docker_machine_url }}"
    dest="{{ ansible_unit_test_prefix_dir }}/usr/local/bin/docker-machine"
    mode="755"
```

デフォルト値として以下を追加します。

```yaml:defaults/main.yml
docker_machine_url: https://github.com/docker/machine/releases/download/v0.5.5/docker-machine_linux-amd64
```

テスト時はどこかもっと軽い物をダミーとして落とすことにします。
どこでもいいのですが私は[Gist](https://gist.github.com/)を利用しました。
これをテスト側でセットします

```yaml:tests/test.yml
- hosts: 127.0.0.1
  connection: local
  tags:
    - install
  roles:
    - role: ../..
    docker_machine_url: https://gist.githubusercontent.com/tumf/579c84e276b6d2d88e09/raw
  tasks:
    - debug: msg="The test case is succeeded."
```

これでテスト時のみ軽い`Gist`からのダミーファイルダウンロードに切り替わるはずです。
早速テストしてみます。

    -> % ./tests/run
    * Test: install

    PLAY [127.0.0.1] **************************************************************

    GATHERING FACTS ***************************************************************
    ok: [127.0.0.1]

    TASK: [../.. | Create Install Directory] **************************************
    changed: [127.0.0.1]

    TASK: [../.. | Install Docker Machine] ****************************************
    changed: [127.0.0.1]

    TASK: [debug msg="The test case is succeeded."] *******************************
    ok: [127.0.0.1] => {
        "msg": "The test case is succeeded."
    }

    PLAY RECAP ********************************************************************
    127.0.0.1                  : ok=4    changed=2    unreachable=0    failed=0

    Binary files tests/cases/install/usr/local/bin/docker-machine and tests/outputs/install/usr/local/bin/docker-machine differ
    TestCase: install ERROR
    1 error(s)

上記の用にテストは失敗します。最初にテストケース`install`を作った時においた本物のバイナリとGistのダミーファイルが違うと言われています。
この結果は正しいので、テストケースの方を修正します。

このような場合、テストケースの修正は以下のコマンドで上書きできます。


```
-> % ./tests/run -w install
```

テストが正しく通るようになりました。

    -> % ./tests/run
    * Test: install

    PLAY [127.0.0.1] **************************************************************

    GATHERING FACTS ***************************************************************
    ok: [127.0.0.1]

    TASK: [../.. | Create Install Directory] **************************************
    changed: [127.0.0.1]

    TASK: [../.. | Install Docker Machine] ****************************************
    changed: [127.0.0.1]

    TASK: [debug msg="The test case is succeeded."] *******************************
    ok: [127.0.0.1] => {
        "msg": "The test case is succeeded."
    }

    PLAY RECAP ********************************************************************
    127.0.0.1                  : ok=4    changed=2    unreachable=0    failed=0

    TestCase: install OK
    1 test case(s) passed

上手く行ったようなので、コミットして`add_docker_machine_url`のfeatureを終了します。

```
git commit -a -m 'Impliments add_docker_machine_url feature'
git flow feature finish add_docker_machine_url 
```

これで、`develop`ブランチに戻りました。featureの完了ですお疲れ様でした！

以上のように、ローカルマシン上で手軽にテストを繰り返すことにより安心して`Ansible`ロールに機能追加することができるようになりました。

`Ansible`のユニットテストは従来の`Vagrant`等を利用したテストに変わるものではなく、
あくまでも、開発中に不具合が紛れ込む可能性を防ぎ開発効率を上げる1手です。

今回作った[UnitTesting Framework for Ansible roles](https://github.com/tumf/ansible-unit-test)はリンク先に公開していますので不具合やご要望などありましたらPRやIssueをお気軽に！