像蓋房子一樣寫程式碼:當我以測試驅動開發的時候,我在想些什麼

willin發表於2019-02-16

當我寫一個功能模組方法時,我在想些什麼

// 無論什麼方法,都是這樣一個結構
const fn = () => {

};

比如,我要寫一個介面,查詢組織下的裝置列表 /api/device/list

地基

const deviceList = (params) => { // 傳入一些引數
  return []; // 返回一個列表
};

我需要哪些引數:

  • 使用者基本資訊(主要是使用者 id,使用者的組織 id)
  • 使用者對應的組織基本資訊(主要是組織 id,組織管理員 id,層級關係,以及許可權邏輯)

輸出結果很簡單,為一個陣列。

澆築

第一步分析,存在成功和錯誤(錯誤型別先不考慮)兩種型別的結果。

// 成功
// 錯誤
const deviceList = async (ctx) => {
  // 錯誤
  if(someError) {
    // 返回錯誤結果
  }
  // 成功
  return getDevicesByOid(oid);
};

這是一個大概的設想,沒有必要將程式碼寫出來。然後潤化該思路,寫出第一段框架。

主體結構

首先,傳入的引數為組織 oid,使用者的資訊可以通過 session(或其他方式)從內部獲得。

可能的一種思路

// 成功
// 錯誤
// 錯誤1:使用者未加入組織
// 錯誤2:傳入引數組織不存在
// 錯誤3:使用者無組織許可權

// 傳入引數: 要查詢的組織 oid
// 能夠通過 session 取到的資訊: user
const deviceList = async (ctx) => {
  // 使用者資訊 ctx.user
  // 判斷使用者是否有組織
  if (ctx.user.oid === 0) {
    // 錯誤1:使用者未加入組織
  }

  // 如果不傳該引數,查詢當前使用者組織的裝置
  const { oid = ctx.user.oid } = ctx.request.body;
  if (oid === ctx.user.oid) {
    // 成功
    return getDevicesByOid(oid);
  }

  // 根據oid查詢組織資訊
  // 錯誤2:傳入引數組織不存在
  // 判斷是否有許可權
  const checkRights = await checkUserOrgRights(ctx.user.uid, oid);
  if (!checkRights) {
    // 錯誤3:使用者無組織許可權
  }
  // 成功
  return getDevicesByOid(oid);
};

推薦的實現方式

// 成功
// 錯誤
// 錯誤1:使用者未加入組織
// 錯誤2:傳入引數組織不存在
// 錯誤3:使用者無組織許可權

// 傳入引數: 要查詢的組織 oid
// 能夠通過 session 取到的資訊: user
const deviceList = async (ctx) => {
  // 使用者資訊 ctx.user
  // 判斷使用者是否有組織
  if (ctx.user.oid === 0) {
    // 錯誤1:使用者未加入組織
  }

  // 如果不傳該引數,查詢當前使用者組織的裝置
  const { oid = ctx.user.oid } = ctx.request.body;
  if (oid !== ctx.user.oid) {
    // 為什麼這裡不用等於判斷:如果等於的話,則當時就需要返回出去,這樣的話該方法會有兩個成功的 return
    // 根據oid查詢組織資訊
    // 錯誤2:傳入引數組織不存在
    // 判斷是否有許可權
    const checkRights = await checkUserOrgRights(ctx.user.uid, oid);
    if (!checkRights) {
      // 錯誤3:使用者無組織許可權
    }
  }
  // 成功
  return getDevicesByOid(oid);
};

封頂

完成其他的業務程式碼。

當我寫一段測試的時候,我在想些什麼

按照上面推薦方式完成程式碼後,需要進行程式碼的測試。

首先需要明確業務的流程,理清測試的思路。

  • 成功
  • 錯誤

    • 錯誤1:使用者未加入組織
    • 錯誤2:傳入引數組織不存在
    • 錯誤3:使用者無組織許可權

主要有兩種設計思路:

設計思路

思路一

  1. 完成測試用例,覆蓋成功的所有情況
  2. 完成測試用例,覆蓋錯誤1的所有情況
  3. 完成測試用例,覆蓋錯誤2的所有情況
  4. 完成測試用例,覆蓋錯誤3的所有情況

這是傳統的單元測試衍生而來的 BDD 測試方式。

這裡測試用例的個數應該為8次:

  • 成功:

    • 1.當前組織的使用者有傳入組織 oid
    • 2.當前組織的使用者未傳入組織 oid
    • 3-5.上級組織,上上級組織,根級組織的管理員使用者傳入組織 oid
  • 6.失敗1:使用者未加入組織
  • 7.失敗2:傳入引數組織不存在
  • 8.失敗3:使用者無組織許可權

其中,測試3-5可以優化為一次測試(即根據所有管理員 uid 的陣列比較是否包含當前使用者 uid),最終優化後的結果應當為6次。

但由於該思路中不明確使用者,所以使用者行為無法準確表達,在建立測試資料的時候較為困難,不仔細思考分析,無法優化需要建立多少條測試資料。

思路二

而實際上 BDD 測試為使用者行為測試,可以以幾類使用者的情形分別進行測試。

  1. 模擬一個使用者的資料,覆蓋成功和可能錯誤(有可能無法涵蓋到所有錯誤)的所有情況
  2. 根據未覆蓋的部分,再模擬另一個使用者的資料,覆蓋成功和可能錯誤(有可能無法涵蓋到所有錯誤)的所有情況

以此迴圈,直至覆蓋所有。

  • 使用者1(非組織管理員,查詢自己的組織)

    • 1.成功(未傳入組織 oid)(組織1)
    • 2.成功(傳入組織 oid)
    • 3.失敗2:傳入引數組織不存在
    • 4.失敗3:使用者無組織許可權(組織2)
  • 使用者2(上級某組織管理員)(組織3)

    • 5.成功
  • 使用者3(未加入組織使用者)

    • 6.失敗1:使用者未加入組織

非常簡潔明瞭的關係,需要3個測試使用者,3個組織(上下級關係進行資料複用,一個無許可權的組織),即可涵蓋所有範圍。

最終優化版設計:

  • 使用者1(某組織管理員,有下級組織)

    • 1.成功(未傳入組織 oid,查詢自己的組織)
    • 2.成功(傳入當前的組織 oid(組織1))
    • 3.成功(傳入下級的組織 oid(組織2))
    • 4.失敗2:傳入引數組織不存在
    • 5.失敗3:使用者無組織許可權
  • 使用者2(未加入組織使用者)

    • 6.失敗1:使用者未加入組織(組織3)

兩個使用者,三個組織。完成所有覆蓋。

當我以測試驅動開發的時候,我在想些什麼

可以從上述測試思路二中進行反推。

實際上思路可能是在寫程式碼或者寫測試的過程中不斷的改進和完善的。

  • 如果已經寫好了測試正在寫程式碼,可以及時回過頭來調整測試;
  • 如果功能寫好了又再重新測試,可以在測試優化後再去看邏輯程式碼是否還有優化的空間。

更多關注: https://leader.js.cool/#/expe…

相關文章