FrontendでTDDを使用して強力なJSソフトウェアを作成


この文章は、金正煥(キム・ジョンファン)氏の講義を聞いた後、整理して復習したものだ.
  • 課の内容は私の理解の方向と少し違うかもしれません.
  • Frontend JSはTDDが使えますか?


    TDDはいいですね



    TDDを利用してテストコードをずっと抽出し、レンガを1階ずつ開発してきました.これは私の長い間の仕事の経験です.
    個人的には、NodeJSプロジェクトを行い、少しずつ応用してみるTDDはとても甘いです.
    スタックされたテストコードとコードが格納されるたびに、自動テストが行われ、緑で描画されます.
    カバーするときもナレーション効果が出る心配はありません.
    ソースコードを久しぶりに見ても、何度かテストを振り返るだけで開発を再開できます.

    先端でTDDは使えないのでしょうか?


    だからフロントでもTDDを使いたい…
    長所より難しいところがたくさんあります.

    どうしてそうなるの?

    テストしにくいソースコード。


    授業中に話す.
    フロントエンドテストは難しいです.
    その作成はテストしにくいからです.

    テストしにくいソースコード

    <button onclick="counter++; countDisplay()">증가</button>
    <span id="counter-display">0</span>
    
    <script>
      let counter = 0
    
      function countDisplay() {
        const el = document.getElementById("counter-display")
        el.innerHTML = counter
      }
    </script>
    このコードの問題は何ですか?

    関心事項は分離していない

    <button onclick="counter++; countDisplay()">증가</button>
    この線はボタンを作成したり、変数を増やしたり、表示したりします.
    関心事項の分離はまったく実現していない.

    グローバル変数を汚す

    counter変数とcountDisplay関数はグローバルとして宣言される.
    最近谁がモジュールを使わないでこのように使うことができます...

    再利用しにくい

    const el = document.getElementById("counter-display")
    注目事項も分離されていないため、elementIdを指定しても再利用しにくい.

    では、どのようにコードを書くとテストが便利になりますか?


    まずモジュール化の方法を理解してみましょう。


    任意のモジュールモード方式を採用し,グローバル変数を乱さずにモジュール形式で実現する.
    var App = App || {}
    
    App.Person = initName => {
      let name = initName
      return {
        getName: () => name,
        setName: newName => {
          name = newName
        },
      }
    }
    このように実装される任意のモジュールは、以下のように使用される.
    const person = App.Person("jone")
    console.log(person.getName()) // jone
    person.setName("doe")
    console.log(person.getName()) // doe

    TDD思考—注入依存性


    モジュールは、単一の責任原則を実現するために依存性を注入しなければならない.
    次のソースコードは、clickCounter、updateEL注入で使用します.
    結合度を下げ、凝集度を高める
    var App = App || {}
    
    App.ClickCountView = (clickCounter, updateEl) => {
      return {
        updateView() {
          updateEl.innerHTML = clickCounter.getValue()
        },
    
        increaseAndUpdateView() {
          clickCounter.increase()
          this.updateView()
        },
      }
    }

    テストメソッド


    依存項目を入力するかどうかをテスト


    依存性が注入されているかどうかをテストするにはどうすればいいですか?
    注入された変数がない場合は、エラーが発生し、テストされます.
    // ClickCounterView.spec.js
    
    describe("의존성 주입 테스트", () => {
      it("ClickCounter를 주입하지 않으면 에러를 던진다", () => {
        const clickCounter = null
        const updateEl = document.createElement("span")
        const actual = () => App.ClickCountView(clickCounter, updateEl)
        expect(actual).toThrowError()
      })
    
      it("updateEl를 주입하지 않으면 에러를 던진다", () => {
        const clickCounter = App.ClickCounter()
        const updateEl = null
        const actual = () => App.ClickCountView(clickCounter, updateEl)
        expect(actual).toThrowError()
      })
    })
    // ClickCountView.js
    
    var App = App || {}
    
    App.ClickCountView = (clickCounter, updateEl) => {
      if (!clickCounter) throw new Error()
      if (!updateEl) throw new Error()
    
      return {
        // ...
      }
    }
    ClickCounterView.spec.jsactualおよびexpect(actual).toThrowError()に注意してください.

    呼び出すかどうかをテスト


    B関数はA関数を呼び出しますが、B関数のテストコードでA関数を検証する必要がありますか?
    各関数呼び出しの関係に対してテストコードを1つずつ記述すると,テストはかなり困難になる.
    では、B関数のテストでは、A関数をどのようにテストすればいいのでしょうか.
    B関数を呼び出すときにA関数を呼び出すかどうかをテストできますか?
    このとき必要なテスト関数はspyOnである.
    // ClickCounterView.spec.js
    
    describe("increaseAndUpdateView()는", () => {
      it("ClickCounter의 increase 를 실행한다", () => {
        spyOn(clickCounter, "increase")
        view.increaseAndUpdateView()
        expect(clickCounter.increase).toHaveBeenCalled()
      })
    
      it("updateView를 실행한다", () => {
        spyOn(view, "updateView")
        view.increaseAndUpdateView()
        expect(view.updateView).toHaveBeenCalled()
      })
    })
    spyOnspyOn(객체명, "메서드명")を一緒に使用します.
    spyをインプラントすると、オブジェクトを監視する方法.expect(view.updateView).toHaveBeenCalled()を確認できます.
    // ClickCountView.js
    
    var App = App || {}
    
    App.ClickCountView = (clickCounter, updateEl) => {
      // ...
    
      return {
        updateView() {
          updateEl.innerHTML = clickCounter.getValue()
        },
    
        increaseAndUpdateView() {
          clickCounter.increase()
          this.updateView()
        },
      }
    }