

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 外掛程式測試指南
<a name="sbomgen-plugin-testing-guide"></a>

 外掛程式作者完全在 Lua 中撰寫和測試 Lua 外掛程式，不需要 Go 工具鏈。測試與 下的外掛程式共置，`init_test.lua`並透過 `inspector-sbomgen plugin test`命令執行。

 如需一般外掛程式撰寫，請參閱 [外掛程式開發人員指南](sbomgen-plugin-developer-guide.md)。如需完整的函數目錄 （包括 `testing.*` API)，請參閱 [外掛程式 API 參考](sbomgen-plugin-api-reference.md)。

```
-- init_test.lua (next to init.lua)
function test_discovers_curl_version()
    local result = testing.scan_directory("_testdata/include/curl")
    testing.assert_equals(1, #result.findings)
    testing.assert_equals("libcurl", result.findings[1].name)
    testing.assert_equals("8.14.1", result.findings[1].version)
end
```

```
# Run every test under a plugin directory
inspector-sbomgen plugin test --path ./my-plugins

# Verbose output — show each test name and result
inspector-sbomgen plugin test --path ./my-plugins -v
```

## Quick Start
<a name="sbomgen-plugin-testing-guide-quick-start"></a>

### 1. 建立測試檔案
<a name="sbomgen-plugin-testing-guide-1-create-a-test-file"></a>

 放置在外掛程式的 `init_test.lua`旁`init.lua`：

```
my-plugin/
├── discovery/cross-platform/extra-ecosystems/curl/
│   ├── init.lua
│   ├── init_test.lua
│   └── _testdata/
│       └── include/curl/curlver.h
```

### 2. 寫入測試函數
<a name="sbomgen-plugin-testing-guide-2-write-test-functions"></a>

 探索並執行任何以 開頭`test_`的全域函數：

```
function test_finds_libcurl()
    local result = testing.scan_directory("_testdata/include/curl")
    testing.assert_equals(1, #result.findings)
    testing.assert_equals("libcurl", result.findings[1].name)
end

function test_no_findings_for_empty_dir()
    local result = testing.scan_directory("_testdata/empty")
    testing.assert_equals(0, #result.findings)
end
```

### 3. 執行測試
<a name="sbomgen-plugin-testing-guide-3-run-tests"></a>

```
inspector-sbomgen plugin test --path ./my-plugin
```

## 目錄配置
<a name="sbomgen-plugin-testing-guide-directory-layout"></a>

### 外掛程式結構
<a name="sbomgen-plugin-testing-guide-plugin-structure"></a>

 測試檔案和測試資料會與外掛程式共置：

```
my-plugins/
├── discovery/
│   └── cross-platform/
│       └── extra-ecosystems/
│           └── curl/
│               ├── init.lua          # plugin source
│               ├── init_test.lua     # test file
│               └── _testdata/        # test fixtures
│                   ├── include/curl/curlver.h
│                   └── binaries/unix/curl
├── collection/
│   └── cross-platform/
│       └── extra-ecosystems/
│           └── curl-installation/
│               ├── init.lua
│               └── init_test.lua
```

### 測試檔案命名
<a name="sbomgen-plugin-testing-guide-test-file-naming"></a>
+ 預設： 外掛程式的 `init_test.lua` 旁 `init.lua`
+ 每個外掛程式有多個測試檔案：`*_test.lua`發現任何檔案比對
+ 範例：`init_test.lua`、`parsing_test.lua`、`discovery_test.lua`

### 測試資料： `_testdata/`
<a name="sbomgen-plugin-testing-guide-test-data-testdata"></a>

 測試資料存在於 外掛程式`_testdata/`旁。前導底線是一種慣例，可讓固定裝置在視覺上與外掛程式來源分開；在搜尋`*_test.lua`檔案`_testdata/`時，`plugin test`命令不會遞減至 ，因此固定裝置永遠不會誤認為測試檔案。

 具有相對路徑的測試檔案參考配件：

```
local result = testing.scan_directory("_testdata/include/curl")
```

 路徑會相對於包含測試檔案的目錄解析。

## `testing.*` API
<a name="sbomgen-plugin-testing-guide-the-testing-api"></a>

### 掃描函數
<a name="sbomgen-plugin-testing-guide-scan-functions"></a>

 每個掃描函數都會建立成品、執行外掛程式的探索 → 集合管道，並傳回問題清單。測試作者永遠不會手動建立成品、事件匯流排或登錄檔。

```
-- Scan a directory of test fixtures (most common)
local result = testing.scan_directory("_testdata/curl")

-- Alias for scan_directory (archives use the same backend)
local result = testing.scan_archive("_testdata/app.tar.gz")

-- Scan as a localhost artifact
local result = testing.scan_localhost("_testdata/curl")

-- Scan a compiled binary
local result = testing.scan_binary("_testdata/binaries/curl")

-- Scan a mounted volume
local result = testing.scan_volume("_testdata/volume-root")

-- Scan a container image tarball
local result = testing.scan_container("_testdata/images/alpine.tar")
```

 每個掃描函數：
+ 為每個呼叫建立新的成品 （呼叫之間沒有狀態洩漏）
+ 僅載入目前外掛程式的探索 \+ 集合對
+ 傳回結果資料表

### 結果資料表
<a name="sbomgen-plugin-testing-guide-result-table"></a>

```
result.findings              -- array of finding tables
result.findings[1].name      -- package name (string)
result.findings[1].version   -- package version (string)
result.findings[1].purl      -- package URL (string)
result.findings[1].component_type -- component type (string)
result.findings[1].properties    -- table<string, string>
result.findings[1].children      -- array of nested finding tables (same shape)
```

### 聲明
<a name="sbomgen-plugin-testing-guide-assertions"></a>

```
-- Equality
testing.assert_equals(expected, actual, message?)
testing.assert_not_equals(expected, actual, message?)

-- Truthiness
testing.assert_true(value, message?)
testing.assert_false(value, message?)

-- Nil checks
testing.assert_nil(value, message?)
testing.assert_not_nil(value, message?)

-- String
testing.assert_contains(haystack, needle, message?)
testing.assert_matches(string, pattern, message?)

-- Tables
testing.assert_length(table, expected_length, message?)

-- Control flow
testing.fail(message)    -- immediately fail the current test
testing.skip(message)    -- skip the current test (not a failure)
```

### 標準 `sbomgen.*` API
<a name="sbomgen-plugin-testing-guide-standard-sbomgen-api"></a>

 完整的 `sbomgen.*` API （檔案 I/O、regex、系統資訊、記錄等） 可在測試檔案中使用，與生產外掛程式相同。不過，需要成品 （例如 `sbomgen.read_file()`) 的`sbomgen.*`函數只能在回`testing.scan_*`呼內運作，無法在測試函數的最上層使用。

## 執行測試
<a name="sbomgen-plugin-testing-guide-running-tests"></a>

```
# Run all tests under a plugin directory
inspector-sbomgen plugin test --path ./my-plugins

# Filter by regex pattern
inspector-sbomgen plugin test --path ./my-plugins --run curl

# Verbose output (show each test name and result)
inspector-sbomgen plugin test --path ./my-plugins -v

# Stop on the first failing test
inspector-sbomgen plugin test --path ./my-plugins --fail-fast
```

 `--path` 旗標接受外掛程式根目錄 （包含 `discovery/`和/或 `collection/`) 或單一生態系統目錄 （自動偵測）。如果任何測試失敗，命令會結束非零。

### 輸出格式
<a name="sbomgen-plugin-testing-guide-output-format"></a>

 使用 `-v`時，每個測試都會列印一`=== RUN`行和一行結果 (`--- PASS`、 `--- FAIL`或 `--- SKIP`)。如果沒有 `-v`，則只會列印失敗的測試。摘要行會在結尾列印：

```
=== RUN   curl/discovery/init_test/test_discovers_libcurl_header
--- PASS: curl/discovery/init_test/test_discovers_libcurl_header (0.04s)
=== RUN   curl/discovery/init_test/test_discovers_curl_binary_unix
--- PASS: curl/discovery/init_test/test_discovers_curl_binary_unix (0.04s)
=== RUN   curl/discovery/init_test/test_no_findings_for_unrelated_files
--- PASS: curl/discovery/init_test/test_no_findings_for_unrelated_files (0.04s)
ok    3 tests passed
```

## 測試外掛程式協助程式
<a name="sbomgen-plugin-testing-guide-testing-plugin-helpers"></a>

 測試檔案會載入與外掛程式 相同的 Lua VM`init.lua`。外掛程式中定義的全域函數可從測試呼叫。若要測試協助程式函數，請將它們公開為全域或模組資料表中：

```
-- init.lua
M = {}
function M.parse_version(raw)
    return string.match(raw, "(%d+%.%d+%.%d+)")
end

function collect(file_path)
    local ver = M.parse_version(...)
    -- ...
end
```

```
-- init_test.lua
function test_parse_version_extracts_semver()
    testing.assert_equals("1.2.3", M.parse_version("curl/1.2.3"))
end

function test_parse_version_returns_nil_for_garbage()
    testing.assert_nil(M.parse_version("not-a-version"))
end
```

 測試檔案`init.lua`看不到`local`在 中宣告的函數。這是標準 Lua 範圍。

## 行為和變異
<a name="sbomgen-plugin-testing-guide-behavior-and-invariants"></a>

### 共用 VM、共用狀態
<a name="sbomgen-plugin-testing-guide-shared-vm-shared-state"></a>

 單一檔案內的測試函數會共用 Lua VM。後續`test_*`函數可看見一個函數中設定的全域變數。如果兩個函數定義相同的全域，第二個函數會覆寫第一個函數。每個測試應該是獨立的，而不是依賴其他測試的狀態。

### 非確定性執行順序
<a name="sbomgen-plugin-testing-guide-non-deterministic-execution-order"></a>

 透過反覆使用以雜湊為基礎的排序來探索測試函數。**測試不保證按照定義的順序執行。**請勿撰寫相依於執行順序的測試。

### 每次掃描呼叫的新鮮成品
<a name="sbomgen-plugin-testing-guide-fresh-artifact-per-scan-call"></a>

 每次呼叫 `testing.scan_directory()`（或任何掃描函數） 都會建立全新的成品。在測試內的掃描呼叫之間或跨測試之間，不會傳送任何狀態。

### 外掛程式載入
<a name="sbomgen-plugin-testing-guide-plugin-loading"></a>

 每次測試執行會載入一次外掛程式，而不是每個測試檔案載入一次。測試執行器會從提供的檔案系統載入所有外掛程式，然後依階段、平台、類別和生態系統將每個測試檔案與其對應的外掛程式 VM 配對。

### 聲明行為
<a name="sbomgen-plugin-testing-guide-assertion-behavior"></a>

 當宣告失敗時，會記錄失敗，但測試函數會繼續執行 - 後續宣告和陳述式仍會執行。如果同一個測試中有多個宣告失敗，**最近的**失敗是摘要中報告的訊息；先前的失敗會被覆寫。若要在第一次失敗時停止測試，請在失敗宣告後從函數傳回 （或在條件式`testing.fail()`中使用）。

## 限制
<a name="sbomgen-plugin-testing-guide-limitations"></a>
+ **`local` 函數無法測試。**只有來自 的全域函數`init.lua`會顯示在測試檔案中。如果需要測試，請透過模組資料表公開協助程式。
+ **掃描呼叫外沒有`sbomgen.*`檔案 I/O。**像 的函數`sbomgen.read_file()`需要成品內容，它只存在於`testing.scan_*`呼叫中。
+ **無生命週期關聯。**沒有 `before_each`、`setup`、 `after_each`或 `teardown`。每個測試函數都會管理自己的狀態。
+ **沒有測試逾時。**永遠循環的測試函數會掛起執行器。
+ **無涵蓋範圍報告。**無法測量`init.lua`已執行哪些 行。
+ **無基準。**測試架構不支援效能基準。

## 開發人員責任
<a name="sbomgen-plugin-testing-guide-developer-responsibilities"></a>

### 寫入新的 Lua 外掛程式時
<a name="sbomgen-plugin-testing-guide-when-writing-a-new-lua-plugin"></a>
+ 在 `init_test.lua`旁邊建立 `init.lua`
+ `_testdata/` 使用最少的固定裝置來建立 ，以練習外掛程式的邏輯
+ 撰寫`test_*`函數，涵蓋：成功偵測、版本擷取、邊緣案例和不相符案例
+ 在提交之前於本機執行測試

### 測試資料準則
<a name="sbomgen-plugin-testing-guide-test-data-guidelines"></a>
+ 將固定裝置降至最低 - 使用可練習行為的最小檔案
+ 當小型文字固定裝置足夠`_testdata/`時，避免將大型二進位檔遞交給
+ 每個外掛程式的 `_testdata/`應該是獨立的 - 不參考外掛程式目錄外部的檔案