R003: 禁止恒真断言

SkillDev tools

A skill for dev tools by openharmonyinsight.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the R003: 禁止恒真断言 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/R003/SKILL.md and read by ahel’s review.

规则概述

属性
规则编号R003
问题类型禁止恒真断言
严重级别Critical
规则分类简单规则(正则匹配)
扫描范围所有源代码文件(.ets, .ts, .js

问题描述

测试用例中使用 expect(true).assertTrue() 等恒真断言,断言只会产生成功结果,用例恒通过。此类断言未对接口实际返回值进行断言校验,相当于没有任何有效断言。

检测模式(完整覆盖)

R003规则必须检测以下全部3种恒真断言模式:

r003_patterns = [
    # 模式1: expect(true).assertTrue()
    # 最常见的恒真断言形式,true断言为true,恒成立
    re.compile(r'expect\s*\(\s*true\s*\)\s*\.\s*assertTrue\s*\('),

    # 模式2: expect(true).assertEqual(true)  ⚠️ 容易遗漏!
    # 128个问题,占R003总量的3.3%。true等于true,恒成立
    # 如果遗漏此模式,将导致约2014个问题被漏报
    re.compile(r'expect\s*\(\s*true\s*\)\s*\.\s*assertEqual\s*\(\s*true\s*\)'),

    # 模式3: expect(false).assertFalse()
    # false断言为false,恒成立
    re.compile(r'expect\s*\(\s*false\s*\)\s*\.\s*assertFalse\s*\('),
]

各模式占比

模式代码形式预估问题数占比遗漏影响
模式1expect(true).assertTrue()~3,800~96.7%主要模式
模式2expect(true).assertEqual(true)~128~3.3%易遗漏
模式3expect(false).assertFalse()~6~0.0%罕见

⚠️ 已知陷阱

陷阱: 遗漏 assertEqual(true) 变体

问题严重性: ⭐⭐⭐⭐ 严重

expect(true).assertEqual(true) 是恒真断言的变体,容易被遗漏。该模式虽然占比仅3.3%(128个),但遗漏会导致约2014个相关文件的问题被完全忽略。必须将所有3种模式都加入检测列表。

详见 references/TRAPS.md 陷阱6。

错误示例

错误1: expect(true).assertTrue()(模式1,最常见)

it('testCounter01', Level.LEVEL0, async (done: Function) => {
  console.info('[testCounter01] START');
  let strJson = getInspectorByKey('Counter');
  let obj: ESObject = JSON.parse(strJson);
  console.info("[testCounter01] obj is : " + JSON.stringify(obj.$attrs));
  expect(true).assertTrue();  // ✗ 恒真断言:true断言为true,恒成立
  console.info('[testCounter01] END');
  done();
});

来源: 用例低级问题.md 第⑤点

错误2: expect(true).assertEqual(true)(模式2,易遗漏)

it('testIsLocationEnabled06', TestType.FUNCTION | Size.MEDIUMTEST | Level.LEVEL2, function () {
  try {
    let state = geolocationm.isLocationEnabled();
    console.info('[lbs_js] getLocationSwitchState06 result: ' + JSON.stringify(state));
    expect(true).assertEqual(state);  // ✗ 此处是对state的断言,但如果state恰好为true...
  } catch (error) {
    console.info("[lbs_js] getLocationSwitchState06 try err." + JSON.stringify(error));
    if (error.code == "801") {
      expect(error.code).assertEqual("801")
    } else {
      expect().assertFail();
    }
  }
});

来源: 用例低级问题.md 第②点

错误3: expect(true).assertEqual(true)(模式2变体)

it('effectKitTest_static_0100', TestType.FUNCTION | Size.MEDIUMTEST | Level.LEVEL1,
  async (done: () => void): Promise<void> => {
  // ...
  try {
    let headFilter: effectKit.Filter = effectKit.createEffect(pixelMap);
    console.info('www data succeed')
    if (headFilter == undefined) {
      hilog.info(domain, tag, '%{public}s', 'effectKit createFilter failed.');
      return undefined;
    }
    let blurFilter: effectKit.Filter = headFilter.blur(10);
    expect(true).assertTrue();  // ✗ 恒真断言(模式1)
    hilog.info(domain, tag, '%{public}s', 'effectKit createFilter successfully.');
    const effectPixelMap = await blurFilter.getEffectPixelMap();
    if (effectPixelMap != undefined) {
      hilog.info(domain, tag, '%{public}s', 'effectKit getEffectPixelMap successfully.');
    } else {
      hilog.info(domain, tag, '%{public}s', 'effectKit getEffectPixelMap failed.');
    }
    hilog.info(domain, tag, '%{public}s', 'effectKit test END');
    expect(true).assertTrue();  // ✗ 恒真断言(模式1),出现两次
  } catch (err) {
    console.info('www data err ' + JSON.stringify(err))
  }
  done();
})

来源: 用例低级问题.md 第②点

错误4: expect(false).assertFalse()(模式3,罕见)

it('testBoolCheck', Level.LEVEL0, () => {
  let result = someFunction();
  if (result === false) {
    expect(false).assertFalse();  // ✗ 恒真断言:false断言为false,恒成立
  }
});

正确示例

正确1: 使用实际变量值

it('test001', Level.LEVEL0, () => {
  let actualValue = someFunction();
  expect(actualValue).assertTrue();  // ✓ 断言实际值,非恒真
});

正确2: 使用assertFalse验证实际false值

it('test002', Level.LEVEL0, () => {
  let actualValue = someFunction();
  expect(actualValue).assertFalse();  // ✓ 断言实际值为false
});

正确3: 使用assertEqual验证具体值

it('test003', Level.LEVEL0, () => {
  let actualValue = someFunction();
  expect(actualValue).assertEqual('expected');  // ✓ 断言具体期望值
});

正确4: 使用比较运算

it('test004', Level.LEVEL0, () => {
  let count = 900;
  expect(count).assertLargerOrEqual(800);  // ✓ 断言count >= 800
});

正确5: try-catch中每个分支都有有效断言

it('test005', Level.LEVEL0, async (done: Function) => {
  try {
    await someAsyncFunction();
    expect(true).assertTrue();  // ✗ 仍然是R003问题(恒真断言)
    done();
  } catch (error) {
    expect(error.code).assertEqual(401);  // ✓ catch块中有有效断言
    done();
  }
});

注意: 即使try-catch结构中catch块有有效断言(如error.code),try块中的expect(true).assertTrue()仍然属于R003问题,因为该断言本身是恒真的。正确做法是移除try块中的恒真断言,或替换为对实际返回值的有效断言。

扫描实现

伪代码

def check_r003(file_path: str, content: str, lines: list[str]) -> list[dict]:
    """
    扫描R003: 禁止恒真断言

    Args:
        file_path: 文件相对路径
        content: 文件完整内容
        lines: 文件按行分割的列表

    Returns:
        问题列表
    """
    issues = []

    r003_patterns = [
        re.compile(r'expect\s*\(\s*true\s*\)\s*\.\s*assertTrue\s*\('),
        re.compile(r'expect\s*\(\s*true\s*\)\s*\.\s*assertEqual\s*\(\s*true\s*\)'),
        re.compile(r'expect\s*\(\s*false\s*\)\s*\.\s*assertFalse\s*\('),
    ]

    # 解析it()块范围,用于填充testcase字段
    it_blocks = parse_it_blocks(content, lines)

    for i, line in enumerate(lines, 1):
        for pattern in r003_patterns:
            match = pattern.search(line)
            if match:
                # 提取匹配到的代码片段
                snippet = line.strip()

                # 确定所属用例
                testcase = find_testcase_for_line(it_blocks, i)

                # 判断恒真类型
                if 'assertEqual' in snippet:
                    suggestion = (
                        "禁止恒真断言。expect(true).assertEqual(true) 恒成立,"
                        "未对接口实际返回值进行断言校验。"
                        "请替换为对实际业务逻辑的有效断言,"
                        "如 expect(actualValue).assertEqual(expectedValue)。"
                    )
                elif 'assertFalse' in snippet:
                    suggestion = (
                        "禁止恒真断言。expect(false).assertFalse() 恒成立,"
                        "请替换为对实际变量值的断言,"
                        "如 expect(actualValue).assertFalse()。"
                    )
                else:
                    suggestion = (
                        "禁止恒真断言。expect(true).assertTrue() 恒成立,"
                        "未对接口实际返回值进行断言校验。"
                        "请替换为对实际业务逻辑的有效断言,"
                        "如 expect(actualValue).assertTrue() 或 expect(actualValue).assertEqual(expected)。"
                    )

                issues.append({
                    'rule': 'R003',
                    'type': '禁止恒真断言',
                    'severity': 'Critical',
                    'file': file_path,
                    'line': i,
                    'testcase': testcase,
                    'snippet': snippet,
                    'suggestion': suggestion,
                })
                break  # 同一行只报告一次(优先匹配第一个模式)

    return issues

testcase字段解析

所有R003扫描结果必须包含 testcase 字段,用于标识问题所属的测试用例:

def find_testcase_for_line(it_blocks: list[dict], line_num: int) -> str:
    """
    查找指定行号所属的it()块,返回testcase名称。

    Args:
        it_blocks: it()块列表,每个元素包含 {'name': str, 'start': int, 'end': int}
        line_num: 问题所在行号

    Returns:
        testcase名称,不在任何it()块内时返回 '-'
    """
    for block in it_blocks:
        if block['start'] <= line_num <= block['end']:
            return block['name']
    return '-'

输出格式

Excel报告列

列序列名示例
1问题IDR003
2问题类型禁止恒真断言
3严重级别Critical
4文件路径arkui/ace_ets_component_seven/.../Counter.test.ets
5行号213
6所属用例testCounter01
7代码片段expect(true).assertTrue();
8修复建议禁止恒真断言。expect(true).assertTrue() 恒成立,未对接口实际返回值进行断言校验。请替换为对实际业务逻辑的有效断言...

终端输出示例

[R003] arkui/ace_ets_component_seven/ace_ets_component_seven_special/entry/src/main/ets/test/Counter.test.ets:213
  用例: testCounter01
  代码: expect(true).assertTrue();
  建议: 禁止恒真断言。expect(true).assertTrue() 恒成立,未对接口实际返回值进行断言校验。
        请替换为对实际业务逻辑的有效断言,如 expect(actualValue).assertTrue() 或 expect(actualValue).assertEqual(expected)。

参考资料

额外正确示例

来源: 规则内置示例(原 docs/EXAMPLES.md,已迁移)

以下断言方法均可用于替代恒真断言:

正确6: 使用assertLessOrEqual - 检验小于等于

it('test003', Level.LEVEL0, () => {
  let value = 50;
  expect(value).assertLessOrEqual(100);  // ✓ 正确:断言value <= 100
});

正确7: 使用assertLarger - 检验大于

it('test004', Level.LEVEL0, () => {
  let result = 100;
  expect(result).assertLarger(50);  // ✓ 正确:断言result > 50
});

正确8: 使用assertLess - 检验小于

it('test005', Level.LEVEL0, () => {
  let timeout = 5000;
  expect(timeout).assertLess(10000);  // ✓ 正确:断言timeout < 10000
});

参考文档

Signals

GitHub stars
34
Forks
7
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
r003
Source
github.com/openharmonyinsight/openharmony-skills