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.05.01 加密貨幣行情看法

目前狀況:撰寫時間 BTC 價位約 57000 USD,從最高點 73500 發生 22.5% 的回撤。 個人認為這波在 K 線型態上以及 X (Titter) 的情緒上,跟過往到 69000 甚至下殺到 16000 沒有什麼不同,不同的是自己開始寫文章了,所以就記錄下對於當下市場的看法。 短期看法 正常的回調 從 02/26 花了 3 天時間跳過了 50000 的區間直達 60000,又在 10 天內到達 73500,加上減半 & 美國 ETF 通過的情緒在這短短 2 個月的時間發生,市場情緒高漲下,整個幣圈都覺得要破 10

網格交易中鮮少被討論到的交易次數 Part 2

延續 Part 1 幣本位合約網格 (進階) ⚠️ 風險高。適合長期看好,想要囤幣的策略。 當時心想:BTC 不像 ETH 圖靈完備可以應用在智能合約及各種衍生金融品,所以購買了 BTC 後,還能使用何種方法使幣增加 (或減少...)? 先談風險,再談如何應對: 1. 同 U 本位合約網格風險:既要扛資金費率,又要小心爆倉歸 0。 2. 跌起來比 U 本位還慘:都是跌 10%,因為以 BTC 計價,資產價值也連帶受到影響。舉例說明:BTC/USDT 60000,U 本位跌 10% 仍有 54000,開設 1 BTC 的合約網格,

網格交易中鮮少被討論到的交易次數 Part 1

網格交易是一個很好的策略,各大 YouTuber 或部落客都有再介紹與推薦,自己運行了 2 年多,現貨 / U 本位合約 / 幣本位合約網格都有嘗試,儘管都是使用 BTC ETH 交易對,但山寨幣也是相同道理。 通則 開網格前需思考:要放多長? 個人因為很懶的每天盯盤與過多人為介入的交易,所以希望開了網格後都至少可以放個半年甚至 1 年以上,走出一個大週期後再思考後續方向。我在 2023/05/03 開了一個網格,設定區間 24000 - 48000 的 149 格 (每格利潤 0.42%),維持了約 7 個月的紀錄。 每格利潤若抓 0.4x%,日均最好能到 8 次交易以上 網格設定過密集會被手續費吃掉獲利,太寬鬆則成交次數不高而讓資金利用率低,