欠陥を見つけるのに役立つテスト


これは私が経験豊富な開発者を教えることを試みる最も重要なレッスンの一つです.あなたのコードが現在動作することを確認するために、開発者が将来欠陥を修正することができることを確認することも重要です.これを実現する第一歩はテストを書くことです.なるべくなら、あなたはテストを書いていますbefore you write the code . しかし、たとえあなたのテストが他の開発者にとってまだアクセスできないかもしれないか、彼らが何が壊れたかについて理解するのが難しいようにしたとしても.テストを良いものから偉大にするためのプロパティのリストがあります.

TLドクター


あなたが記事全体を読みたくないならば、ここにリストがあります.詳細を見つけるために、各エントリをクリックしてください.
  • Each test should test one thing and one thing only
  • The test should do this with precisely the data it needs, nothing more
  • There should be no connection between tests
  • When the test fails the error message should provide context to finding the problem
  • テストにおける混合懸念


    エーconcern ソフトウェア工学では、何がこのコードを行うのですか?あなたのコードがコマンドラインから読み込んで、あなたが少なくとも2つの懸念を扱っているデータベースに書くならば.コードをテストするとき、一度に1つの懸念をテストすることが重要です.テストが失敗したときに、コードのどのような側面が適切に動作していないかについて、より鮮明な画像が得られます.もしあなたのテストが2つ以上の懸念を同時に処理するなら、あなたは何が間違っているかを見始めることができる前に、いくつかの余分な仕事をしなければならない.必要です.
  • テストの問題の数を把握する.
  • どのようにお互いに関連する
  • どちらがトラブルを起こすのか.
  • すべてのこの余分な仕事は、欠陥に応じて重要かもしれない時間がかかります.
    個人的には、私は親指の単純な規則に従います.テスト記述で“and”という単語を書くように誘惑されるたびに、代わりに2つのテストを書きます.例を見てみましょう.
    describe("Database manager", () => {
      it("should support reading and writing to the database")
    })
    
    これはデータベース接続を持つ何かが間違っているときに失敗する大きなテストかもしれません.しかし、どの部分に問題があるのかわかりますか?データを読み込むアプリケーションの一部かもしれませんが、データを書き込む部分でもあります.さらに悪いことに、それは両方ともありえます.このテストを2つに分けることで、この状況から私たちを得ることができます.
    describe("Database manager", () => {
      it("should support reading from the database.")
      it("should support writing to the database.")
    })
    
    最初のテストが失敗したとき、データを読み込むコードが何か間違っていることを知っています.第2のテストが失敗すると、データベースから読み取るコードが欠陥を持つことを知っています.
    私にとって、テストを組み合わせるのは難しいです.新しい機能と、すべての異なるユースケースを始めるとき、私は私の頭にポップをテストする必要があります.これらの状況ではto-do tests 私は主に jest ). DOSのために、私が私がまだすでに書いた既存のテストをブロックすることなく実装する必要があるものを追跡します.

    テストの外部データ


    私は、彼らが簡潔であるとき、私のテストが最も好きです.私にとって、これはテストの中のコードのすべての行が目的を持っていることを意味します.たとえば、次のようなテストを想像してください.
    it("should communicate the value when a user changes the input.", () => {
      const onChange = jest.fn()
      const onKeyDown = jest.fn()
      const value = "test value"
    
      component.setProps({
        value: "initial value",
        onChange,
        onKeyDown,
      })
    
      component.find("input").simulate("click")
    
      component.find("input").simulate("change", { target: { value } })
    
      component.find("input").simulate("blur")
    
      expect(onChange).toHaveBeenCalledWith(value)
    })
    
    さて、次の質問に答えてください.
  • なぜ我々は設定する必要がありますかonKeyDown ハンドラ?
  • 最初にクリックしても重要ですかinput ?
  • このコンポーネントはvalue 正しく動作するには?
  • おそらく“はい”または“いいえ”と任意の質問に答えることはできません.少なくとも、他のテストや、テストするコードに目を通しないでください.
    テストが何らかの些細な行動を実行する必要があるならば、あなたは記述的な名前でもう一つの機能にそれらを抜粋したいかもしれません.たとえば、クリックしてぼやけて入力を変更するには、重要な場合はsimulateChange ヘルパー.
    function simulateChange(value) {
      component.find("input").simulate("click")
      component.find("input").simulate("change", { target: { value } })
      component.find("input").simulate("blur")
    }
    
    これにより、テストが読みやすくなり、変更が複数のステップからなることが明らかになります.
    it("should communicate the value when a user changes the input.", () => {
      const onChange = jest.fn()
      const onKeyDown = jest.fn()
      const value = "test value"
    
      component.setProps({
        value: "initial value",
        onChange,
        onKeyDown,
      })
    
      simulateChange(value)
    
      expect(onChange).toHaveBeenCalledWith(value)
    })
    
    我々はまだ自己説明していない我々のテストの一部を持っています.例えば、onKeyDown ハンドラはmock function しかし、それの主張はありません.我々が割り当てる必要があるならばvalue プロップとonKeyDown テストのためのハンドラは、私たちはおそらくコメントを追加する必要があります動作します.しかし、どちらもそのテストのために重要な場合は、我々はそれらを削除することができます.
    it("should communicate the value when a user changes the input.", () => {
      const onChange = jest.fn()
      const value = "test value"
    
      component.setProps({
        onChange,
      })
    
      simulateChange(value)
    
      expect(onChange).toHaveBeenCalledWith(value)
    })
    
    我々は今、重要なことにテストを削減して、将来の読者がそれを理解してより少ない問題を持っていることを確認しました.

    ブランクテストフック


    一部の開発者はinterdependent tests 望ましくない.実際には、これは1つのテストを変更すると、別の1つが失敗する可能性があることを意味します.「Bloated Test Hooks」は、これの私の特別なバージョンです.
    つのテストが別のテストが実行されたという事実に依存するとき、テストは相互依存になることができます.テストが一般的な設定に依存している場合も同様です.

    マイケル羽
    @ mFethers

    多分、フレームワークをテストする際の'セットアップ'メソッドは'結合'に変更されるべきです
    00 : 29 AM - 10月20日
    この状況には、あなたは react 世界共通の時render テスト間で共有するメカニズム.次の例を見てみましょうonClick ユーザーがボタンをクリックするとハンドラが呼び出されます.
    let component
    let onClick = jest.fn()
    
    beforeEach(() => {
      component = mount(<Button onClick={onClick} />)
    })
    
    it("should communicate when the button was clicked", () => {
      component.simulate("click")
    
      expect(onClick).toHaveBeenCalled()
    })
    
    このテストはすばらしい.開発者はonClick 彼女が避けたかったので、テストの範囲の外のハンドラextraneous data テスト自体で.開発者がテストを維持しようとすると、この動作を観察するDRY .
    個人的には、テストケースでは厳密に乾かす必要があると信じています.いくつかの繰り返しは、それぞれのユースケースに必要なときに良いです.
    ボタンをクリックできないという別のユースケースを追加したいと思います.
    it("should not be possible to click disabled buttons.", () => {
      component.setProps({
        disabled: true,
      })
    
      component.simulate("click")
    
      expect(onClick).not.toHaveBeenCalled()
    })
    
    何が起こると思いますか.テストは失敗するか成功するか我々は確かであるはずがない.テストが他のテストの前に実行されるならば、それは多分成功するでしょう.しかし、テストが最初の1つの後に実行される場合、それは中断します.我々は、抽出することによってテスト間の相互依存性を導入しましたonClick ハンドラモック.
    つのテスト間の接続を解決するには、テストケースにとって重要なすべてをテスト自体に移動する必要があります.両方のテストでは、我々はonClick ハンドラが呼び出されました.それは私たちが自分自身を繰り返しているようだが、各テストケースにそのセットアップを移動するためにはるかに良いことは、これはその特定のテストは、その目標を達成するために重要であることを明らかにする.
    let component
    
    beforeEach(() => {
      component = mount(<Button />)
    })
    
    it("should communicate when the button was clicked", () => {
      const onClick = jest.fn()
    
      component.setProps({
        onClick,
      })
    
      component.simulate("click")
    
      expect(onClick).toHaveBeenCalled()
    })
    
    it("should not be possible to click disabled buttons.", () => {
      const onClick = jest.fn()
    
      component.setProps({
        disabled: true,
        onClick,
      })
    
      component.simulate("click")
    
      expect(onClick).not.toHaveBeenCalled()
    })
    
    この変更で、すべてのテストケースは少し大きいですが、また、それが必要とするすべてのコードをカプセル化します.実際には、我々はまた、各テストにレンダリング部分を移動することができます.
    しかし、一部の開発者は、スペックの外側にコンポーネントをレンダリングし続けることを好む.私は、あなたが最も良いと思うもので行くと言いますが、よくカプセル化された仕様を書くのを妨げません.

    主張の適切な使用


    あなたがTDDと一緒に働いていないときに、これはそれに気付かずに間違って取得する最も簡単なものです.それはすべて我々は平等のチェックとしてアサーションの99 %を表現することができる事実になる.いくつかの例を見てみましょう.
    expect(user.name).toEqual("John")
    
    expect(component.props().disabled).toEqual(true)
    
    expect(error.indexOf("A custom error message")).not.toEqual(-1)
    
    上記の主張のどれも正しくない.彼らは、彼女がスペックを書いたとき、開発者の精神的なモデルを表すかもしれません.これらのテストが失敗したときに、コンソールで見ることができるものを見てみましょう.
    expect(user.name).toEqual("John")
    // > Expected "Jane" to equal "John"
    
    expect(component.props().disabled).toEqual(true)
    // > Expected "false" to equal "true"
    
    expect(error.indexOf("A custom error message")).not.toEqual(-1)
    // > Expected "-1" not to equal "-1"
    
    私がその出力を見るとき、私は決定的にまた、何が間違っているかを理解するためにそれぞれのテストを見なければなりません.しかし、私はそれをするのが好きでありません.私はおそらく、特定の何かに取り組んでいると私の変更のいずれかのテストが失敗した.テスト失敗が私により詳細を与えることができるならば、それは素晴らしいことでありませんか?それから、私は私の間違いが文脈を切り換える必要なしであることを理解することができて、失敗したテストのコードを読むことができるかもしれません.
    良いニュースは、私が知っているすべてのテストフレームワークがより具体的な主張を持っているということです.我々は“ちょうど”それらを使用する必要があります.テストを次のように書き直しますjest and jest-enzyme アサーション
    expect(user).toHaveProperty("name", "John")
    // > Expected property "name" to have value "John", but got "Jane"
    
    expect(component).toHaveProp("disabled", true)
    // > Expected prop "disabled" to be "true", but got "false"
    
    expect(error).toContainText("A custom error message")
    // > Expected "Generic error" to contain "A custom error message", but it didn't
    
    このアプローチの利点は、失敗したテストでは失敗した理由についていくつかのコンテキストを与えることです.たとえば、あなたは今知っているときJohn 等しくなかったJane それは何か関係があったname プロパティ.またはtrue なかったfalse これはdisabled コンポーネントのプロップ.これらは情報の小さな断片であるにもかかわらず、彼らはあなたのキャリアのコースを介して多くの時間を節約する可能性があります.