【Vue.js】いつから「フロントエンド開発でTDDができない」と錯覚していた？

# Vue.jsのコンポーネント開発をTDDでやってみる

※ TDD (test-driven development): テスト駆動開発
※ テスト駆動開発は文化です。チームの「状況」「納期」「スキルレベル」、その他いろんな要因が絡んできた結果、そのチームが導入するかどうか決めたらいいと思います。
※ 例えがいいかはわかりませんが、私は「早起き」と「テスト」は同じようなものだと思っています。「早起き」は健康にいいよねって誰でも言うと思うけど、実際に万人がやっているかどうかは別じゃないですか。それと同じで「テストすること」も絶対いいことだと私は思っていますが、やるかどうかはチームの置かれている状況によって決まります。この記事は、その「テストを導入するかどうか」という意思決定の一助にでもなれればいいなと思います。

## はじめに

こんにちは。ぼくです。

今回はVue.jsでTODOアプリを作ってみようと思います。その時の条件は

- TDDで開発して、
- ブラウザは最後まで見ない

です。普通にチュートリアルな感じなので、ざっくりコード眺めていけるかと思いますし、スクショ多めでいくので、自分でやらなくてもなんとなく追体験ができるようにしています。よかったら自分でもやってみてください。

またテストが分かるかたは、このTodoアプリを参考に、ハンズオンとか開いてくれたりするとうれしかったりします。Vue-meetupでもテストを書いている割合が圧倒的に少なかったので、テストユーザー増やしていきましょう〜〜

## コンポーネント作成

### 目標物の確認

今回作成するものは[こちら](https://jp.vuejs.org/v2/examples/todomvc.html)の TODOアプリ です。

いろんなフレームワークでTODOアプリのサンプルは[こちら](http://todomvc.com/)をみるとたくさん載っていて、

- Backbone.js
- AngularJS
- Ember.js
- KnockoutJS
- ( etc )

などたくさんサンプルがあるので、フレームワーク選定の時にお使いください。

### 目標物の設計を分解して考える

責務の分解をします。

<img width="643" alt="スクリーンショット 2018-06-24 0.21.35のコピー.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/5e6cfd2f-c99d-35aa-b640-943fa8db20b4.png">

今回は、コンポーネント構成を以下のようにする。

```
- TodoContainer.vue
    - NewTodo.vue
    - TodoItem.vue
```

- TodoContainer.vue
    - todoのリストを管理するコンポーネント
- NewTodo.vue
    - 新しいTodoリストのテキストを親のコンポーネントにemitする
- TodoItem.vue
    - 親のコンポーネントからtodoリストのpropsをもらってレンダリングする

### 環境構築

```shell-session
$ vue -v
3
$ vue create vue-todo
1. manually select features
2. ---
◉ Babel
◉ Unit Testing
3. Jest
4. In package.json(どっちでもいい)
5. N
6. yarn
```

もしくは

```shell-session
$ git clone git@github.com:ykhirao/vue-todos.git
$ cd vue-todos
$ git checkout 9b2b09d5081 -b practice
```

ではじめのコミットを`checkout`してやってください。

### 入力から作ってみる NewTodo.vue

ここのインプットフォームから作っていきたいと思いますが、このコンポーネントは

- v-modelでinputのデータを管理する
- エンターが押されると親コンポーネントにそのテキストをemitする

という責務にしましょう。

<img width="648" alt="スクリーンショット 2018-06-24 0.51.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/ec851d96-c1db-033b-e89c-5c615772b515.png">

```shell-session
$ touch src/components/NewTodo.vue
$ touch tests/unit/NewTodo.spec.js
```

1. 期待する結果をexpectで書く
  - 今回はインプットタグがレンダーされているかだけを検証する
  - 基本的にコンポーネントは `wrapper` という変数にshallowMountでラップする 
  - イコール文: expect(1).toBe(1)
  - これが基本の形なので覚えてください。

shallowMount のAPIは[こちら](https://vue-test-utils.vuejs.org/ja/api/shallowMount.html)

他にも `mount` というものでラップできるけど、弊社だと基本的には早いから `shallowMount` を使います。 またstubを作りまくれば`shallowMount`だけでもかけますが、 `mount`を使ったほうが楽になるパターンもあったり結構深いテーマなので、両者の使い分けは後日Qiitaに書きます！！！

#### テスト1 単純なレンダリングテストを書いて、適切なhtml構造を作る

```NewTodo.spec.js
import { shallowMount } from '@vue/test-utils'
import NewTodo from "@/components/NewTodo.vue"

describe("NewTodo.vue", () => {
  it("インプットタグをレンダリングする", () => {
    const wrapper = shallowMount(NewTodo)

    expect(wrapper.find("input.new").exists()).toBe(true)
  })
})
```

テストコードを簡単に説明すると `newってclassがついたinputタグが存在する？` ってテストです。

- `find = document.querySelector` みたいなものです。 [find api](https://vue-test-utils.vuejs.org/ja/api/#%E3%82%BB%E3%83%AC%E3%82%AF%E3%82%BF)
- [exists api](https://vue-test-utils.vuejs.org/ja/api/wrapper/exists.html) 存在するか
- toBe(true) 判定式


<img width="1677" alt="スクリーンショット 2018-06-24 1.13.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/cb75e13f-3e08-3c0b-214f-91f03e9f21bd.png">

エラー文はこちら。

> [Vue warn]: Failed to mount component: template or render function not defined.

そもそもテンプレートがないみたいなので作りにいきましょう。

```src/components/NewTodo.vue
<template>
  <div>test</div>
</template>
```

次はマウントコンポーネントのエラーが無くなり単純な失敗みたいです。

<img width="513" alt="スクリーンショット 2018-06-24 1.16.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/4caa3b45-0631-9d04-9f13-ea7fec49b6ae.png">

次は `<input>` を作ってみましょう。

```src/components/NewTodo.vue
<template>
  <div>
    <input class="new">
  </div>
</template>
```

<img width="401" alt="スクリーンショット 2018-06-24 1.18.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/7929ead7-90cf-32af-212a-93d38254bd8c.png">

パスしました！！！！

#### テスト2 clickイベントを拾い、新しいタグをemitする

とりあえずクリックイベントを発火させて、emitも検知できればいいかなと思います。

この段階で作り始めたときはしっかり設計できてなかった、以下のことを再定義します

- .vueファイルのdataに { text: "text" } というオブジェクトを持たせる
- それを `createTodo`という名前でemitします。

```NewTodo.spec.js
it("新しいタグをemitする", () => {
  const text = "text"
  const wrapper = shallowMount(NewTodo)

  wrapper.setData({text})
  wrapper.find(".new").trigger("keyup.enter")

  expect(wrapper.emitted("createTodo")).toBe(text)
})
```

今回は、 `shallowMount` でコンポーネントをラップして、 `find` でinputタグを見つけ出し、 `trigger` でイベントを発火させて、本当にemitするかどうかというテストになります。

```shell-session
$ yarn test --watch
```

うーんテストが失敗しています。

<img width="654" alt="スクリーンショット 2018-06-25 0.07.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/af17d1fe-8cab-239c-5152-6df61e891df1.png">

たぶん`createTodo`がemitされていませんね！作りにいきましょう。

```src/components/NewTodo.vue
<template>
  <div>
    <input
      class="new"
      @keyup.enter="submit"
    >
  </div>
</template>

<script>
export default {
   methods: {
    submit() {
      this.$emit("createTodo", "ここにsubmitされるテキストが入るよ！")
    }
  } 
}
</script>
```


こんな感じで `input` に `keyup.enter` エンターイベントが発火したときに `submit` というメソッドが呼ばれるように修正しました。このときのテストを以下のように修正して `wrapper.emitted("createTodo")` がどういう値になるか見てみましょう。

```NewTodo.spec.js
it("新しいタグをemitする", () => {
  const text = "text"
  const wrapper = shallowMount(NewTodo)

  wrapper.setData({text})
  wrapper.find(".new").trigger("keyup.enter")

  console.log(wrapper.emitted("createTodo"))
  expect(wrapper.emitted("createTodo")).toBe(text)
})
```

```shell-session
console.log tests/unit/NewTodo.spec.js:18
  [ [ 'ここにsubmitれるテキストが入るよ！' ] ]
```

これで `wrapper.emitted("createTodo")[0][0]` に emitされるテキストが入ることがわかりました。のでテストを修正します。

```NewTodo.spec.js
it("新しいタグをemitする", () => {
  const text = "text"
  const wrapper = shallowMount(NewTodo)

  wrapper.setData({text})
  wrapper.find(".new").trigger("keyup.enter")

  expect(wrapper.emitted("createTodo")[0][0]).toBe(text)
})
```

また２つのエラーがでてきました。

<img width="991" alt="スクリーンショット 2018-06-25 0.17.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/2a807f4d-cc03-7912-3b49-6f4fa7cb2c5c.png">

2つのエラーは

- 単純にexpect().toBe() が失敗していますが、これは後に解決させます。
- もう一つは `wrapper.setData({text})` というところでコンポーネントにdataを定義していないので怒られていると思われます。作りにいきましょう。 [setData api](https://vue-test-utils.vuejs.org/ja/api/wrapper-array/#setdata-data)

```src/components/NewTodo.vue
<template>
  <div>
    <input
      class="new"
      v-model="text" // 1. ここ追加！！
      @keyup.enter="submit"
    >
  </div>
</template>

<script>
export default {
  data() { // 2. ここ追加！！
    return {
      text: ""
    }
  },
   methods: {
    submit() {
      this.$emit("createTodo", this.text) // 3. ここ追加！！
    }
  } 
}
</script>
```

修正箇所は3箇所です。

1. v-modelに dataのtextをいれた
2. .vueファイルにdata関数をもたせた
3. emitの第二引数に textのデータをもたせた。

以上！！

<img width="379" alt="スクリーンショット 2018-06-25 0.24.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/8574edd2-8ca8-0571-a05d-d597278989cb.png">

テストがパスしました〜！！！！！

```shell-session
$ yarn test --watch
```

こちらはgitで差分があるファイルだけホッとリロードでテストしてくれるので、完成したこのコンポーネントはもうコミットしておきます。


### コンテイナーを作る

再度こちらの図になります。今回は `TodoContainer.vue` を作っていきます。

<img width="643" alt="スクリーンショット 2018-06-24 0.21.35のコピー.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/5e6cfd2f-c99d-35aa-b640-943fa8db20b4.png">

まずはテストから。

```shell-session
$ touch tests/unit/TodoContainer.spec.js
$ touch src/components/TodoContainer.vue
```

よし！！次のテストは `TodoContainer.vue` をレンダーしたら `NewTodo.vue` もレンダーされるかチェックしようではないか。

```tests/unit/TodoContainer.spec.js
import { shallowMount } from '@vue/test-utils'
import TodoContainer from "@/components/TodoContainer.vue"
import NewTodo from "@/components/NewTodo.vue"

describe("TodoContainer.vue", () => {
  it("formをレンダー", () => {
    const wrapper = shallowMount(TodoContainer)

    expect(wrapper.find(NewTodo).exists()).toBe(true)
  })
})
```

ポイントは `find(NewTodo)` でVueコンポーネントを簡単に探せるところです。

<img width="616" alt="スクリーンショット 2018-06-25 0.40.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/6cf30922-ecf4-7375-d383-2687ff29500c.png">

もちろん失敗します〜〜のでエラーを見て直して行きましょう。先程みたやつですね！！テンプレートないよってやつです。

```src/components/TodoContainer.vue
<template>
</template>
```

ん？なんかこれだけでもテストがパスしちゃいました。。。とりあえず進めると

```src/components/TodoContainer.vue
<template>
  <NewTodo></NewTodo>
</template>
```

`コンポーネント登録しろよ〜〜` って感じで怒られました。

<img width="662" alt="スクリーンショット 2018-06-25 0.47.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/d3720672-6403-8a8b-c6cc-d2cd05022b95.png">

コンポーネントをインポートして登録します。

```src/components/TodoContainer.vue
<template>
  <NewTodo></NewTodo>
</template>
<script>
import NewTodo from "@/components/NewTodo.vue"
export default {
  components: {
    NewTodo
  },
}
</script>

```

はい完了。

`For recursive components, make sure to provide the "name" option.`が気になったので、ちょっと `NewTodo.vue`に戻って

```NewTodo.vue
<script>
export default {
  name: "new-todo", // こちら追加
  data() {
    return {
      text: ""
    }
  },
```

に戻って`nameオプションつけろ` って感じで怒られましたところを追加しました。

そうすると

```tests/unit/TodoContainer.spec.js
expect(wrapper.find(NewTodo).exists()).toBe(true)
```

がきちんと働くようになってコンポーネントを見つけ出してくれるみたいです。 nameオプションは必ずつけましょう。またnameオプションは `ケバブ-ケース` にすることとVueがいい感じに解釈してくれます。 html5では要素の大文字と小文字区別しないから `<NewTodo>` と <newtodo> とかが同じと解釈されるので、そのあたりのことを丸めるための処理。（かな）

次は子コンポーネントからemitされた値を [ todos ]というデータに追加するところを作ろうかと思います。テストでまた新しい概念が追加されるので頑張って覚えてください。むしろ覚えようとせずに「ふ〜〜んそんなものあるんだ〜〜」くらいがいいかもです。

今回やること

- `createTodo` としてemitされた`text` を`addTodo` というメソッドで追加する

```tests/unit/TodoContainer.spec.js
import TodoContainer from "@/components/TodoContainer.vue"

it("todoを追加する", () => {
  const localThis = {
    todos: []
  }

  TodoContainer.methods.addTodo.call(localThis, "text")

  expect(localThis.todos[0]).toEqual({
    id: 1,
    text: "text",
    checked: false
  })
})
```

今回は `localThis` という概念がでてきます。後 `call` めっちゃ重要な概念だから ~~調べて~~ 簡単に書きます。

`import TodoContainer` インポートしたコンポーネントは `TodoContainer.methods.addTodo` という形でコンポーネントメソッドを使うことができます。

ただこのときコンポーネントメソッドの `this.todos` が呼び出す文脈によって変わります。これを解消するのが `call` です。それでは見ていきましょう。


<img width="500" alt="スクリーンショット 2018-06-25 1.13.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/7720827b-1e8f-9414-0240-34dfaff95ce9.png">

```chrome's.console.js
function getText () {
  return(this.text)
}
const localThis = { text: "localThisのテスト"}

getText()

getText.call(localThis)
```

`call` に引数を渡すことで擬似的に `this` を書き換えことができます。

ちなみに

```chrome's.console.js
function getText () {
  console.log(this)
}
const localThis = { text: "localThisのテスト"}

getText()

getText.call(localThis)
```

chromeの開発者ツールでの `this` は `window` になります。今回は `spec.js` ではchromeは使わないのでまた別の `this` になることでしょう。node?

<img width="525" alt="スクリーンショット 2018-06-25 1.16.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/9ac05ff3-8184-acfd-2b17-57b335736b1b.png">

テストに戻ります

```
expect(localThis.todos[0]).toEqual({})
```

オブジェクトの検証は`toEqual`を使います。

- `toBe ===`
- `toEqual ==` 

みたいな感じです。

<img width="553" alt="スクリーンショット 2018-06-25 1.23.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/e0cd8af6-87e2-03ca-969b-40ef43b01542.png">

とりあえずなんかエラーでているので

- dataに todosを作る
- methodsにaddTodoを作る

```src/components/TodoContainer.vue
<template>
  <NewTodo
    @createTodo="addTodo" // 追加
  ></NewTodo>
</template>
<script>
import NewTodo from "@/components/NewTodo.vue"

export default {
  name: "todo-container",
  components: {
    NewTodo
  },
  data() {
    return {
      todos: [] // 追加
    }
  },
  methods: {
    addTodo(text) { // 追加
      this.todos.push({text, checked:false, id: this.todos.length + 1 })
    },
  }
}
</script>

```

addTodoについて

- Vueのオブサーバー壊さないために`push` で追加しています。
- 暫定的に`id` は配列の数で適当にやってます。
- またcheckedは最初はtodoは完了していないはずなので `false` からスタートさせています

とりあえずコンテナーは一旦作れたので次は子コンポーネントに移ります。

### todolistを作る

ここを作ります〜〜

`todos` から一つだけ `todo` をもらって

- テキストをレンダーする
- チェックボックスレンダーする
- チェックボックスにクリックイベント持たせて、親コンポーネントのメソッドをemitして、`Todo`から完了と非完了を入れ替える

<img width="648" alt="スクリーンショット 2018-06-24 0.51.29のコピー2.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/fc2098ac-0083-2a0c-abf9-15f44be87513.png">


```shell-session

$ touch src/components/TodoItem.vue
$ touch tests/unit/TodoItem.spec.js
```

テスト書きましょう。

```tests/unit/TodoItem.spec.js
import { shallowMount } from '@vue/test-utils'
import TodoItem from "@/components/TodoItem.vue"

describe("todo-item.vue", () => {
  const id = 1
  it("renders text", () => {
    const text = "最初の文字"
    const wrapper = shallowMount(TodoItem, {
      propsData: {
        id,
        text,
        checked: true
      }
    })

    expect(wrapper.find("span").text()).toBe(text)

    wrapper.setProps({ text: "二番目の文字" })
    expect(wrapper.find("span").text()).toBe("二番目の文字")
  })
})
```

ここで出てくる新しい概念は `shallowMount` の第二引数です。 `propsData` という場所に `todo`一つだけわたします。こうすることで todoitemに `todo` の中身が渡っていきます。`テンプレートがないよ` ってエラーが出てきたので作ります。


```src/components/TodoItem.vue
<template>
  <span>{{text}}</span>
</template>

<script>
export default {
  name: "todo-item",
  props: {
    text: {
      type: String,
      required: true
    }
  }
}
</script>

```

いい感じ。テストパスしました。テスト追加する〜

```tests/unit/TodoItem.spec.js
  it("renders radio", () => {
    const text = "text1"
    const wrapper = shallowMount(TodoItem, {
      propsData: {
        id,
        text,
        checked: true
      }
    })

    expect(wrapper.find(".radio:checked").exists()).toBe(true)
  })
```

propsとして `checked` が渡ってきたらラジオボタンがちゃんとレンダリングされて、しかもcheckedがつくかチェックします。

<img width="579" alt="スクリーンショット 2018-06-25 1.54.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/84ac3f49-5172-5f0d-0322-6f8a74090f19.png">

まあ存在しないって怒られました。

```src/components/TodoItem.vue
<template>
  <input
    type="radio"
    class="radio"
  >
  <span>{{text}}</span>
</template>
```

> Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.

<img width="655" alt="スクリーンショット 2018-06-25 1.55.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/843e20cc-d0be-bd77-32c5-471c64f994fe.png">

コンポーネントは<root> 要素、つまり`<div>` で囲ってあげればよさそうね！

```src/components/TodoItem.vue
<template>
<div>
  <input
    type="radio"
    class="radio"
    :checked="checked" //追加
  >
  <span>{{text}}</span>
</div>
</template>

<script>
export default {
  name: "todo-item",
  props: {
    text: {
      type: String,
      required: true
    },
    checked: { //追加
      type: Boolean,
      required: true
    }
  }
}
</script>

```

これでテストがパスするはずです。

ポイントは

- propsとして `checked` を受け取るところ
- `:checked="checked"` でcheckedの値をhtmlにバインドしてあげるところ

です。！

```tests/unit/TodoItem.spec.js
    const wrapper = shallowMount(TodoItem, {
      propsData: {
        id,
        text,
        checked: false // 追加
      }
    })
```

自分はcheckedがfalseのときも一応チェックしていましたがそれはお好みにまかせます。


次は

- ラジオボタンをクリックしたら親コンポーネントにemitする
- 親コンポーネントのtodosが書き換わったらpropsとして渡されて自動にチェックボックスのチェックも書き換わる

ということで親コンポーネントにemitするところまで書きましょう。

```tests/unit/TodoItem.spec.js
  const id = 1

  it("emit event when I click radio", () => {
    const text = "text1"
    const wrapper = shallowMount(TodoItem, {
      propsData: {
        id,
        text,
        checked: false
      }
    })

    wrapper.find(".radio").trigger("click")

    expect(wrapper.emitted("toggleChecked")[0][0]).toBe(id)
  })
```

既存の知識だけでもうこのテストは理解できると思います。ラジオボタンのクリックイベントでテキストをemitするメソッドを発火させます。渡すのは`todo`の`id`にしましょう。

```src/components/TodoItem.vue
<template>
<div>
  <input
    type="radio"
    class="radio"
    :checked="checked"
    @click="toggleChecked"
  >
  <span>{{text}}</span>
</div>
</template>

<script>
export default {
  name: "todo-item",
  props: {
    text: {
      type: String,
      required: true
    },
    checked: {
      type: Boolean,
      required: true
    },
    id: {
      type: Number,
      required: true
    }
  },
  methods: {
    toggleChecked() {
      this.$emit("toggleChecked", this.id)
    }
  }
}
</script>

```

完成形。

### 親コンテナーに戻る

最初の図間違っていたので修正しました。

<img width="643" alt="スクリーンショット 2018-06-24 0.21.35のコピー3.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/8e0bd242-fa33-0b19-6c3e-a2fde14f99d4.png">

こんな感じでいきましょう。

```tests/unit/TodoContainer.spec.js
  it("toggleChecked", () => {
    const localThis = {
      todos: [
        {
          id: 1,
          text: "text",
          checked: true
        }
      ]
    }

    TodoContainer.methods.toggleChecked.call(localThis, 1)

    expect(localThis.todos[0].checked).toBe(false)
  })
```

`todos`からidでfindして、チェックをいれたり外したりする関数を作ります。

```src/components/TodoContainer.vue
    toggleChecked(id) {
      const todo = this.todos.find(x => x.id === id)
      todo.checked = !todo.checked
    }
```

メソッドに追加したらpassしました。

```src/components/TodoContainer.vue
<template>
<div>
  <NewTodo
    @createTodo="addTodo"
  ></NewTodo>
  <TodoItem
    v-for="todo in todos"
    :id="todo.id"
    :key="todo.id"
    :text="todo.text"
    :checked="todo.checked"
    @toggleChecked="toggleChecked"
  ></TodoItem>
</div>
</template>

```

### 結合する、ブラウザテスト

最終的に少し修正しました。コンポーネントに登録してなくてエラーでたのが2箇所。

<img width="917" alt="スクリーンショット 2018-06-25 3.33.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/6b348607-eda3-42f9-e331-1a50b04d6166.png">

これで最終的にブラウザは1度しか見ずに簡単なTODOアプリが作れました！

<img width="647" alt="スクリーンショット 2018-06-25 3.31.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/112929/7955b362-072d-e9f9-52ad-3075da61e4c6.png">


### 作ってみた結論

結論として言いたいのは

- 簡単な内部ロジックで構成されているコンポーネント開発だと簡単にTDDできるよ
- 私の経験だと3ヶ月くらいずっとテスト書き続けていたら、TDDできるようになると思う
- StorybookでHot-reloadで開発してもいいと思うけどな！
- これは得意な人がつきっきりで、チュートリアルな形ならやれるものであって、決してVue-test-utils初心者の方ができるものではない気がしてきました。。。

です。

## 参考資料

- [2018年Vue.jsとVuexを使ってる人に提案するコンポーネントの分類と設計パターン](https://qiita.com/k-okina/items/06366888409e73dfe70f)
- [2018年今更主張する、フロントエンドでなぜテストをするのか](https://qiita.com/k-okina/items/42ab777f8c53726def17)

## おわりに

いかがでしたか？開発終了まで一度もブラウザを見ずに開発できました。黒い画面(vim)好きなかたはぜひ。
`TDDやれ`って言いたいわけじゃないですが、ロジック層だとTDDやってもいいと思うけど、それよりもまずは簡単なところからでもいいからテストを書いていって、Vue-test-utilsのAPI覚えて、テストに慣れていってください。チームがテストに関して成熟してきたかなと思ったらTDDやってもいいかもしれませんが、はじめにも言いましたが `自分のチーム` で決めたらいいと思います。

少しでもテスト書く人が増えますように！！！！！！！

## Thx

編集リクエストありがとうございます！！

- @yamap_55
- @khsk