React現代化テスト

5404 ワード

テストの動機


テスト用例の作成はリスク駆動の行為であり、Bugレポートを受け取るたびに、まずユニットテストを書いてこのBugを暴露し、後日のコード提出において、このテスト用例が通過すれば、開発者はプログラムが再びこのbugが現れないことをより自信を持って確保することができる.
テストの動機は開発者の自信を効果的に高めることである.

先端現代化テストモデル


フロントエンド試験には2つのモデルがあり、 がある.
ピラミッドモデルはMartin Fowler's blogから抜粋され、モデルの概略図は以下の通りである.
ピラミッドモデルは下からセルテスト,集積テスト,UIテストに分けられるが,ピラミッド構造はセルテストのコストが最も低く,それに対してUIテストのコストが最も高いためである.したがって、ユニットテストの書き込み数が最も多く、UIテストの書き込み数が最も少ない.また,上位レベルのテストほど,その通過率が開発者に与える自信が大きいことに注意すべきである.
トロフィーモデルはKent C.Dotsが提案したThe Testing Trophyから抜粋し、このモデルは筆者が比較的に認めた先端現代化テストモデルであり、モデルの概略図は以下の通りである.
トロフィーモデルでは、静的テスト、ユニットテスト、集積テスト、e 2 eテストに分けられ、それらの職責は大体以下の通りである.
  • :コード論理フェーズの作成時にエラーメッセージを発行します.(代表ライブラリ:eslint、flow、Type Script)
  • :トロフィーモデルでは、ユニットテストの役割は、いくつかの境界状況または特定のアルゴリズムをテストすることである.(代表ライブラリ:jest、mocha)
  • :ユーザの動作をシミュレートしてテストを行い、ネットワークリクエスト、データベースのデータ取得などサードパーティ環境に依存する動作をmockする.(代表ライブラリ:jest、react-testing-library)
  • e2e :ネットワーク要求、データベースデータの取得などを含むユーザの実際の環境での動作をシミュレートするテスト.(代表ライブラリ:cypress)
  • 上位レベルのテストほど開発者に自信を与えるとともに,下位レベルのテストほど効率が高い.トロフィーモデルはこの2つの要因を総合的に考慮し,集積試験におけるその割合が最も高いことが分かった.

    ユーザーの動作に基づいてテスト


    テスト用例を書くのは開発者のプログラムに対する自信を高めるためだが、多くの場合、テスト用例を書くことは開発者に無駄な落胆をもたらす.落胆の原因は、開発者がコンポーネントの具体的な実装の詳細をテストしたためであり、角度を変えてユーザーの行動に立ってテストを行うと、テストの効率が大幅に向上するためであることが多い.
    コンポーネントの詳細をテストすると、次の2つの問題が発生します.
  • 試験例対コード ;
  • 試験例対コード ;
  • を例に、上記の問題を順に見る.図コンポーネントの疑似コードは次のとおりです.
    class Carousel extends React.Component {
      state = {
        index: 0
      }
    
      /*   */
      jump = (to: number) => {
        this.setState({
          index: to
        })
      }
    
      render() {
        const { index } = this.state
        return <>
          
          
          ` ${index} `
        >
      }
    }
    enzymeのapiに基づいて書かれたテスト例を以下に示します.
    import { mount } from 'enzyme'
    
    describe('Carousel Test', () => {
      it('test jump', () => {
        const wrapper = mount(
          
    ) expect(wrapper.state('index')).toBe(0) wrapper.instance().jump(2) expect((wrapper.state('index')).toBe(2) }) })

    合格おめでとうございます✅.ある日、開発者はindexの命名が妥当ではないと感じ、その再構築はindexcurrentPageに改名した.このときコードは以下の通りである.
    class Carousel extends React.Component {
      state = {
        currentPage: 0
      }
    
      /*   */
      jump = (to: number) => {
        this.setState({
          currentPage: to
        })
      }
    
      render() {
        const { currentPage } = this.state
        return <>
          
          
          ` ${currentPage} `
        >
      }
    }

    再度テスト用例を走り、このときexpect(wrapper.state('index')).toBe(0)箇所でエラーを投げ出した❌, これは、いわゆるテスト例がコードを 行ったことである.このコードは使用者にとって問題はないが,テスト用例が誤りを投げ出したため,開発者は「無駄」にしてテスト用例を調整して新しいコードに適合させなければならない.調整後の試験例は以下の通りである.
    describe('Carousel Test', () => {
      it('test jump', () => {
        ...
    
    -   expect(wrapper.state('index')).toBe(0)
    +   expect(wrapper.state('currentPage')).toBe(0)
        wrapper.instance().jump(2)
    -   expect((wrapper.state('index')).toBe(2)
    +   expect((wrapper.state('currentPage')).toBe(2)
      })
    })

    そしてある日、不注意な明ちゃんはコードを以下のように変更しました.
    class Carousel extends React.Component {
      state = {
        currentPage: 0
      }
    
      /*   */
      jump = (to: number) => {
        this.setState({
          currentPage: to
        })
      }
    
      render() {
        const { currentPage } = this.state
        return <>
          
    -     
    +     
          ` ${index} `
        >
      }
    }

    明ちゃんは上記の単測を走って、テストに合格しました.✅, そして喜んでコードを提出しました.その結果、オンラインになった後、オンラインで問題が発生しました!これは、いわゆるテスト例がコードを 行ったことである.テスト例はコンポーネントの内部詳細(ここではjump関数)をテストしたため、明ちゃんはすべてのシーンをカバーしていると勘違いした.
    テスト例 および は、コンポーネント内部の具体的な詳細をテストしたため、開発者に挫折感と悩みをもたらした.安定で信頼性の高いテスト・インスタンスは、コンポーネント内部の実装の詳細から逸脱しなければならない.ユーザーの動作に近いテスト・インスタンスほど、開発者に十分な自信をもたらすことができる.enzymeと比較して、react-testing-libraryが提供するapiは、上記のテスト例を再構築するために、ユーザーの使用動作に近い.
    import { render, fireEvent } from '@testing-library/react'
    
    describe('Carousel Test', () => {
      it('test jump', () => {
        const { getByText } = render(
          
    ) expect(getByText(/ /)).toBeInTheDocument() fireEvent.click(getByText(/ /)) expect(getByText(/ /)).not.toBeInTheDocument() expect(getByText(/ /)).toBeInTheDocument() }) })
    react-testing-Libraryの使い方については、次の章Jestとreact-testing-Libraryで具体的に説明します.Reactテクノロジースタックに興味があれば、個人ブログに注目してください.

    関連リンク

  • write-tests
  • Testing
  • Testing Implementation Details
  • why-i-never-use-shallow-rendering
  • react-testing-1-best-practices