R008: 用例声明格式不规范
SkillDev toolsA skill for dev tools by openharmonyinsight.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the R008: 用例声明格式不规范 skill
What this skill tells your AI
The instructions your AI receives, as published by openharmonyinsight/openharmony-skills in skills/check-test-code-quality/rules/R008/SKILL.md and read by ahel’s review.
规则信息
| 属性 | 值 |
|---|---|
| 规则编号 | R008 |
| 问题类型 | 用例声明格式不规范 |
| 严重级别 | Warning |
| 规则复杂度 | complex |
问题描述
测试用例的文档注释格式不符合规范要求,包括缺少注释标记、分隔符错误、空行等问题。
规范来源: 用例低级问题.md 第15条 — "XTS上库用例声明需符合要求"
规范要求
- 文档注释以
/**开头,以*/结尾,每行以*开始 - 参数名以
@修饰,参数名和参数值以(一或若干个)空格分隔,禁止使用其他分隔符 - 文档注释结束行的下一行应紧接要修饰的测试用例,禁止出现空行
扫描范围
| 应扫描 | 文件扩展名 |
|---|---|
| 测试文件 | .test.ets, .test.ts, .test.js |
检测逻辑
步骤1: 收集测试文件
def get_r008_scan_files(directory: str) -> list[str]:
test_files = get_test_files(directory) # .test.ets, .test.ts, .test.js
return test_files
步骤2: 提取文档注释块
在测试文件中,找到所有位于 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
步骤3: 检测注释格式问题
对每个文档注释块,检测以下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
步骤4: 输出问题
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( 之间存在空行 |
注意事项
- R008属于Warning级别,默认不扫描,需使用
--level warning或--level all - 预期问题数量约321676个,扫描时间约60分钟
- 只扫描测试文件(
.test.ets/.test.ts/.test.js),不扫描非测试源代码文件 - 同一个注释块可能存在多个问题,每个问题分别报告
- testcase字段取注释块后紧跟的
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.name - 将
tc.number改为@tc.number - 删除多余空行
修复命令:
python3 scripts/main.py /path/to/code --rules R008 --fix
参考文档
- SKILL.md - 主技能文档(规则总览和评估结果)
- scripts/simple_rules.py - 预置扫描脚本(需添加R008实现)
- scripts/main.py - 扫描入口(需修改noop扫描器逻辑)
- guides/R008_testcase_format/R008_FIX_GUIDE.md - 修复指南
详细评估报告见:/home/xianf/master/test/xts/evaluation_report.md
Signals
- GitHub stars
- 34
- Forks
- 7
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
r008- Source
- github.com/openharmonyinsight/openharmony-skills