Posted in

【Golang测试报告工业化标准】:基于Test262兼容性验证的权威报告规范(附GitHub Star 2.4k开源工具链)

第一章:Golang自动化测试报告的工业化演进与核心价值

早期Go项目常依赖go test -v的原始输出,信息零散、不可追溯、难以集成。随着CI/CD流水线普及和质量门禁要求提升,测试报告从“能跑通”迈向“可度量、可审计、可归因”的工业化阶段——这不仅是格式升级,更是质量治理范式的重构。

测试报告的三重演进阶段

  • 手工解析期:开发者手动截取终端日志,用Excel统计失败用例;缺乏时间戳、环境标识与版本关联
  • 结构化输出期:借助go test -json生成机器可读流式事件,每条记录含Time、Action(pass/fail/benchmark)、Test、Elapsed等字段
  • 平台协同期:JSON流经jq或专用工具(如gotestsum)转换为HTML/JUnit XML,无缝对接Jenkins、GitLab CI及SonarQube

标准化报告生成实践

使用gotestsum替代原生go test可一键生成多格式报告:

# 安装并运行,同时输出控制台摘要 + HTML报告 + JUnit兼容XML
go install gotest.tools/gotestsum@latest
gotestsum --format testname \
  -- -race -timeout 30s \
  -- -o ./report.html \
  -- -xml ./junit.xml

该命令执行逻辑:先运行所有测试(启用竞态检测),实时渲染简洁测试名列表;完成后自动生成带CSS样式HTML报告,并输出符合JUnit Schema的XML供CI系统消费。

工业化报告的核心价值维度

维度 传统输出 工业化报告
可追溯性 仅含当前运行结果 关联Git commit、Go version、OS环境变量
协作效率 开发者需人工定位失败点 点击HTML报告中失败用例直接跳转源码行号
质量度量 仅有通过率 内置耗时分布图、历史趋势对比、慢测试告警

高质量测试报告已成为Go工程交付物的标配组件,其价值不仅在于呈现结果,更在于将测试行为转化为可沉淀、可分析、可驱动改进的质量数据资产。

第二章:Test262兼容性验证体系在Go生态中的落地实践

2.1 Test262标准解析与Go语言运行时语义映射理论

Test262 是 ECMAScript 官方合规性测试套件,其用例以 .js 文件形式定义断言、预期行为及执行上下文。将其实现语义映射到 Go 运行时需解决三类核心问题:词法/语法解析差异、抽象操作(Abstract Operations)的 Go 实现等价性、以及严格模式与 Promise 微任务队列的调度对齐。

映射关键维度

  • 执行上下文建模:Go 中用 *runtime.Context 结构体封装 LexicalEnvironment、VariableEnvironment 等字段
  • 抽象操作桥接:如 OrdinaryGetPrototypeOf(O) → js.Object.GetPrototype() 方法封装
  • 错误传播机制:ECMAScript 的 ThrowCompletionRecord 映射为 Go 的 error + recover() 组合

典型测试用例映射示例

// test262/test/built-ins/Array/prototype/slice/S15.4.4.10_A1_T1.js 对应 Go 运行时实现
func (a *Array) Slice(start, end int) *Array {
    if start < 0 { start = max(0, len(a.elements)+start) } // 符合 ES §15.4.4.10 step 3.a
    if end < 0 { end = max(0, len(a.elements)+end) }
    return NewArray(a.elements[start:end]...) // 语义等价于 CreateArrayFromList
}

该实现严格遵循 Test262 第 15.4.4.10 节规范步骤:先标准化索引(含负数处理),再切片构造新数组,确保 Array.prototype.slice 在 Go 引擎中行为与 V8/SpiderMonkey 一致。

规范要素 Go 运行时实现方式 合规性保障机制
Completion Record Result{Value: v, Err: err} 通过结构体显式携带类型与异常
Job Queue runtime.jobQueue.Push(&promiseJob) 基于 channel 的 FIFO 微任务调度器
graph TD
    A[Test262 TestCase] --> B[Parser: AST with directive context]
    B --> C[Semantic Mapper: JS→Go type binding]
    C --> D[Runtime Executor: goroutine-per-job + panic-recover]
    D --> E[Assertion Checker: deepEqual vs. expected result]

2.2 go-test262工具链架构设计与跨平台验证流程实现

go-test262 是一个面向 ECMAScript 标准兼容性验证的 Go 原生工具链,核心采用分层架构:parser → runner → reporter → adapter。

架构分层职责

  • Parser:解析 test262 测试用例(.js + .html + metadata),提取 flags, includes, esid 等元信息
  • Runner:基于 goja(ES5.1+)与 otto(ES5.1)双引擎沙箱执行,支持 --engine=v8 外部进程模式
  • Reporter:统一输出 JSON Schema 兼容的 TestResult,含 status, duration_ms, error_stack
  • Adapter:提供 linux/amd64, darwin/arm64, windows/386 平台专用 shim 层,处理信号、时区、文件路径归一化

跨平台验证流程

// runner/executor.go
func (e *Executor) Run(ctx context.Context, tc TestCase) (Result, error) {
    // 设置平台无关的 sandbox 约束
    opts := []goja.ExecOptions{
        goja.WithTimeout(30 * time.Second),
        goja.WithMaxHeap(64 << 20), // 64MB
        goja.WithFSAdapter(e.fsAdapter), // 关键:注入平台适配器
    }
    return e.engine.EvalWithContext(ctx, tc.Source, opts...)
}

该调用通过 fsAdapter 将 /test/xxx.js 映射为 C:\test\xxx.js(Windows)或 /private/var/...(macOS),确保路径语义一致。WithTimeout 和 WithMaxHeap 防止平台差异导致的 hang 或 OOM。

验证策略对比

平台 引擎模式 启动延迟 内存开销 支持 strict mode
linux/amd64 goja embedded 12ms ~18MB ✅
darwin/arm64 otto embedded 28ms ~22MB ❌
windows/386 v8 subprocess 156ms ~94MB ✅
graph TD
    A[Parse Test Suite] --> B{Platform Detect}
    B -->|linux| C[goja + cgroup limit]
    B -->|darwin| D[otto + sandbox exec]
    B -->|windows| E[v8.exe IPC pipe]
    C --> F[Normalize Result]
    D --> F
    E --> F
    F --> G[Aggregate to CI Dashboard]

2.3 测试用例自动裁剪与覆盖率驱动的精简策略实践

传统全量回归测试在持续交付中成为瓶颈。我们引入基于插桩覆盖率反馈的动态裁剪机制,仅保留触发变更代码路径的最小用例集。

裁剪核心流程

def prune_test_suite(cov_report, test_inventory):
    # cov_report: {file_path: {line_nums: [hit_count]}}
    # test_inventory: [{"name": "test_login_200", "lines_covered": {"/auth.py": {12, 15}}}]
    critical_lines = set().union(*[set(l) for l in cov_report.values()])
    return [t for t in test_inventory if critical_lines & t["lines_covered"].get("/auth.py", set())]

该函数通过交集运算快速筛选覆盖变更行的测试,避免执行未受影响用例;critical_lines来自CI构建时AST差异分析结果,确保裁剪粒度精确到行级。

覆盖率驱动阈值配置

策略类型 行覆盖率阈值 用例保留率 适用场景
严格模式 ≥95% ~38% 核心支付模块
平衡模式 ≥80% ~62% 用户中心服务

执行流程

graph TD
    A[CI触发] --> B[代码差异分析]
    B --> C[运行轻量探针获取增量覆盖率]
    C --> D[匹配测试用例依赖图]
    D --> E[裁剪并并发执行]

2.4 多版本Go runtime兼容性矩阵生成与Delta分析方法

为精准识别Go各版本间runtime行为差异,需构建结构化兼容性矩阵。核心依赖go tool compile -S与runtime/internal/sys符号导出比对。

Delta分析流程

# 提取两版本runtime符号哈希(含GOOS/GOARCH约束)
go1.21.0/bin/go tool compile -S std -o /dev/null 2>&1 | \
  grep -E 'call.*runtime\.' | sha256sum > v1.21.symbols.sha

该命令捕获汇编级调用链,排除编译器优化干扰;-S输出确保符号可见性,std包覆盖核心runtime路径。

兼容性维度矩阵

维度 检查项 差异敏感度
GC触发逻辑 gcStart, gcMarkDone 高
Goroutine调度 newproc, gopark 中高
内存分配器 mallocgc, mcache.next 高

自动化比对流程

graph TD
    A[提取v1/v2 runtime符号] --> B[计算SHA256指纹]
    B --> C[生成笛卡尔积差异表]
    C --> D[标记breaking-change行]

关键参数:GOOS=linux GOARCH=amd64保障环境一致性;-gcflags="-l"禁用内联以暴露真实调用点。

2.5 工业级报告元数据建模:从原始日志到结构化指标的转换实践

工业场景中,原始日志(如设备心跳、PLC采样、告警事件)需映射为可聚合、可溯源的指标体系。核心在于定义元数据契约——统一描述字段语义、单位、时效性与血缘关系。

元数据模型关键维度

  • metric_id:全局唯一指标标识(如 temp_sensor_01_avg_1m)
  • source_path:原始日志路径模板(/logs/machine-{id}/sensor/*.log)
  • extraction_rule:正则或JSONPath提取逻辑
  • aggregation_window:1m / 5m / 1h

日志解析规则示例

# 从半结构化日志提取温度均值指标
import re
log_line = "[2024-03-15T08:22:17] DEV-007 TEMP=42.3°C HUMID=65%"
match = re.search(r"TEMP=([\d.]+)°C", log_line)
if match:
    value = float(match.group(1))  # 提取数值:42.3
    # → 输出指标元组:(metric_id, timestamp, value, tags)

该正则精准捕获温度值,避免误匹配湿度等干扰字段;float() 强制类型校验保障下游计算可靠性。

元数据注册表(简化)

metric_id unit granularity source_system
temp_sensor_01_avg_1m °C 1m PLC-SCADA
alarm_count_5m cnt 5m MES
graph TD
    A[原始日志流] --> B{元数据路由引擎}
    B --> C[按metric_id分发]
    C --> D[时序数据库写入]
    C --> E[指标血缘快照]

第三章:权威测试报告规范的设计原理与合规性保障

3.1 ISO/IEC/IEEE 29119-4测试报告框架在Go CI/CD中的适配模型

ISO/IEC/IEEE 29119-4 定义了结构化测试报告的元数据要素(如 testExecutionDate、testEnvironmentID、defectDensity),需映射至 Go 构建上下文与 CI/CD 流水线生命周期。

数据同步机制

通过 go test -json 输出流式事件,经自定义解析器注入标准化字段:

// 将 go test -json 输出转换为 29119-4 兼容的 TestResultEntry
type TestResultEntry struct {
    TestCaseID     string    `json:"testCaseID"`     // 映射至 29119-4 的 testItemIdentifier
    ExecutionTime  time.Time `json:"executionTime"`  // 对应 testExecutionDate
    EnvironmentRef string    `json:"envRef"`         // 对应 testEnvironmentID(如 "gke-prod-v1.28")
}

该结构确保每条测试记录携带标准元数据,支持下游报告聚合与合规审计。

关键字段映射表

29119-4 字段 Go CI/CD 实现方式 来源
testExecutionDate time.Now().UTC() 测试启动时戳
testEnvironmentID $CI_ENVIRONMENT_SLUG 或 GOOS/GOARCH CI 环境变量或构建标签

流程集成

graph TD
    A[go test -json] --> B[Parse & enrich with 29119-4 metadata]
    B --> C[Serialize to JSON-LD report]
    C --> D[Upload to Test Reporting Service]

3.2 报告可信度三要素:可复现性、可审计性、可比性工程实践

可信报告不是结果,而是系统性工程产出。三者构成闭环验证链:可复现性确保相同输入必得相同输出;可审计性提供全路径溯源能力;可比性则建立跨环境/版本/团队的标准化锚点。

数据同步机制

为保障可复现性,需固化数据快照与环境指纹:

# 生成带哈希与时间戳的可重现数据包
tar -czf report-data-$(date +%Y%m%d-%H%M%S)-$(sha256sum dataset.csv | cut -d' ' -f1).tar.gz dataset.csv config.yaml

逻辑分析:sha256sum 提取数据唯一指纹,date 注入确定性时间戳,二者共同构成不可篡改的复现标识符;.tar.gz 封装确保二进制一致性,规避路径/时区等隐式依赖。

审计追踪设计

采用结构化日志+元数据标签实现可审计性:

字段 类型 说明
run_id UUID 全局唯一执行会话标识
input_hash string 输入数据 SHA256 值
env_hash string Docker image digest 或 Conda lock hash

可比性基准对齐

通过标准化指标命名与单位统一支撑横向对比:

# 指标注册表(强制单位与语义约束)
METRICS_SCHEMA = {
    "latency_p95_ms": {"unit": "ms", "semantic": "end-to-end processing time"},
    "throughput_eps": {"unit": "events/sec", "semantic": "sustained ingestion rate"}
}

参数说明:unit 防止单位混淆(如 ms vs s),semantic 约束业务含义,避免同名异义(如“latency”在不同模块指代网络/计算/序列化延迟)。

graph TD A[原始数据] –> B[带哈希快照] B –> C[容器化执行环境] C –> D[结构化审计日志] D –> E[标准化指标输出] E –> F[跨报告自动比对]

3.3 基于OpenTracing与OpenTelemetry的测试执行链路追踪集成

追踪能力演进:从OpenTracing到OpenTelemetry

OpenTracing(已归档)提供统一API但缺乏指标/日志融合能力;OpenTelemetry(OTel)作为CNCF毕业项目,统一规范并支持Trace、Metrics、Logs三者关联。

SDK迁移关键适配点

  • Tracer接口兼容性需桥接(如opentracing-to-opentelemetry转换器)
  • 上下文传播格式保持W3C TraceContext兼容
  • 测试框架(JUnit/TestNG)需注入OpenTelemetrySdk实例

示例:JUnit5中注入追踪器

@BeforeEach
void setupTracing() {
    OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
        .setTracerProvider(TracerProvider.builder()
            .addSpanProcessor(BatchSpanProcessor.builder(
                OtlpGrpcSpanExporter.builder()
                    .setEndpoint("http://localhost:4317") // OTLP gRPC端点
                    .build())
                .build())
            .build())
        .build();
    GlobalOpenTelemetry.set(openTelemetry);
}

此代码初始化OTel SDK并配置gRPC导出器:setEndpoint指定Collector地址;BatchSpanProcessor保障吞吐与可靠性;GlobalOpenTelemetry.set()使测试上下文自动继承全局追踪器。

追踪数据结构对比

维度 OpenTracing OpenTelemetry
上下文传播 TextMapCarrier Context, Propagation
Span生命周期 手动start/finish 自动scope管理(try-with-resources)
语义约定 社区约定 官方Semantic Conventions
graph TD
    A[测试用例执行] --> B[创建Span]
    B --> C{是否启用OTel?}
    C -->|是| D[注入TraceID/ParentID]
    C -->|否| E[跳过追踪]
    D --> F[上报至OTLP Collector]
    F --> G[可视化分析平台]

第四章:GitHub Star 2.4k开源工具链深度应用指南

4.1 reportgen-cli:声明式报告模板引擎与YAML Schema驱动实践

reportgen-cli 是一个轻量级命令行工具,将 YAML 声明式配置直接编译为结构化 PDF/HTML 报告,核心依赖于可扩展的 Schema 驱动验证机制。

核心设计理念

  • 声明优先:用户仅定义“要什么”,而非“如何生成”
  • Schema 即契约:YAML 模板需通过 JSON Schema 校验,保障字段语义一致性
  • 模板即代码:支持 Jinja2 表达式嵌入,实现动态段落渲染

典型工作流

# report.yaml
title: "Q3销售分析"
data_source: postgres://report:pwd@db/report_db
sections:
  - type: chart
    name: revenue-by-region
    query: "SELECT region, SUM(amount) FROM sales GROUP BY region;"

该配置经 reportgen-cli --schema schema/report.schema.json --template report.yaml --output report.pdf 执行后,自动完成数据拉取、Schema 校验、模板渲染与 PDF 合成。其中 --schema 参数指定校验规则,确保 sections[*].type 必须为预定义枚举值(如 chart, table, text)。

Schema 约束示例(关键字段)

字段 类型 必填 说明
title string ✓ 报告主标题,长度 ≤ 100 字符
data_source string ✗ 若为空则跳过数据查询阶段
sections[].type enum ✓ 限定为 chart/table/text
graph TD
  A[YAML 输入] --> B{Schema 校验}
  B -->|通过| C[数据提取]
  B -->|失败| D[报错退出]
  C --> E[模板渲染]
  E --> F[PDF/HTML 输出]

4.2 diff-reporter:多环境/多版本测试结果智能比对与回归根因定位

核心能力设计

diff-reporter 以测试用例粒度为锚点,自动对齐不同环境(dev/staging/prod)或版本(v1.2.0/v1.3.0)的执行结果,识别行为漂移与状态回退。

数据同步机制

采用增量快照 + 时间戳水位线保障跨环境数据一致性:

# config.yaml 示例
sync:
  source: "junit-xml://ci-artifacts/dev/latest/"
  target: "junit-xml://ci-artifacts/prod/v1.3.0/"
  watermark_key: "build_timestamp"  # 确保比对基线时间对齐

该配置驱动 reporter 拉取两套结果集,并按 test_id + suite_name 双键哈希归一化,规避路径/命名差异干扰。

差异归因流程

graph TD
  A[原始测试报告] --> B[标准化解析]
  B --> C[维度对齐:status/duration/stacktrace]
  C --> D[语义差异检测]
  D --> E[根因标签:env-config/dependency/bugfix]

常见回归模式识别表

模式类型 触发条件 定位线索
环境配置漂移 仅 dev 失败,prod 成功 检查 env vars、DB connection URL
依赖版本冲突 v1.3.0 新增失败,v1.2.0 正常 对比 pom.xml / package-lock.json
修复引入副作用 某用例修复后,关联用例开始失败 分析 commit diff 中的 shared module

4.3 web-report-server:实时可视化看板与RBAC权限管控部署实践

核心架构设计

web-report-server 基于 Spring Boot 3.x + Vue 3 + Ant Design Pro 构建,采用前后端分离架构,通过 WebSocket 实现毫秒级数据推送。

RBAC 权限模型落地

# application.yml 中的权限策略配置
security:
  rbac:
    enabled: true
    admin-roles: ["SUPER_ADMIN", "REPORT_ADMIN"]
    permission-cache-ttl: 300 # 秒级缓存,降低鉴权延迟

该配置启用动态权限校验,permission-cache-ttl 控制角色-权限映射缓存生命周期,避免高频 DB 查询;admin-roles 明确系统特权角色白名单,保障敏感操作(如看板导出、SQL 模板编辑)仅限授权角色触发。

实时看板数据流

graph TD
  A[MySQL Binlog] -->|Debezium| B[Kafka Topic]
  B --> C[web-report-server Consumer]
  C --> D[内存计算引擎 Flink SQL]
  D --> E[Redis Pub/Sub]
  E --> F[Vue 前端 WebSocket 监听]

权限与资源映射关系

资源类型 操作权限 可分配角色
看板管理 CREATE/READ/UPDATE REPORT_ADMIN
数据源配置 READ/UPDATE SUPER_ADMIN
用户行为日志 READ AUDIT_ANALYST

4.4 plugin-sdk:自定义指标采集器开发与CI流水线原生嵌入方案

plugin-sdk 提供轻量级 Go 接口规范,支持开发者以插件形式注入指标采集逻辑,无需修改核心监控系统。

数据同步机制

采集器通过 Collector 接口实现 Collect() 方法,返回 []prometheus.Metric:

func (c *MyPlugin) Collect() []prometheus.Metric {
    val := getCustomLatency() // 自定义业务延迟采样
    return []prometheus.Metric{
        prometheus.MustNewConstMetric(
            latencyDesc, prometheus.GaugeValue, val,
            "service_a", "v2.3.1", // label values
        ),
    }
}

latencyDesc 需预先注册,含 name、help 和 labels;GaugeValue 表示瞬时值;标签键顺序必须与 prometheus.NewDesc 中定义一致。

CI嵌入流程

插件构建产物(.so)在 CI 中自动签名并推送至制品库:

阶段 工具链 输出物
build go build -buildmode=plugin latency_plugin.so
verify plugin-sdk verify --strict 签名哈希与 ABI 兼容性报告
deploy Argo CD + Helm 注册至 Prometheus ServiceMonitor
graph TD
    A[CI Pipeline] --> B[Build .so]
    B --> C[ABI Check & Sign]
    C --> D[Push to Nexus]
    D --> E[Auto-reload in Cluster]

第五章:面向云原生时代的Golang测试报告范式升级路径

从JUnit XML到Cloud-Native Test Report Schema

传统Go测试输出(go test -v -json)生成的JSON流虽结构清晰,但缺乏跨平台可观测性语义。某金融级API网关项目在迁入Kubernetes集群后,发现CI/CD流水线中Jenkins无法原生解析-json输出中的Pod上下文、Sidecar注入状态及服务网格版本信息。团队基于CNCF TestGrid规范扩展了自定义Reporter,将testing.T生命周期事件与OpenTelemetry trace ID绑定,并注入k8s.pod.name、istio.version等标签字段。以下为增强型测试事件片段:

{
  "time": "2024-06-12T08:23:45.123Z",
  "name": "TestAuthMiddleware_WithValidToken",
  "status": "PASS",
  "k8s": { "pod": "auth-tester-7b8f9c4d-xqz2p", "namespace": "ci-staging" },
  "istio": { "version": "1.21.3", "sidecar": "enabled" }
}

多维度测试结果聚合看板

云原生环境要求测试报告同时反映代码质量、基础设施稳定性与服务依赖健康度。某电商订单微服务采用Prometheus+Grafana构建测试仪表盘,关键指标包括:

  • go_test_duration_seconds{job="order-service", phase="unit"}(单元测试耗时P95)
  • test_failure_rate{service="payment", env="prod-canary"}(生产灰度环境失败率)
  • dependency_latency_ms{upstream="user-service", status="timeout"}(下游依赖超时次数)

下表展示某次发布前全链路测试结果对比(单位:毫秒):

测试类型 平均耗时 P99延迟 失败率 关联Pod重启次数
单元测试 12.4 48.7 0.0% 0
Service Mesh集成测试 218.6 1120.3 1.2% 3
Chaos场景测试(网络分区) 493.2 3850.1 18.7% 12

基于eBPF的运行时测试行为捕获

为验证服务网格Sidecar对gRPC拦截的准确性,团队在测试Pod中部署eBPF探针(使用libbpf-go),实时捕获syscall.connect()调用栈与TLS握手状态。当测试用例TestGRPCRetryOn503执行时,eBPF程序自动关联测试ID与内核事件,生成带时间戳的上下文快照:

flowchart LR
    A[go test -run TestGRPCRetryOn503] --> B[eBPF probe: connect syscall]
    B --> C{TLS handshake success?}
    C -->|Yes| D[记录istio-proxy TLS version]
    C -->|No| E[触发重试机制并标记failure_reason=\"tls_handshake_timeout\"]
    D --> F[注入到TestReport JSON的runtime_context字段]

自动化测试报告签名与可信审计

在符合等保三级要求的政务云平台中,所有测试报告必须通过国密SM2算法签名。CI流水线在go test完成后,调用HSM模块对报告摘要进行硬件签名,生成不可篡改的审计凭证:

# 签名脚本片段
openssl sm2 -sign -in test-report.json -out report.sig \
  -certfile /etc/hsm/cert.pem -keyform ENGINE -engine hsm_engine

签名后的报告被上传至区块链存证服务,每个测试用例对应唯一CID(Content Identifier),运维人员可通过kubectl get testreport order-create-20240612-001 -o jsonpath='{.spec.cid}'直接追溯原始执行环境。

动态测试基线漂移检测

针对容器镜像频繁更新导致的性能基线偏移问题,项目引入基于TSFresh的时间序列特征分析。每次CI运行时,采集100次BenchmarkOrderProcessing的GC pause时间序列,提取mean_abs_change、number_peaks等27个特征,通过Isolation Forest算法识别异常基线变动。当检测到number_peaks突增300%时,自动触发git bisect定位引入内存泄漏的提交。

跨集群测试结果联邦查询

在混合云架构下,测试报告分散于AWS EKS、阿里云ACK及本地K3s集群。团队基于GraphQL构建联邦查询层,支持统一查询语句:

query GetFailedTests {
  clusters(names: ["prod-us-east", "staging-cn-hangzhou"]) {
    name
    testRuns(status: FAILED, lastDays: 7) {
      id
      testCase
      infraContext { k8sVersion, cniPlugin }
      errorLogs @include(if: $withLogs)
    }
  }
}

敏捷如猫,静默编码,偶尔输出技术喵喵叫。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注