SpecFormula AI
ISA後端指令

EntitySetup

以宣告式語法準備測試資料

EntitySetup 讓你直接在 Gherkin 中宣告測試的前置資料,無需在 Step Definition 中撰寫 INSERT 語句。

基本用法

Given 準備一個使用者, with table:
  | >Alice.id | name  | email             | status |
  | <userId   | Alice | alice@example.com | ACTIVE |

這段指令會:

  1. 透過 Entity Spec 將「使用者」對應到 users 資料表,並取得欄位定義
  2. 新增一筆 name = Aliceemail = alice@example.comstatus = ACTIVE 的資料寫入資料庫
  3. 從資料庫反查 user_id 欄位值,存入變數 Alice.id

變數擷取

INSERT 後,可透過 >< 配對從資料庫反查自動產生的欄位值(如 auto-increment ID),並存入變數供後續步驟使用:

  • >variableName(header row):定義變數名稱,用於儲存擷取的值
  • <columnName(data row):指定要從資料庫反查的欄位名稱
Given 準備一個使用者, with table:
  | >Alice.id | >Alice.createdAt | name  | email             | status |
  | <userId   | <createdAt       | Alice | alice@example.com | ACTIVE |

執行流程:

  1. nameemailstatus 寫入資料庫
  2. 從資料庫反查 userId 欄位值 → 存入變數 Alice.id
  3. 從資料庫反查 createdAt 欄位值 → 存入變數 Alice.createdAt

引用變數

使用 $variable 引用先前儲存的變數:

Given 準備一個使用者, with table:
  | >Alice.id | name  |
  | <userId   | Alice |

Given 準備一個待辦事項, with table:
  | userId    | title  |
  | $Alice.id | 買牛奶 |

JSON 欄位描述

當 Entity 欄位儲存 JSON 物件(如 PostgreSQL JSONB、MySQL JSON)時,提供兩種方式描述 JSON 欄位內容。

JSON Path DataTable Header

在 DataTable header 使用路徑語法展開 JSON 結構:

Given 準備一個訂單, with table:
  | id | orderItems[0].productId | orderItems[0].quantity | orderItems[1].productId | orderItems[1].quantity |
  | 1  | 5                       | 2                      | 6                       | 10                     |

等同於壓縮為單行字串的寫法:

Given 準備一個訂單, with table:
  | id | orderItems                                                   |
  | 1  | [{"productId":5,"quantity":2},{"productId":6,"quantity":10}] |

搭配 Symbol System:

Given 準備一個訂單, with table:
  | id | orderItems[0].productId | orderItems[0].createdAt |
  | 1  | $productId              | @time(now)              |

JSON DocString 格式

使用 with json: 搭配 """json ... """ 直接撰寫結構化 JSON,適合深層巢狀結構:

Given 準備一個訂單, with json:
  """json
  {
    "id": 1,
    "orderItems": [
      { "productId": 5, "quantity": 2 },
      { "productId": 6, "quantity": 10 }
    ]
  }
  """

多筆資料使用 JSON Array:

Given 準備一個訂單明細, with json:
  """json
  [
    { "orderId": 1, "productId": 101, "quantity": 2 },
    { "orderId": 1, "productId": 102, "quantity": 1 }
  ]
  """

搭配 Symbol System:

Given 準備一個訂單, with json:
  """json
  {
    >orderId: <id,
    "orderNo": "ORD-001",
    "createdAt": @time(now),
    "orderItems": [
      { "productId": $productId, "quantity": 2 }
    ]
  }
  """

JSON 格式注意事項:符號系統的表達式($變數、&CAS 約束、@時間符號、>/<擷取鍵)不可放在 JSON 字串引號 "" 內,否則會被視為純字串。

Key 含 . 的逃脫語法

當 JSON key 本身包含 . 字元時,使用 ["..."] bracket notation 避免被誤拆為多層巢狀:

Given 準備一個設定, with table:
  | id | content.["btn.save"] | content.["btn.cancel"] |
  | 1  | 儲存                  | 取消                    |

JSON DocString 中直接使用原始 key,框架自動處理逃脫。


與 Entity Spec 的對應關係

EntitySetup 指令透過 Entity Spec 將業務實體名稱對應到實際的資料表,並根據 DDL 驗證欄位是否存在。

對應機制

# entity_to_table_mapping.yml
entity_to_table_mapping:
  - 使用者: users
  - 待辦事項: todos
  - 訂單: orders
  - 商品: products
-- schema.sql
CREATE TABLE users (
    user_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE,
    status VARCHAR(20) DEFAULT 'ACTIVE',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Given 準備一個使用者, with table:
  | name  | email             |
  | Alice | alice@example.com |

# SpecFormula 自動對應到 users 資料表
# INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')
Gherkin 元素對應來源
使用者entity_to_table_mapping 的 key
users 資料表entity_to_table_mapping 的 value
name, email 欄位DDL 欄位定義

命名規則

Entity 名稱:使用業務團隊熟悉的中文或英文名稱

# ✅ 推薦:業務語言
entity_to_table_mapping:
  - 會員: members
  - 訂單: orders
  - 商品: products

# ❌ 避免:技術名稱
entity_to_table_mapping:
  - member_entity: members
  - tbl_order: orders

欄位名稱轉換:DataTable 使用 camelCase,自動對應資料庫的 snake_case

Gherkin: userId    → DB: user_id
Gherkin: createdAt → DB: created_at

在查詢或驗證資料庫欄位時,框架同樣會對每一個欄位名稱套用三層匹配邏輯:

  • 第一層:精確匹配
    • 先用原始欄位名稱直接找,例如 userIduserId
  • 第二層:camelCase → snake_case
    • 若找不到精確匹配,會將 userId 轉成 user_id,再嘗試匹配 DDL / 實際資料的欄位名稱。
  • 第三層:忽略大小寫
    • 若前兩層都沒找到,最後會以「忽略大小寫」的方式比對,例如 user_idUSER_IDUser_Id 都會被視為同一個欄位。
Feature 欄位名稱DDL / Map 欄位名稱是否匹配說明
userIduserId✅ 是第一層:直接 equals 匹配
userIduser_id✅ 是第二層:camelCase → snake_case
userIdUSER_ID✅ 是第二層轉成 user_id 後,再第三層 equalsIgnoreCase 匹配

實務上只要在 Spec 中統一使用 camelCase,無論 DDL 是 snake_case 還是慣用全大寫命名,都能被穩定解析,避免在 SQL/程式碼裡手動處理大小寫與底線差異。

Schema 驗證

SpecFormula 會根據 DDL 驗證 DataTable 中的欄位是否存在:

# ✅ 正確:欄位存在於 DDL
Given 準備一個使用者, with table:
  | name  | email             | status |
  | Alice | alice@example.com | ACTIVE |

# ❌ Lint 錯誤:欄位不存在
Given 準備一個使用者, with table:
  | name  | emailAddress      |    # emailAddress 不存在,應為 email
  | Alice | alice@example.com |

為什麼不用 API 建立前置資料?

透過 API 建立前置資料會造成測試耦合——如果「建立使用者」API 有 bug,所有依賴該使用者的測試都會連帶失敗,即使被測功能完全正確。

# ❌ 用 API 建立:建立使用者 API 有 bug 會影響所有測試
When (No Actor) 建立使用者, call table:
  | name  | email             |
  | Alice | alice@example.com |

# ✅ EntitySetup:直接插入資料庫,不受 API 影響
Given 準備一個使用者, with table:
  | name  | email             |
  | Alice | alice@example.com |

型別轉換

DataTable 中的值皆為字串,框架會根據 DDL 中對應欄位的型別定義(如 INTDATEDATETIME)自動將字串轉為正確的資料庫型別:

DDL 欄位型別DataTable 輸入轉換結果
INT / BIGINT"123"Integer / Long
DECIMAL"99.99"BigDecimal
DATE"2026-01-27"LocalDate
DATETIME / TIMESTAMP"2026-01-27T10:00:00"LocalDateTime
DATETIME / TIMESTAMP@time("now")當前 Mock 時間
DATE@date("now")當前 Mock 日期

錯誤處理

Lint 階段

錯誤說明
Entity 未定義entity_to_table_mapping 中找不到對應的 entity
欄位不存在DataTable 中的欄位在資料表定義中找不到

執行階段

錯誤說明
INSERT 失敗資料庫約束違反(唯一鍵衝突、NOT NULL 等)

完整範例

Feature: 新增待辦事項

  Background:
    Given 現在時間為 "2026-01-27T10:00:00"

    # EntitySetup 指令 — 準備使用者資料
    # >Alice.id / <userId:INSERT 後反查 userId 存入 Alice.id
    Given 準備一個使用者, with table:
      | >Alice.id | name  | email             | passwordHash | status |
      | <userId   | Alice | alice@example.com | password123  | ACTIVE |

  Example: 新增一筆待辦事項

    # ApiCall 指令(參見 ApiCall 文件)
    When (UID="$Alice.id") 新增待辦事項, call table:
      | >todo1.id | title  |
      | <todoId   | 買牛奶 |

    # ResponseValidate 指令(參見 ResponseValidate 文件)
    Then 新增待辦事項(201)回應, with table:
      | todoId    | userId    | title  |
      | $todo1.id | $Alice.id | 買牛奶 |

目錄