AI 如何加速前端單元測試和團隊協作,提升產出品質

公司主力產品是 2B TMS 物流系統,自己負責帶領前端團隊處理管理後台、物流士勤務 APP,最常需要針對 (1) 第三方 ERP 資料匯入的格式驗證 (2) 表單 (form) 格式驗證 (3) 正規化為系統格式 幾個部分做偵錯。

我們發現在加入 OpenAI ChatGPT 4 進單元測試後,在跨部門協作品質上也得到有感的提升,一來科普了 AI 在公司內非技術團隊間的應用,二來可以讓團隊提升對於交付產品的品質與信心。

我們 React 使用 Vite 框架,選擇 Vitest 框架做單元測試,比起 Jest 在安裝、Config 設定上相容性更優異。

GPTs

我們使用 GPT4 訓練了一個 GPTs,省下在每次需要撰寫單元測試時都需要重新下 prompt,且每人下的 prompt 都不同,所以提供了一些指導原則:

  1. 理解程式碼
  2. 使用抽象測試框架
  3. 確保小型且獨立的測試
  4. 賦予測試結構:describe、it、expect、before、after
  5. 使用 AAA 原則:Arrange、Act、Assert
  6. 測試條件:正/反向、edge case 測試
  7. 最後附了一個最佳實踐的連結

完整 Instruction 如下,有更好的 prompt 可以一起交流:

Here are instructions from the user outlining your goals and how you should respond:
As Vitest Helper, your primary task is to generate complete unit test code for functions or files provided by the user, specifically using Vitest. Assume that the user has already installed the Vitest environment, and focus on writing unit tests for each function in the provided code. 

Your tests should be comprehensive, covering all aspects of the code, ensuring high test coverage and attention to all critical factors in unit testing. You will directly produce test code without any prefixes, and the tests should reflect best practices in unit testing.

Understand the Codebase: Analyze the TypeScript code thoroughly, step by step. Identify the possible ambiguity or missing information such as constants, type definitions, conditions, external APIs, etc and provide steps, and questions and seek clarification for better code understanding. Only proceed to the next step once you have analyzed the codebase fully.

Testing framework: For this task, use an abstract testing framework instead of known frameworks such as chai, jest, etc., remembering that the principles of unit testing apply regardless of the specific tools.

Design Small, Focused Tests: Each unit test should focus on one functionality, enhancing readability and ease of debugging. Ensure each test is isolated and does not depend on others. Simulate the behavior of external dependencies using mock objects to increase the reliability and speed of your tests.

Structure and Name Your Tests Well: Your tests should follow a clear structure and use descriptive names to make their purpose clear. Follow the provided below test structure:

// import necessary methods from an abstract testing framework
import { describe, expect, it, beforeAll, beforeEach, afterAll } from 'vitest';

// import necessary function names from upload file and aggregate all these function names in one line
import  { {variable} , {variable1}} from '../src/{modules}/{variable2}'

// <NAME_OF_MODULE_TO_TEST>  should clarify what this group test is implement 
describe('<NAME_OF_MODULE_TO_TEST>', () => {
  // Define top-level test variables here

  beforeAll(async () => {
    // One-time initialization logic _if required_
  });

  beforeEach(async () => {
    // Logic that must be started before every test _if required_
  });
  
  afterAll(async () => {
    // Logic that must be started after all tests _if required_
  });

  // Use method-lavel beforeAll, beforeEach or afterAll _if required_
  // each <TEST_CASE>  name should clarify explain what this test function implement and follow Goole General Naming Rules 
  it('<TEST_CASE>', async () => {
    // Test case code

    // to assert definitions of variables use:
    // expect(<VARIABLE>).toBeDefined();

    // to assert equality use:
    // expect(<TEST_RESULT>).toEqual(<EXPECTED_VALUE>);
    // expect(<TEST_RESULT>).toStrictEqual(<EXPECTED_VALUE>);

    // for promises use async assertion:
    // await expect(<ASYNC_METHOD>).rejects.toThrow(<ERROR_MESSAGE>);
    // await expect(<ASYNC_METHOD>).resolves.toEqual(<EXPECTED_VALUE>);
  });

});

Your additional guidelines:
Implement the AAA Pattern: Implement the Arrange-Act-Assert (AAA) paradigm in each test, establishing necessary preconditions and inputs (Arrange), executing the object or method under test (Act), and asserting the results against the expected outcomes (Assert).

Test the Happy Path and Failure Modes: Your tests should not only confirm that the code works under expected conditions (the 'happy path') but also how it behaves in failure modes.

Testing Edge Cases: Go beyond testing the expected use cases and ensure edge cases are also tested to catch potential bugs that might not be apparent in regular use.

Avoid Logic in Tests: Strive for simplicity in your tests, steering clear of logic such as loops and conditionals, as these can signal excessive test complexity.

Leverage TypeScript's Type System: Leverage static typing to catch potential bugs before they occur, potentially reducing the number of tests needed.

Handle Asynchronous Code Effectively: If your test cases involve promises and asynchronous operations, ensure they are handled correctly.

Write Complete Test Cases: Avoid writing test cases as mere examples or code skeletons. You have to write a complete set of tests. They should effectively validate the functionality under test. Besides, you should write complete tasks case to ensure upload file content all covered unit test.

Your ultimate objective is to create a robust, complete test suite for the provided TypeScript code.

If a user's request is cut off due to token limitations, you will continue writing the test code in a follow-up message. Additionally, you will clarify or ask for more context if the provided code is unclear or complex, to ensure accurate and effective testing solutions.

Last but not least, please follow below link to offer test case
https://github.com/goldbergyoni/javascript-testing-best-practices?tab=readme-ov-file#section-3%EF%B8%8F%E2%83%A3-frontend-testing

格式驗證

我們使用 yup 做 validation,如果牽涉到驗證表單,我們使用 React Hook Form,來處理,一個案例是我們需要驗證一個字串型數字是否合規。這裡為了保留 user 匯入的各種格式的資料都可以被呈現,特地不限制 input 為 number 型別:

type YupNumericStringOptions = {
  min: number,
  max: number,
  decimalPlaces?: number,
  allowEmpty?: boolean,
}

export const yupNumericString = (
  t: TFunction<'translation', undefined>,
  errorMessage: Message,
  { min, max, decimalPlaces, allowEmpty }: YupNumericStringOptions = { min: 0, max: Infinity, decimalPlaces: 0, allowEmpty: true }
) => {
  return yup.string()
    .transform(value => value?.trim())
    .test(
      'numericString',
      errorMessage,
      (value) => {
        if (!value) return !!allowEmpty

        const num = parseFloat(value)
        const decimalRegex = decimalPlaces && decimalPlaces > 0
          ? new RegExp(`^-?\\d+(\\.\\d{1,${decimalPlaces}})?$`) // Regex for decimal with specified decimalPlaces
          : /^-?\d+$/ // Regex for integer if decimalPlaces is 0 or not allowed

        return !isNaN(num) &&
             decimalRegex.test(value) &&
             num >= min && num <= max
      })
}

我們丟回訓練好的 GPTs,首先會先解析這個 function

To create a comprehensive set of unit tests for the yupNumericString function using Vitest, we'll focus on the various behaviors encapsulated within the function, including handling empty values, validating number ranges, checking decimal precision, and respecting the allowEmpty parameter.

The function depends on:

TFunction and Message types, which appear to be related to localization and error handling. We'll assume basic string types for testing purposes unless more specific details are provided.
YupNumericStringOptions which outlines the constraints for the numeric string validation.
An external validation library (yup) which is assumed to be mocked or abstracted for testing.
Let's write the unit tests:

再來提供初步的 Unit Test 程式碼

// import necessary methods from an abstract testing framework
import { describe, expect, it, beforeEach } from 'vitest';

// Simulating the TFunction and Message types for testing
type TFunction<K extends string, T> = (key: K) => string;
type Message = string;

// Import the function to test
import { yupNumericString } from '../src/validators/yupNumericString';

describe('yupNumericString', () => {
  let tFunction: TFunction<'translation', undefined>;
  let errorMessage: Message;

  beforeEach(() => {
    // Mocking the translation function for simplicity
    tFunction = (key: 'translation') => 'Error message';
    errorMessage = 'Invalid input';
  });

  it('should accept a valid integer within range', async () => {
    const validator = yupNumericString(tFunction, errorMessage, { min: 1, max: 10 });
    const result = await validator.validate('5');
    expect(result).toEqual('5');
  });

  it('should reject an integer outside the range', async () => {
    const validator = yupNumericString(tFunction, errorMessage, { min: 1, max: 10 });
    await expect(validator.validate('0')).rejects.toThrow('Invalid input');
  });

  it('should accept a valid decimal number when decimals are allowed', async () => {
    const validator = yupNumericString(tFunction, errorMessage, { min: 0, max: 5, decimalPlaces: 2 });
    const result = await validator.validate('4.25');
    expect(result).toEqual('4.25');
  });

  it('should reject a decimal number with too many decimal places', async () => {
    const validator = yupNumericString(tFunction, errorMessage, { min: 0, max: 5, decimalPlaces: 1 });
    await expect(validator.validate('4.25')).rejects.toThrow('Invalid input');
  });

  it('should handle empty string when allowEmpty is true', async () => {
    const validator = yupNumericString(tFunction, errorMessage, { allowEmpty: true });
    const result = await validator.validate('');
    expect(result).toEqual('');
  });

  it('should reject empty string when allowEmpty is false', async () => {
    const validator = yupNumericString(tFunction, errorMessage, { allowEmpty: false });
    await expect(validator.validate('')).rejects.toThrow('Invalid input');
  });

  it('should reject a non-numeric string', async () => {
    const validator = yupNumericString(tFunction, errorMessage, { min: 1, max: 100 });
    await expect(validator.validate('abc')).rejects.toThrow('Invalid input');
  });

  // Additional tests can be added for different scenarios such as negative numbers, boundary values, etc.
});

最後針對程式碼的解釋

Explanation
Mocking Dependencies: We mock TFunction and Message for simplicity.
Test Scenarios: Tests cover different scenarios like valid inputs, range checking, decimal precision, handling empty strings, and validating against non-numeric inputs.
AAA Pattern: Each test is structured with arrange (setup), act (validation call), and assert (checking the outcome).
This structure ensures the function is robustly tested against various typical and edge cases. If there are further specific scenarios or configurations you would like to test, let me know!

比起過往要從 0 發想測資到撰寫出來,AI 加速了這一切的進行,讓我們更快速的迭代 Test Case 與提升交付品質。

團隊協作

與 QA 一起 Unit Test:如果 QA 有使用 Test Case Management System (如 QASE),可以討論把測項加入到 Unit Test 的 codebase 裡。如果沒有的話就訪問他平常怎麼測試然後記錄下來 XDD

資源充足的最佳狀況是 QA 應在 RD 對立面,儘管 RD 測試通過 QA 仍做最後環節把關,畢竟如果程式碼出錯呢?但現實是時間有限的,在中小型團隊如果在一開始就通過 Unit Test,並加入 git push 或 CI 環節的檢查,那從交付的當下就能確保品質,並變相減輕 QA 的負擔,進而去規劃更完整的測試流程及項目。

科普團隊 AI Unit Test:在站會或 Task Management System (如 Jira、Trello、clickup 等) 提到在這次交付做了哪些情境的測試,告訴團隊如何讓 AI 提升交付品質、GPTs 如何訓練,一起發想如何應用在各個部門的專業上。

接收前線第一手資訊如 PO/PM,他們會更放心團隊所交付的成果,即使在客戶端發生錯誤,修正後也不會出現改 A 壞 BCDEFG 的狀況。

Read more

2024.08 加密貨幣行情看法

當前幣價:BTC $60500、ETH $2640。 市場剛經歷過 08/05 大跌,日線 ema 30/60/120 糾纏中,週線級別迎來回調。 7 月操作回顧 3 張空單 止損 1 次,止盈 2 次。盈虧比 1:1.5。 第二筆:ema 60 未下穿 120 提前預判空頭排列嘗試空單,打到止損出場。 第三筆:07/29 ~ 07/31 沒有突破前高又壓回均線,決定再次嘗試空單,碰巧遇到 08/05 下跌止盈。 三筆交易都在尚未確認空頭排列就提前預判進入市場,仰賴的是下緣支撐位

2024.07 加密貨幣行情看法

目前狀況 幣價:BTC $57150、ETH $3065。日線 ema 30/60/120 即將進入空頭排列,週線級別迎來回調。 短期看法 下跌概率 > 上漲,但當前無法確定是觸底或即將反彈,需要時間 + 支撐位與阻力位驗證。 驗證方法:日線 ema 壓下來之後即是阻力位,能夠突破就能成為新的支撐位視為反彈訊號,根據概率建立好劇本及應對策略。以交易結果為主,用結果驅動下一步動作。 長期看法 長期看法不變。身為 BTC 信仰者,跌下來是撿便宜的好時機,但不確定多便宜,但還是要分批謹慎進出場。 應對策略 打到支撐位分批賣出現貨,同時觀察幣價盤整位置,通常會在支撐位、阻力位、整數位 (50000、60000) 等盤整,如果不繼續跌就可以進場開始撿便宜。 近期可能會有一波小反彈,像是多頭排列也會回調至均線,如果突破失敗可以當作一個做空訊號,止損設置前高,

2024.06 加密貨幣行情看法

繼上次寫完 2024.05.01 加密貨幣行情看法,覺得每 1, 2 個月寫一篇觀點還蠻有趣的。也記錄自己對行情的理解。 目前狀況:當下 BTC $69500、ETH $3670。 短期看法: 短期仍偏空,BTC、ETH ETF 通過等利好消息都釋出了,市場仍未突破前高。 美股、台股再創新高,整個市場非常狂熱,都在討論 NVIDIA + 黃仁勳,有點 Tesla + Elon Musk 及 Ark + Cathie Wood 的既視感,這種時候是很好的出貨時間點。 上個月 BTC 回落 57000 是個很好的支撐,大約是華爾街機構的持倉成本線,再往下探頂多 50000 或 42000。 從

來自從事高管的 60 歲大伯的職涯建議

1. 不支持裸辭,大公司 (上市櫃) 會在意 gap year,要有很好的理由,e.g. 家務。 2. 人生沒有辦法每個時段都有導師,但書本可以,同意 ✅。 3. 推薦 AI:Perplexity,Good ✅ 4. 推薦書籍:如何思考怪誕現象、底層邏輯,Good ✅ 最喜歡被推書 👍 5. 面試準備不完的,要針對該公司背景客製化內容,盡可能要沾上邊。 同意 ✅,與近期面試得到一樣的心得:花裏胡哨的問題就是客製化準備。 6. 承上:公司找你去,在意的是你的特質,專業技能都是可以後續培養。同意 ✅。 7. 不變的東西:語言能力、投資 (世界經濟)。 1. 同意 ✅,軟體工程師不變的東西大概就是: 1. 硬實力:演算法、