測試程式碼是軟體開發最重要的方面之一,因為它可以確保產品的品質、可擴展性和可靠性。

但是,如果沒有任何指導,編寫有效的測試可能會很困難。測試程式碼可能比實際功能的程式碼更複雜、更難維護!

原文出處:https://dev.to/one-beyond/the-5-principles-of-unit-testing-1p5f

1.精簡和準確的測試

測試程式碼必須簡單且易於使用。任何查看測試的人都應該立即知道測試的內容及其目的是什麼。開發測試應該以很少的精力和時間投入帶來巨大的價值。

您是否需要超過 30 秒的時間來閱讀和理解您的測試?重寫它!

測試“覆蓋率”是重點嗎?不! 僅測試必要的部份就好。為了敏捷性和簡單性,最好放棄一些測試,只測試主要業務邏輯和主要邊緣情況就好

2.測試行為,而不是元件實作

不要檢查程式碼中的每一行和內部變數。測試時,您應該專注於結果。即使方法內的程式碼被重構,結果也應該永遠保持不變!

這樣,如果程式碼庫發生更改,您不需要重寫測試

// Wrong ❌ - Test behaviour
describe('Evaluation Service', () => {
  describe('Register Students', () => {
    it('Should add new students to the evaluation service', () => {
      const studentJosh = {
        id: 1,
        name: 'Josh McLovin',
        average: 6.98,
      }
      evaluationService.addStudent(studentJosh)

      expect(evaluationService._students[0].name).toBe('Josh')
      expect(evaluationService._students[0].average).toBe(6.98)
    })
  })
})
// Right ✅ - Test behaviour
describe('Evaluation Service', () => {
  describe('Register Students', () => {
    it('Should add new students to the evaluation service', () => {
      const studentJosh = {
        id: 1,
        name: 'Josh McLovin',
        average: 6.98,
      }

      evaluationService.addStudent(studentJosh)
      expect(evaluationService.getStudentAverage('Josh')).toBe(6.98)
    })
  })
})

3. 關於命名和結構:AAA 模式

您是否曾經遇到過名為“它應該[...]正確”的失敗測試,但花了幾分鐘才找到問題所在?

良好命名和結構可以讓您快速且準確地找到任何失敗的測試,最終節省您的寶貴時間。因此,讓我們深入探討兩個關鍵原則,以便在下次測試時牢記:

3.1 經過深思熟慮的測試命名:

在命名您的測試時,請嘗試合併以下資訊:

  - 正在測試什麼

  - 什麼情況

  - 預期結果是什麼?

// Right ✅ - Test naming

// 1. What is being tested:
describe('Evaluation Service', () => {
  describe('Evaluate Students', () => {
    // 2 & 3. Context and expected result
    it('If the student grade is below the minimum grade, student should be suspended', () => {
      const students = [
        { name: 'Mark', grade: 4.25 },
        { name: 'Colin', grade: 6.7 },
        { name: 'Ben', grade: 5.3 },
      ]

      const result = evaluationService.evaluateStudents({ students, minGrade: 5 })

      expect(result['Mark']).toBe('suspended')
    })
  })
})

3.2 測試程式碼結構的AAA模式:

如果您想維護一個可讀且易於理解的測試套件,請按如下方式建立測試:

  • 安排:設定模擬所需情況所需的所有程式碼。這可以包括初始化變數、模擬響應、實例化被測單元等。

  • 行為:執行正在測試的內容,通常在一行程式碼中。

  • 斷言:檢查得到的結果是否為預期的結果。與上面的一樣,這應該只需要一行。

// Right - AAA Testing Pattern
describe('Evaluation Service', () => {
  describe('Average Calculation', () => {
    it('Should calculate the average grade of all the students', () => {
      // Arrange: create an object with the student names and their grades
      const students = [
        { name: 'Mark', grade: 4 },
        { name: 'Colin', grade: 10 },
        { name: 'Ben', grade: 7 },
        { name: 'Tim', grade: 3 },
      ]

      // Act: execute the getAverage method
      const avg = evaluationService.getAverage(students)

      // Assert: check if the result is the expected one -> (4+10+7+3)/4 = 6
      expect(avg).toEqual(6)
    })
  })
})

4。確定性和隔離測試

如果一個失敗的測試使您的整個套件變紅,那麼您可能沒有以正確的方式處理它!

測試應該獨立和隔離,一次針對並處理一個特定邏輯,從而完成更快、更穩定的測試套件。

如果您不獨立編寫測試會發生什麼?

  • 您將無法找出錯誤和問題的確切原因和位置

  • 重構測試時,您必須更新和同步多個測試

  • 您將無法以任何順序執行測試,這可能會導致破壞或跳過某些斷言或期望

5。基於屬性的測試和真實資料

厭倦了在測試中編寫大量可能的輸入?基於屬性的測試可以為您做到這一點!……那是什麼?

基於屬性的測試建立了數百種可能的組合,強調了測試並增加了發現以前未被注意到的錯誤的機會。這種方法甚至可以傳回可能導致意外結果的輸入。

JSVerifyFast-Check 等函式庫提供了促進基於屬性的測試的基本工具。

但是,如果您不想深入進行如此廣泛的測試,那麼盡可能利用真實資料至關重要。像“abc”或“1234”這樣的輸入可能會在實際失敗時錯誤地通過測試。

// Wrong ❌ - False Positive - Test that passes even though it shouldn't 
class EvaluationService {
  _students =  [];

  addStudent(student) {
    // Add the student if the name has no numbers
    if(!student.name.matches(/^([^0-9]*)$/)){
      this._students.push(student);
    }
  }
}

describe('Evaluation Service', () => {
  describe('Register Students', () => {
    it('Should add students to the Evaluation service', () => {
      const mockStudent = {
        id: 2,
        name: 'username',
        average: 7
      }
      // Won't fail because the name is a string without number -> We are not checking what happens if the user
      // inputs a name with a number.
      evaluationService.addStudent(mockStudent) 
      expect(evaluationService.getStudentAverage('username')).toBe(7)
    })
  })
})
// In the example above, we are making a test so the test passes, instead of looking for edge cases with realistic data

額外提示!

如果您在測試元件中的任何邏輯時遇到困難,這可能反映出您也許應該將元件邏輯分解為更小、更容易且可測試的程式碼片段!

總而言之,遵循此最佳實踐可能會帶來一種新的、可讀且可維護的方法來測試您喜愛的生產程式碼。

經過測試的應用程式才是可靠的應用程式!


共有 0 則留言