skills/check-test-code-quality/rules/R008/SKILL.md
# R008: 用例声明格式不规范 ## 规则信息 | 属性 | 值 | |------|------| | 规则编号 | R008 | | 问题类型 | 用例声明格式不规范 | | 严重级别 | Warning | | 规则复杂度 | complex | ## 问题描述 测试用例的文档注释格式不符合规范要求,包括缺少注释标记、分隔符错误、空行等问题。 **规范来源**: 用例低级问题.md 第15条 — "XTS上库用例声明需符合要求" ### 规范要求 1. 文档注释以 `/**` 开头,以 `*/` 结尾,每行以 `*` 开始 2. 参数名以 `@` 修饰,参数名和参数值以(一或若干个)空格分隔,禁止使用其他分隔符 3. 文档注释结束行的下一行应紧接要修饰的测试用例,禁止出现空行 ## 扫描范围 | 应扫描 | 文件扩展名 | |--------|-----------| | **测试文件** | `.test.ets`, `.test.ts`, `.test.js` | ## 检测逻辑 ### 步骤1: 收集测试文件 ```python def get_
npx skillsauth add openharmonyinsight/openharmony-skills skills/check-test-code-quality/rules/R008Install this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| 属性 | 值 | |------|------| | 规则编号 | R008 | | 问题类型 | 用例声明格式不规范 | | 严重级别 | Warning | | 规则复杂度 | complex |
测试用例的文档注释格式不符合规范要求,包括缺少注释标记、分隔符错误、空行等问题。
规范来源: 用例低级问题.md 第15条 — "XTS上库用例声明需符合要求"
/** 开头,以 */ 结尾,每行以 * 开始@ 修饰,参数名和参数值以(一或若干个)空格分隔,禁止使用其他分隔符| 应扫描 | 文件扩展名 |
|--------|-----------|
| 测试文件 | .test.ets, .test.ts, .test.js |
def get_r008_scan_files(directory: str) -> list[str]:
test_files = get_test_files(directory) # .test.ets, .test.ts, .test.js
return test_files
在测试文件中,找到所有位于 it( 调用之前的文档注释块:
import re
def extract_doc_blocks(content: str) -> list[dict]:
lines = content.split('\n')
doc_blocks = []
i = 0
while i < len(lines):
line = lines[i]
stripped = line.strip()
# 查找 /** 开头的文档注释
if stripped.startswith('/**'):
block_start = i + 1 # 1-indexed
block_lines = [line]
j = i + 1
# 收集注释块内容直到 */
while j < len(lines):
block_lines.append(lines[j])
if '*/' in lines[j]:
break
j += 1
block_end = j + 1 # 1-indexed
block_text = '\n'.join(block_lines)
# 检查注释块后是否紧跟 it() 调用
next_code_line_idx = j + 1
testcase_name = None
has_empty_line = False
# 跳过空行
while next_code_line_idx < len(lines) and lines[next_code_line_idx].strip() == '':
has_empty_line = True
next_code_line_idx += 1
if next_code_line_idx < len(lines):
next_line = lines[next_code_line_idx].strip()
tc_match = re.search(r"\bit\s*\(\s*['\"]([^'\"]+)['\"]", next_line)
if tc_match:
testcase_name = tc_match.group(1)
doc_blocks.append({
'start_line': block_start,
'end_line': block_end,
'lines': block_lines,
'text': block_text,
'testcase': testcase_name or '-',
'has_empty_line_before_it': has_empty_line,
'next_code_line': next_code_line_idx + 1 if next_code_line_idx < len(lines) else None
})
i = j + 1
else:
i += 1
return doc_blocks
对每个文档注释块,检测以下5种问题:
def check_doc_block(block: dict) -> list[dict]:
issues = []
lines = block['lines']
testcase = block['testcase']
# 问题1: 缺少 /** 开头或 */ 结尾
first_line = lines[0].strip()
last_line = lines[-1].strip()
if not first_line.startswith('/**'):
issues.append({
'line': block['start_line'],
'type': 'missing_opening',
'snippet': first_line,
'detail': '文档注释未以/**开头'
})
if '*/' not in last_line and (len(lines) == 1 or '*/' not in lines[-1]):
issues.append({
'line': block['end_line'],
'type': 'missing_closing',
'snippet': last_line,
'detail': '文档注释未以*/结尾'
})
# 问题2: 每行未以 * 开始(跳过首行和末行)
for idx, line in enumerate(lines[1:-1], start=block['start_line'] + 1):
stripped = line.strip()
if stripped and not stripped.startswith('*'):
issues.append({
'line': idx,
'type': 'missing_asterisk',
'snippet': stripped,
'detail': '注释行未以*开始'
})
# 问题3: 参数名缺少 @ 修饰符
for idx, line in enumerate(lines, start=block['start_line']):
stripped = line.strip().lstrip('*').strip()
# 匹配 tc.name, tc.number 等无 @ 前缀的参数
no_at_match = re.match(r'^(tc\.\w+)\s+\S', stripped)
if no_at_match:
issues.append({
'line': idx,
'type': 'missing_at_modifier',
'snippet': line.strip(),
'detail': f'参数 {no_at_match.group(1)} 缺少@修饰符'
})
# 问题4: 使用冒号分隔符(应使用空格)
for idx, line in enumerate(lines, start=block['start_line']):
# 匹配 @tc.name : value 或 @tc.name: value 模式
colon_match = re.search(r'@(tc\.\w+)\s*:\s', line)
if colon_match:
issues.append({
'line': idx,
'type': 'colon_separator',
'snippet': line.strip(),
'detail': f'参数 {colon_match.group(1)} 使用了冒号分隔符,应使用空格'
})
# 问题5: 注释结束行与测试用例之间存在空行
if block['has_empty_line_before_it'] and block['testcase'] != '-':
issues.append({
'line': block['end_line'],
'type': 'empty_line_before_it',
'snippet': '*/ 后存在空行',
'detail': '文档注释结束行与测试用例之间不应有空行'
})
return issues
def check_r008(file_path: str, content: str) -> list[dict]:
doc_blocks = extract_doc_blocks(content)
all_issues = []
for block in doc_blocks:
issues = check_doc_block(block)
for issue in issues:
all_issues.append({
'rule': 'R008',
'type': '用例声明格式不规范',
'severity': 'Warning',
'file': file_path,
'line': issue['line'],
'testcase': block['testcase'],
'snippet': issue['snippet'],
'suggestion': (
f'路径: {file_path}, 行号: {issue["line"]}, '
f'问题描述: {issue["detail"]}'
)
})
return all_issues
| 列名 | 说明 |
|------|------|
| 问题ID | R008 |
| 问题类型 | 用例声明格式不规范 |
| 严重级别 | Warning |
| 文件路径 | 相对路径 |
| 行号 | 问题所在行号 |
| 所属用例 | 关联的 it(' 参数名 |
| 代码片段 | 匹配到的代码行 |
| 修复建议 | 路径+行号+问题描述 |
// 错误1: 缺少@修饰符
/**
* tc.name testName // ✗ 错误:缺少@
* tc.number SUB_XXX_XXXX_XXXX // ✗ 错误:缺少@
*/
it('testName', () => {
console.info('test001');
});
// 错误2: 使用/* 开头(应为/**)
/* // ✗ 错误:应使用 /**
* @tc.name testName
*/
it('testName', () => {
console.info('test001');
});
// 错误3: 使用冒号分隔
/**
* @tc.name : testName // ✗ 错误:不应使用冒号
* @tc.number : SUB_XXX_XXXX_XXXX // ✗ 错误:不应使用冒号
*/
it('testName', () => {
console.info('test001');
});
// 错误4: 注释结束行与用例间有空行
/**
* @tc.name testName
*/
// ✗ 错误:不应有空行
it('testName', () => {
console.info('test001');
});
// 错误5: 注释行未以*开始
/**
tc.name testName // ✗ 错误:未以*开始
* @tc.number SUB_XXX_XXXX
*/
it('testName', () => {
console.info('test001');
});
// 正确: 使用标准格式
/**
* @tc.name testName
* @tc.number SUB_XXX_XXXX_XXXX
* @tc.desc 测试用例描述
* @tc.size MEDIUMTEST
* @tc.type Function
* @tc.level Level2
*/
it('testName', TestType.FUNCTION | Size.MEDIUMTEST | Level.LEVEL2, async (done) => {
console.info('test001');
done();
});
| 类型编号 | 问题 | 检测方法 |
|---------|------|---------|
| 1 | 缺少 /** 开头 | 首行不以 /** 开始 |
| 2 | 缺少 */ 结尾 | 末行不包含 */ |
| 3 | 每行未以 * 开始 | 中间行不以 * 开始 |
| 4 | 参数名缺少 @ 修饰符 | 行内容匹配 tc.\w+ 但无 @ 前缀 |
| 5 | 使用冒号分隔符 | 行内容匹配 @tc.\w+\s*: |
| 6 | 注释与用例间有空行 | */ 与 it( 之间存在空行 |
--level warning 或 --level all.test.ets/.test.ts/.test.js),不扫描非测试源代码文件it(' 参数名挑战: 解析JSDoc注释并检查完整性
解决方案:
required_tags = [
'@tc.number', '@tc.name', '@tc.desc', '@tc.type',
'@tc.level', '@tc.size', '@tc.size', '@tc.since'
]
---
## 最新评估结果与实现状态(2026-04-14)
### 实现状态
**当前状态**: ❌ **未实现**(使用占位符扫描器)
**技术原因**:
- R008属于"模型生成规则",需要根据本文档的检测逻辑动态生成扫描代码
- 在 `scripts/main.py` 的 `load_rule_scanners()` 函数中,R008被替换为noop扫描器(空操作)
- 当前实现返回空列表,无法检测任何问题
### 改进方案
**方案1:实现为预置脚本(推荐)**
在 `scripts/simple_rules.py` 中添加R008的扫描实现:
```python
# ======================== R008: 用例声明格式不规范 ========================
# Source: rules/R008/SKILL.md
_R008_NO_AT_RE = re.compile(r'^\s*\*\s*(tc\.\w+)\s+\S')
def scan_r008(files, base_dir):
issues = []
for fp in files:
try:
with open(fp, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
except Exception:
continue
lines = content.split('\n')
in_doc_comment = False
doc_start_line = 0
testcase_name = None
for i, line in enumerate(lines, 1):
stripped = line.strip()
# 检测 /** 开始
if stripped.startswith('/**'):
in_doc_comment = True
doc_start_line = i
continue
# 检测 */ 结束
if in_doc_comment and '*/' in stripped:
in_doc_comment = False
# 查找下一个 it() 调用
for j in range(i, min(i + 5, len(lines))):
m = re.search(r"\bit\s*\(\s*['\"]([^'\"]+)['\"]", lines[j])
if m:
testcase_name = m.group(1)
break
continue
# 在注释块内检测问题
if in_doc_comment:
# 检测缺少 @ 修饰符
m = _R008_NO_AT_RE.search(stripped)
if m:
issues.append(_make_issue(
'R008', '用例声明格式不规范', 'Warning',
fp, base_dir, i, line,
f'路径: {os.path.relpath(fp, base_dir)}, 行号: {i}, 问题描述: 参数 {m.group(1)} 缺少@修饰符',
testcase=testcase_name or '-'))
return issues
方案2:动态生成检测代码
修改 scripts/main.py,在扫描时根据本文档的检测逻辑动态生成并执行R008检测代码。
方案3:独立扫描脚本
创建独立的 scripts/scan_r008.py 文件,实现R008的完整检测逻辑。
根据 guides/R008_testcase_format/R008_FIX_GUIDE.md,R008支持自动修复:
修复内容:
tc.name 改为 @tc.nametc.number 改为 @tc.number修复命令:
python3 scripts/main.py /path/to/code --rules R008 --fix
详细评估报告见:/home/xianf/master/test/xts/evaluation_report.md
testing
--- name: ohos-req-value-decision description: Use after review meeting to record decision and route to next step. Triggers: 评审决策纪要, 评审结论回流, value decision, 评审接纳, 评审不接纳, 评审退回, 下次重新上会. Do NOT use for feature baseline (ohos-req-feature-baseline), review gate checks (ohos-req-review-gate), or IR generation (ohos-req-feature-to-ir). metadata: author: openharmony scope: common stage: requirements capability: value-decision version: 0.3.0 status: draft tags: - sdd - requirements
development
Use when converting an OpenHarmony requirement document, spec, or design proposal into an OpenHarmony review slide deck (需求评审 / 需求变更评审 / 设计评审 PPTX) — produces the fixed OpenHarmony-branded review-deck structure (OH logo on every page) with architecture/flow diagrams and field tables. Triggers on "需求评审PPT", "需求变更评审", "把需求文档转成评审PPT", "spec转评审PPT", "requirement/spec to review deck". NOT for arbitrary or generic slide decks unrelated to OpenHarmony requirement/design review.
testing
Use when performing the Phase 0 Step 0.5 Review Ready Gate on a 04-feature.md, especially when the user says "evaluate gate", "review readiness", "feature ready?", "should we generate IR", or when the ohos-req-intake-orchestration main session needs a structured Ready / Conditional Ready / Not Ready judgment instead of doing the check inline. Reads 01-04, runs seven fixed checks plus a conditional-items check, and returns a machine-readable JSON summary plus a human-readable table that the main session can route on. Do NOT use for feature baseline generation (ohos-req-feature-baseline), value decision recording (ohos-req-value-decision), or IR generation (ohos-req-feature-to-ir).
testing
--- name: ohos-req-requirement-intake description: Use when importing an OHOS requirement into Phase 0.1, especially for 01-requirement.md, requirement intake, background, user value, scenarios, scope, FR/NFR, affected modules, or priority. Triggers: 需求导入, 01-requirement, 需求基线, RR单号. Do NOT use for feasibility analysis (ohos-req-feasibility-analysis), architecture decision (ohos-req-arch-decision), or feature baseline (ohos-req-feature-baseline). metadata: author: openharmony scope: common