その他

②Vue3×JestでTDD開発をする【Refactoringまで】

この記事は約9分で読めます。
vue

前回に続いて、ユーザー登録フォーム部分の Green→Refactoring を進めていきます。
ここではテストを通して、より変化に強いアプリケーションを作っていきます。

記事の内容
1:テスト駆動開発とは
2:環境構築(Vue3 x Jest)
3:Red → Green の順番で実装を進める(前回まで)
4:Red → Green → Refactoring(今回)

前回までのファイルは、以下のようになっていました。

SignUp.spec.js

import SignUp from './SignUp.vue';
import { render, screen } from '@testing-library/vue';
import "@testing-library/jest-dom";

describe("ログイン", () => {
  describe('レイアウト', () => {
    it('ログインヘッダー', () => {
      render(SignUp);
      const header = screen.queryByRole('heading', { name: 'ログイン' });
      expect(header).toBeInTheDocument();
    })
    it('ログインボタン', () => {
      render(SignUp);
      const button = screen.queryByRole("button", { name: 'ログインボタン' });
      expect(button).toBeInTheDocument();
    })
  })
})

SignUpPage.vue

<template>
  <h1>ログイン</h1>
  <button>ログインボタン</button>
</template>

ユーザーネームを入力したらログインボタンが押せるようにする

ログインボタンを追加して、「ユーザーがユーザーネームを入力したらログインボタンを押せるようにする」までを実装していきます。
SignUp.spec.js

describe("ログイン", () => {
  describe('レイアウト', () => {
    it('ログインヘッダー', () => {
      render(SignUp);
      const header = screen.queryByRole('heading', { name: 'ログイン' });
      expect(header).toBeInTheDocument();
    })
    it('ユーザーネーム', () => {
      render(SignUp)
      const input = screen.queryByLabelText('username');
      expect(input).toBeInTheDocument();
    })
    it('ログインボタン', () => {
      render(SignUp);
      const button = screen.queryByRole("button", { name: 'ログインボタン' });
      expect(button).toBeInTheDocument();
    })
    it('ログインボタン非活性', () => {
      render(SignUp);
      const button = screen.queryByRole('button', { name: 'ログインボタン' });
      expect(button).toBeDisabled();
    })
  })
})

ログインボタンはデフォルトでは disabled になっているので、ユーザーネームを入力したらボタンが押せるようにしていく必要があります。

今は Jest で2pass, 2errorです。
ユーザーネームと、ログインボタンの部分は未実装なので、SignUpPage.vue はこのように変更します。

SignUpPage.vue

<template>
  <h1>ログイン</h1>
  <label for="username">username</label>
  <input type="username" id="username" v-model="username"/>
  <button :disabled="isDisabled">ログインボタン</button>
</template>

<script>
export default {
  name: 'SignUpPage',
  data() {
    return {
      username: ''
    }
  },
  computed: {
    isDisabled() {
      return this.username && true
    }
  }
}
</script>

これで4passになり、正しい状態になることが確認されました。

interactionを追加する
「ユーザーネームは入力できるが、ログインボタンは押せない」実装までが完了しました。
段々と、ユーザー目線に立ってのテストになっていってます。
次に実装するのは、interaction を用いたテストと実装です。
interaction とはなんでしょうか。
日本語で「交流」という意味になりますが、ここでの interacion は、つまり「Aを押したらBが反応して結果がCとなる」というような「流れ」のテストを指し示すものと考えて良いと思います。SignUp.spec.js をこのように変更します。


describe("ログイン", () => {
  describe('レイアウト', () => {
    it('ログインヘッダー', () => {
      render(SignUp);
      const header = screen.queryByRole('heading', { name: 'ログイン' });
      expect(header).toBeInTheDocument();
    })
    it('ユーザーネーム', () => {
      render(SignUp)
      const input = screen.queryByLabelText('username');
      expect(input).toBeInTheDocument();
    })
    it('ログインボタン', () => {
      render(SignUp);
      const button = screen.queryByRole("button", { name: 'ログインボタン' });
      expect(button).toBeInTheDocument();
    })
    it('ログインボタン非活性', () => {
      render(SignUp);
      const button = screen.queryByRole('button', { name: 'ログインボタン' });
      expect(button).toBeDisabled();
    })
  }),
  describe('インタラクション', () => {
    it('ユーザーが、ユーザーネームを入力したらログインボタンを押せるようにする', async() => {
      render(SignUp);
      const usernameInput = screen.queryByLabelText('username');
      await userEvent.type(usernameInput, 'tanaka');
      const button = screen.queryByRole('button', { name: 'ログインボタン'});
      expect(button).toBeEnabled();
    })
  })
})

ここでしていることは、ユーザーがユーザーネームを入力したら、ボタンが押せるようになることです。テスト項目として、describe(‘レイアウト’) とは無関係なので、describe(‘インタラクション’)に移動してテストを書きます。
ここで、jest を実行するとインタラクション部分でエラーが出るため、SignUpPage.vue を下記のように変更します。

SignUpPage.vue

<template>
  <h1>ログイン</h1>
  <label for="username">username</label>
  <input type="username" id="username" @input="(event) => username = event.target.value"/>
  <button :disabled="isDisabled">ログインボタン</button>
</template>

<script>
export default {
  name: 'SignUpPage',
  data() {
    return {
      username: ''
    }
  },
  computed: {
    isDisabled() {
      return this.username ? false : true
    }
  }
}
</script>

最終的な SignUpPage.vue はこうなります。
SignUpPage.vue

<template>
  <h1>ログイン</h1>
  <input
    id="username"
    v-model="username"
    type="Username"
  />
  <button :disabled="isDisabled">ログインボタン</button>
</template>

<script>
export default {
  name: 'SignUpPage',
  data() {
    return {
      username: '',
    }
  },
  computed: {
    isDisabled() {
      return this.username && true
    }
  }
}
</script>

これにより、interaction を踏まえたテストが完成しました。
いかがでしたでしょうか。
Vue を使ったTDD開発ですが、意外とやることは少ないのに、最終的には yarn test とするだけで単体テストが完了することができてしまいます。
最後に、テストと実コードの対比に関してですが、このような考えがありましたので共有します。

テストを書火なさすぎてエラーを吐いてしまったり、書かなすぎて時間が足りなくなってしまったり。
それらのトレードオフは、自分あるいは会社と相談する必要があると思います。
1:1を目標に、3:1に落ち着くようなテストが、今後書けるように精進したい所存です。

以上、Vue × TDD開発でした。