大数跨境

Claude Code Hooks怎么用自动门禁让AI边写测试边自动修?(下)

Claude Code Hooks怎么用自动门禁让AI边写测试边自动修?(下) 51Testing软件测试网
2026-07-07
6
导读:用 Claude Code Hooks 在测试文件写入瞬间自动拦截硬编码等待、禁用选择器和缺少断言,让坏模式在源头被修复,不再流入代码审查。

点击蓝字

关注我们

上篇《Claude Code Hooks怎么用自动门禁让AI边写测试边自动修?》我们用 Claude Code Hooks 拦截硬编码等待和选择器污染,今天我们继续学习“缺少断言”的检查,并将三条规则合并为一个脚本,形成完整的 QA 门禁。



捕获缺少断言的问题

一个测试跑了一遍:导航、填表单、点按钮——然后啥也没断言——这是自动化测试套件里最危险的模式之一。


它会每次都通过,给你一种“覆盖率很好”的错觉,而等到真正出问题的时候,它什么也告诉不了你。Claude Code 也可能写出这样的测试,尤其是当它专注于“让测试跑通不报错”而不是“这个测试到底在验证什么”的时候。


下面这个 hook 会检查每个被编辑的测试文件,确保里面至少有一个断言:

# .claude/hooks/check_assertions.sh
#!/bin/bash
FILE=$1
# 只检查测试文件if [[ ! "$FILE" == tests/*.py ]]; then  exit 0fi
# 跳过不包含测试类或函数的文件if ! grep -qE "def test_" "$FILE"then  exit 0fi
# 统计文件中的测试函数数量TEST_COUNT=$(grep -cE "def test_" "$FILE")
# 统计断言模式的数量ASSERTION_COUNT=$(grep -cE "assert |expect\(|should\.|to_be_|to_have_|to_contain_" "$FILE")
if [ "$ASSERTION_COUNT" -eq 0 ]; then  echo ""  echo "QA GATE FAILED: No assertions found in $FILE"  echo "Every test must verify at least one expected outcome."  echo "Examples:"  echo "  assert page.url.endswith('/dashboard')"  echo "  expect(page.locator(\"[data-testid='success-msg']\")).to_be_visible()"  exit 1fi
# 如果测试函数数量远多于断言数量,发出警告if [ "$TEST_COUNT" -gt "$ASSERTION_COUNT" ]; then  echo ""  echo "QA GATE WARNING: $FILE has $TEST_COUNT test functions but only $ASSERTION_COUNT assertions."  echo "Some tests may be missing meaningful verifications."fi
exit0


对应的 hook 配置:

{  "hooks": {    "PostToolUse": [      {        "matcher""Edit|Write",        "hooks": [          {            "type""command",            "command""bash .claude/hooks/check_assertions.sh $CLAUDE_TOOL_INPUT_FILE_PATH"          }        ]      }    ]  }}


终端里看到的失败信息是这样的:

QA GATE FAILED: No assertions found in tests/e2e/test_profile.py
Every test must verify at least one expected outcome.
Examples:  assert page.url.endswith('/dashboard')  expect(page.locator("[data-testid='success-msg']")).to_be_visible()


警告的情况也同样有用。如果一个文件里有五个测试函数,却只有一个断言,这强烈暗示 Claude Code 写的测试只是跟 UI 做了交互,但并没有真正验证任何有意义的点。


在文件编辑阶段就捕获到这个问题,意味着你不需要等到生产环境部署失败才发现它。



把多个 Hooks 合并成一个完整的 QA 门禁

分别跑三个独立的 hook 也可以,但把它们合并到一个脚本里会更干净、更容易维护。一个脚本、一条 hook 配置、清晰的输出——一眼就能看出哪里失败了、为什么失败。


下面是合并后的脚本:

# .claude/hooks/qa_gate.sh
#!/bin/bash
FILE=$1VIOLATIONS=0
# 只检查Python测试文件和页面对象if [[ ! "$FILE" == tests/*.py ]] && [[ ! "$FILE" == pages/*.py ]]; then  exit 0fi
echo "Running QA gate checks on $FILE..."
# 检查1:硬编码等待if grep -nE "time\.sleep\(|waitForTimeout\(|Thread\.sleep\(" "$FILE"then  echo ""  echo "FAILED [Hardcoded Wait]: Replace with an explicit wait condition."  echo "  Playwright: page.wait_for_selector(), expect(locator).to_be_visible()"  echo "  Selenium: WebDriverWait(driver, timeout).until(...)"  VIOLATIONS=$((VIOLATIONS + 1))fi
# 检查2:禁止的选择器if grep -nE "By\.XPATH|find_element\(By\.ID|find_element\(By\.CLASS_NAME|css=\'" "$FILE"then  echo ""  echo "FAILED [Forbidden Selector]: Use data-testid attributes only."  echo "  Correct: page.locator(\"[data-testid='submit-btn']\")"  echo "  Forbidden: By.XPATH, By.ID, By.CLASS_NAME, css selectors"  VIOLATIONS=$((VIOLATIONS + 1))fi
# 检查3:缺少断言(仅测试文件)if [[ "$FILE" == tests/*.py ]]; then  if grep -qE "def test_" "$FILE"then    ASSERTION_COUNT=$(grep -cE "assert |expect\(|should\.|to_be_|to_have_|to_contain_" "$FILE")    TEST_COUNT=$(grep -cE "def test_" "$FILE")
    if [ "$ASSERTION_COUNT" -eq 0 ]; then      echo ""      echo "FAILED [Missing Assertions]: Every test must verify at least one outcome."      VIOLATIONS=$((VIOLATIONS + 1))    elif [ "$TEST_COUNT" -gt "$ASSERTION_COUNT" ]; then      echo ""      echo "WARNING [Assertion Coverage]: $TEST_COUNT tests but only $ASSERTION_COUNT assertions found."      echo "Some tests may be missing meaningful verifications."    fi  fifi
# 总结if [ $VIOLATIONS -gt 0 ]; then  echo ""  echo "QA gate failed with $VIOLATIONS violation(s) in $FILE"  echo "Claude Code will attempt to fix the issues above before continuing."  exit 1fi
echo "QA gate passed."exit 0


以及用来替代上面三个独立 hook 的单条配置:

{  "hooks": {    "PostToolUse": [      {        "matcher""Edit|Write",        "hooks": [          {            "type""command",            "command""bash .claude/hooks/qa_gate.sh $CLAUDE_TOOL_INPUT_FILE_PATH"          }        ]      }    ]  }}


当同一个文件里检测出多个违规时,输出会一次性清晰地告诉你所有需要修复的问题:

Running QA gate checks on tests/e2e/test_checkout.py...
tests/e2e/test_checkout.py:14: time.sleep(2)
FAILED [Hardcoded Wait]: Replace with an explicit wait condition.  Playwright: page.wait_for_selector(), expect(locator).to_be_visible()  Selenium: WebDriverWait(driver, timeout).until(...)
tests/e2e/test_checkout.py:22: self.driver.find_element(By.ID, "confirm-btn").click()
FAILED [Forbidden Selector]: Use data-testid attributes only.  Correct: page.locator("[data-testid='confirm-btn']")  Forbidden: By.XPATH, By.ID, By.CLASS_NAME, css selectors
QA gate failed with 2 violation(s) in tests/e2e/test_checkout.py
Claude Code will attempt to fix the issues above before continuing.


这里需要理解的一个重点是:接下来会发生什么。当 Claude Code 看到 hook 返回了非零的退出码时,它会读取输出、理解哪些东西被标记了、然后尝试修复这些违规,然后再继续。


在实际使用中,这意味着门禁几乎不会长时间阻断你的工作流。Claude Code 会自我修正、重新运行 hook、等一切通过之后再继续。你既得到了强制约束,又不会被频繁打断。


随着你的测试框架不断演进,qa_gate.sh 这个脚本会成为你想强制执行的所有新约定的自然落脚点。加一条检查,提交脚本,以后这个项目里的每一个 Claude Code 会话都会自动遵守它。


总结

糟糕的测试模式不总是因为粗心。它们之所以会出现,是因为反馈回路太慢了。


等到一个硬编码等待或一个缺失的断言被送到代码审查环节时,它已经浪费了某个人的时间。Claude Code hooks 把反馈时机提前到了最早可能的时刻——就在文件被写入的那一瞬间——而且不给你增加任何手动操作。


从 qa_gate.sh 脚本里的某一条检查开始,先熟悉 hooks 的行为方式,然后再逐步扩展。这套门禁会随着你的约定一起成长——你文档化的规则越多,这个门禁就越有用。

E n d

【声明】内容源于网络
0
0
51Testing软件测试网
博为峰51Testing软件测试网提供各种线上招聘、线上课程等网络服务,出版软件测试系列丛书及电子杂志,组织线上技术交流活动;同时还举办多种线下公益活动,如软件测试沙龙、软件测试专场招聘会等。
内容 3902
粉丝 0
51Testing软件测试网 博为峰51Testing软件测试网提供各种线上招聘、线上课程等网络服务,出版软件测试系列丛书及电子杂志,组织线上技术交流活动;同时还举办多种线下公益活动,如软件测试沙龙、软件测试专场招聘会等。
总阅读1.7k
粉丝0
内容3.9k