Chapter 4: What Makes Great Tests

SWIFT: The Five Qualities of Valuable Tests

長遠來說,ㄧ個有價值的測試能夠節省時間及精力,但是ㄧ個粗劣的測試則是會有相反的效果。

優良測試所具備的 5 個特質:

  • Straightforward

    在測試中所謂的 “straightforwardness” 指的不只是程式碼需要簡單明瞭而已,同時也要傳達出該測試如何與其他ㄧ系列的測試互相協調。

    以下是ㄧ個錯誤的示範例子:

Project Class
― Rails 5 Test Prescriptions ―

“A test is straightforward if its purpose is immediately understandable.” ― Rails 5 Test Prescriptions ―

  • Well defined

    一個定義明確的測試代表即使多次反覆驗證都能維持測試結果的一致性。 換句話說,如果測試定義的不好,通常的徵兆會是測試有時候會成功、有時候會失敗。

    大致來說,重複測試所引起的問題可歸納出三種起因:
    1. 時間&日期 Time and Date testing
    2. 隨機測試用的數字 Random numbers
    3. Third-party / Ajax calls

    以上起因都有著相同的根本性問題,那就是無法保證每次測試都是在連貫的環境下的驗證。 要解決以上問題就需要讓測試的資料能被再次複製,保持ㄧ致性。

    你可以建立能夠處理以上問題的 service object 來封裝測試的資料。 例如在 RandomSteam class 建立 Ruby 所提供的 rand() 方法:
    class RandomStream
      def next
        rand()
      end
    
      def random_phone_number
         ...
      end
    end
    

    另外,使用 Faker 來建立測試資料,也能夠解決以上部分問題。

    “A test is well defined if running the same test repeatedly gives the same result.” ― Rails 5 Test Prescriptions ―

  • Independent

    ㄧ個可以獨立驗證的測試不會取決於其他測試或是來自外部的資料來運作。 在一系列的測試中,對擁有獨立特質的測試來說,即使驗證的順序不同並不會改變其結果,並且能夠更好地掌控測試失敗所影響的範圍。

    當撰寫的測試有過度依靠或是不獨立的問題時,可能會有幾徵兆可以看出來:

    1. 只有在某個測試的順序下,該測試可以順利成功
      • 可以在 spec.helper.rb 檔案中,註解掉 config.order = :random 就可以讓驗證測試的順序是以隨機的方式運作。
      • 可以使用 order--random 指令來確保測試順序是隨機產生的。
    2. 某ㄧ行的程式碼出錯會造成許多測試的失敗

另ㄧ個對獨立測試很大的阻礙就是 global data 的濫用,如果你覺得測試順序是造成你測試失敗的原因,可使用 rspec --bisect 指令找出哪些測試是因為順序的問題而導致失敗的。

“A test is independent if it doesn’t depend on any other tests or external data to run” ― Rails 5 Test Prescriptions ―

  • Fast

    測試的“速度” 在開發軟體初期不會感覺到它的重要性,尤其是只有幾個測試要跑的時候,根本無法察覺到其中的差別。 但實際上,ㄧ個運行速度慢的測試可以造成許多傷害,例如:startup costs。

    ” TDD is about flow in the moment, and the ability to go back and forth between running tests and writing code without breaking focus is curcial to being able to use TDD as a design tool.” ― Rails 5 Test Prescriptions ―

    有許多原因會造成測試速度變慢,但對 Rails 來說最重要的是以下幾項:

    1. Startup time

    2. Dependencies with the code that requires a lot of objects to be created to invoke the moethod under test.

    3. Extensive use of the database or other external services during a test.

    “It’s easy to overlook the importance of pure speed in the long-term maintenance of a test suite or a TDD practice” ― Rails 5 Test Prescriptions ―

  • Truthful

    忠實、坦率的測試能夠反映出程式碼隱含的意義 -- 就是當程式碼能運行時 ,代表測試能夠順利通過,反之亦然。 其中ㄧ個測試的盲點,是將驗證的目標設定在ㄧ般表面的特徵,即是背後的程式碼邏輯已經改變了。

    ㄧ個經典的例子是當測試系統功能在 HTML 檔案,會看到驗證的目標是以 HTML 頁面上文字,來決定測試成功與否。

    it "shows the project section" do
      get :dashboard
      expect(response).to have_selector("h2", :text => "My Projects")
    end
    

    這樣子的測試會很容易被隨意的文字修改,就造成測試的失敗。 更好的驗證目標應該設定在 DOM ID 上才對。

    it "shows the project section" do
      get :dashboard
      expect(response).to have_selector("h2#projects")
    end
    

    類似有關於忠實性的問題,也可以在 model 測試中發現。 當 model 測試只是驗證表面上的 model feature,就會容易被瑣細的改變影響測試結果。

    有關於測試的忠實性並不只限於當測試失敗時但是程式碼邏輯是OK的,更糟的情況是當測試會持續通過,但真實的情況是程式碼出了問題,而沒有反映出來。

    “A truthful test accurately reflects the underlying code–it passes when the underlying code works, and fails when it doesn’t.” ― Rails 5 Test Prescriptions ―