Open8

そろそろテストを学習する

いもけんいもけん

テストとは

ソフトウェアの品質を担保するためのもの

フロントエンドのテストは開発者の意図通りに機能が使用できるか
データベースなどは意識せず、あくまで画面上の動作

いもけんいもけん

なぜ必要なのか

  • 開発中の早い段階でエラーを発見できる
  • 本番リリース前に問題を修正できる(デグレがないことが担保できる)
  • 国際化対応やアクセシビリティなどの特定の観点からチェックできる
いもけんいもけん

特に有名なテストの種類

テストトロフィー

  • 上のレイヤーに行くほどコストが高く速度が遅いが信頼性は高くなる

ユニットテスト(単体テスト)

  • 関数やメソッドのテスト
  • Jestが最も使われている(最近はVitestも普及)
  • 本番のAPIではなくモックデータが使用される

インテグレーションテスト(結合テスト)

  • ユニット間のインタラクションのテスト
  • TestingLibraryがよく使われている
  • 機能ごとの単体テストに対して、複数の機能が連動するかをテストする

E2Eテスト(エンドツーエンドテスト)

  • ブラウザを動作させて行うインタラクションテスト
  • Cypressがよく使われている(最近はPlaywrightが普及)
  • コードや実装とは一切関係なくユーザーからアプリケーションがどう見えるか・どのように動かすかをテストする
  • モックではなく実際のAPIを使う

スタティックテスト

  • コードのフォーマットやタイポ、型のテスト
  • ESLintやPrettierなど
いもけんいもけん

一旦テストを自作してみる

(関数名をjestで使うもに合わせた)

// テストする関数
function sum(a, b) {
  return a + b
}

// テストケース
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3)
})
test('adds 3 + 4 to equal 7', () => {
  expect(sum(3, 4)).toBe(8)
})

// テスト処理の実装
async function test(title, callback) {
  try {
    await callback()
    console.log(`${title}`)
  } catch (error) {
    console.error(`${title}`)
    console.error(error)
  }
}

function expect(result) {
  return {
    toBe(expected) {
      if (result !== expected) {
        throw new Error(`${result} is not equal to ${expected}`)
      }
    },
  }
}
いもけんいもけん

実際にjestを使ってみる

今回はts-jestを使用

インストール

yarn add --dev ts-jest

ファイル作成

sum.ts
function sum(a, b) {
  return a + b
}
module.exports = sum
sum.test.ts
const sum = require('./sum')

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3)
})

実行

yarn test

いもけんいもけん

コンポーネントテスト(ユーザーイベント(クリック))

簡易コンポーネント作成

ボタンをクリックするたびに表示がON,OFF切り替わるコンポーネントを作成

src/components/SinmpleButton.tsx
import { useState } from 'react'

export const SimpleButton: () => JSX.Element = () => {
  const [state, setState] = useState(false)
  const handleClick = () => {
    setState((prevState) => !prevState)
  }
  return <button onClick={handleClick}>{state ? 'ON' : 'OFF'}</button>
}

テストコード

src/components/SimpleButton.test.tsx
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import '@testing-library/jest-dom'
import { SimpleButton } from './SimpleButton'

test('ボタンをクリックするとON/OFFの表示が切り替わる', () => {
  render(<SimpleButton />)
  const simpleButton = screen.getByRole('button')
  expect(simpleButton).toHaveTextContent('OFF')

  // ボタンをクリックして、テキストが変更されることを確認する
  userEvent.click(simpleButton)
  expect(simpleButton).toHaveTextContent('ON')
})

はまったところ

  • コンポーネントテストの際は拡張子を.tsxにしないといけない
  • テストの実行環境がnodeになっていたのでjsdomでないといけない
いもけんいもけん

コンポーネントテスト(ユーザーイベント(入力フォーム))

入力フォームコンポーネントを作成

src/copmponents/SearchForm.tsx
import { ChangeEvent, FC, useState } from 'react'

export const SearchForm: FC = () => {
  const [value, setValue] = useState<string>('')
  const onchange = (e: ChangeEvent<HTMLInputElement>) => {
    setValue(e.target.value)
  }

  const onclick = () => {
    setValue('')
  }

  return (
    <>
      <input type="text" onChange={onchange} value={value} />
      <button onClick={onclick}>検索</button>
    </>
  )
}

入力フォームコンポーネントテスト作成

src/components/SearchFrom.test.tsx
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
import userEvent from '@testing-library/user-event'
import { SearchForm } from './SearchForm'

describe('input form onchange and button onclick event', () => {
  it('input test', () => {
    render(<SearchForm />)
    const inputValue = screen.getByRole('textbox') as HTMLInputElement
    const searchBtn = screen.getByRole('button')
    // インプットの中にtestと打った時にtestと入力されているか
    userEvent.type(inputValue, 'test')
    expect(inputValue.value).toBe('test')

    // 検索ボタンを押した際にインプットの中身が空になるかテスト
    userEvent.click(searchBtn)
    expect(inputValue.value).toBe('')
  })
})

解説

  • .getByRoleで入力フォームの要素のボタンの要素をそれぞれ取得
  • 型推論がHTMLElementなのでHTMLInputElementをas(型アサーション)で付与
  • userEvent.typeでユーザーのタイピングを実現させる
  • userEvent.clickでユーザのクリックを実現させる
  • expect(inputValue.value)でその時点での要素の中身を確認