

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 插件测试指南
<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
```

## 快速入门
<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/`旁边。前导下划线是一种约定，它使灯具在视觉上与插件源分开；搜索文件`_testdata/`时不会使用该`plugin test`命令，因此永远不会将灯具误认为是测试`*_test.lua`文件。

 测试文件引用带有相对路径的灯具：

```
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")
```

 每种扫描功能：
+ 为每个调用创建一个新的构件（调用之间没有状态泄漏）
+ 仅加载当前插件的 discovery \+ 集合对
+ 返回结果表

### 结果表
<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、正则表达式、系统信息、日志等）可在测试文件中使用，与生产插件相同。但是，需要工件的`sbomgen.*`函数（例如`sbomgen.read_file()`）只能在`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/` and/or `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 虚拟机中。`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
```

 `local`中声明`init.lua`的函数对测试文件不可见。这是标准的 Lua 作用域。

## 行为和不变量
<a name="sbomgen-plugin-testing-guide-behavior-and-invariants"></a>

### 共享虚拟机，共享状态
<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>

 测试函数是通过迭代 Lua 的全局表来发现的，该全局表使用基于哈希的排序。**不能保证测试会按照定义的顺序运行。**不要编写依赖于执行顺序的测试。

### 每次扫描调用都会有新的伪像
<a name="sbomgen-plugin-testing-guide-fresh-artifact-per-scan-call"></a>

 每次调用`testing.scan_directory()`（或任何扫描函数）都会创建一个全新的构件。在测试中的扫描调用之间或测试之间没有状态传输。

### 插件加载
<a name="sbomgen-plugin-testing-guide-plugin-loading"></a>

 插件每次测试运行加载一次，而不是每个测试文件加载一次。测试运行器从提供的文件系统加载所有插件，然后按阶段、平台、类别和生态系统将每个测试文件与其对应的插件虚拟机进行匹配。

### 断言行为
<a name="sbomgen-plugin-testing-guide-assertion-behavior"></a>

 当断言失败时，会记录失败，但测试函数会继续执行——后续的断言和语句仍会运行。如果在同一个测试中多个断言失败，则**最近的**失败是摘要中报告的消息；先前的失败将被覆盖。要在第一次失败时停止测试，请在断言失败后从函数返回（或`testing.fail()`在条件中使用）。

## 限制
<a name="sbomgen-plugin-testing-guide-limitations"></a>
+ **`local`函数不可测试。**只有中的`init.lua`全局函数对测试文件可见。如果助手需要测试，请通过模块表公开它们。
+ **扫描呼叫 I/O 之外没有`sbomgen.*`文件。**诸如此类的函数`sbomgen.read_file()`需要一个工件上下文，该上下文仅存在于`testing.scan_*`调用中。
+ **没有生命周期挂钩。**没有`before_each`、`after_each``setup`、或`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/`应该是独立的 —— 不得引用插件目录之外的文件