Test Triage logo

Test Triage

Community
niki914
test-triage

Use before writing, adding, or modifying any unit test in this repo — before creating a `*Test.kt` file or a `@Test` function, and before touching an existing test after a refactor. Triages whether the test should exist at all, and what must happen to it when the code it guards changes.

Overview

Publisherniki914
Repositoryzafiro
Skill nametest-triage
Stars
305
Forks
50
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by niki914 on GitHub. Read the source before you install it.

Installation

Install the Test Triage AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/niki914/zafiro/tree/main/.agents/skills/test-triage
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/niki914/zafiro.git /tmp/zafiro
mkdir -p .claude/skills
cp -r /tmp/zafiro/.agents/skills/test-triage .claude/skills/test-triage
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Test Triage in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Test Triage on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Test Triage is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Test Triage

写测试前先分诊:这个测试该不该存在。通过了才谈怎么写。

一个测试的全部价值来自它的拦截力——删掉它,哪个真实 bug 会漏到用户手上?答不上来的测试拦截力为零:不要写,已经写了的删掉。

测试膨胀的根源不是测试写得不好,是没经过分诊就写下去了。

门禁

动手写 @Test 之前,先回答一句话:

删掉这个测试,哪一个真实发生过、或真实可能发生的改动,会让 bug 漏出去?

答案必须是一个具体的改动:谁改什么、怎么改坏、漏出去之后用户看到什么。答得出来 → 往下写;指不出来 → 不写。

"我想确认它是对的"不是答案。那是静态分析、类型系统、编译器的工作。

改已有测试,也要过同一道门禁

动任何一个已存在的 @Test 之前,拿同一句话再问它一遍,另外加一句:

如果这个测试今天要我从零写一遍,我还会写吗?

不会 → 删掉它,而不是改两行让它继续绿。

改测试只有两种正当理由:被测契约变了(该重写),或测试本身有缺陷(该重写)。"让它变绿"不是理由。

一票否决:看到这些,直接不写

不需要判断,也不需要"至少测一下"。

被测对象为什么拦不住 bug处置
data class 的字段、copy()、equals/toString编译器已经保证不写
常量、枚举成员、默认参数值编译器已经保证不写
getter / setter / 纯委托的转发方法没有分叉点不写
private 辅助方法的单独测试通过 public 行为间接覆盖不写
资源字符串、文案、提示语的措辞文案不是行为不写
日志有没有打、打成什么不是行为不写
ServiceRegistry 装配、DI 注册、构造注入装配错了启动就崩,不用断言不写
断言某字段"以后仍然是 null / 仍然不会被赋值"拦的是一个尚不存在的变化不写
断言某个已删除 / 遗留的东西不存在拦的是过去的影子不写
另一个测试已经完整覆盖的同一契约两个测试守一个契约 = 只有一个不写,删重复的那个

一票通过:这些是拦截力的来源

行为会分叉,而分叉点在静态分析里看不出来——这才是值得写的。

形态为什么值得
状态机迁移,含非法迁移与出错后停在哪迁移关系不在类型里
if / when 的条件分支、边界值、空集合、超长输入分支覆盖不可静态推导
解析、序列化、编解码的畸形输入真实数据会超出预期
权限、降级、兜底、重试路径出错路径最容易写坏
并发、取消、顺序、重复调用的既定契约时序不可静态推导
曾经真实回归过的点已证实的拦截力,最高优先
ViewModel / Reducer 的状态计算是逻辑,不是渲染

UI 的硬边界

不给 UI 写测试。 渲染、布局、点击、滚动、主题、动画、文案换行——这些都是 UI,由人工和 QA 验证,不写 @Test。这条不开放讨论。

要测的是 UI 背后的状态机:ViewModel / Reducer / Controller 里那段"输入什么、状态变成什么"的纯计算。它和 Compose 无关,只是恰好被 UI 调用。

一个测试里如果出现了 Compose 的 @Composable、createComposeRule、semantics、onNodeWithText——放错地方了,删掉。

拦截力为零的四种形态

四类都归零,原因各不相同。给它们起了名字,方便在提交和评审里直接点名。

① 回声测试

构造一个对象,再断言它等于自己刚塞进去的值。

kotlin
val model = ToolSpec(name = "a", enabled = true)
assertEquals("a", model.name)

它拦的是字段改名导致的编译错误——而编译错误不需要断言兜着。唯一能让它变红的方式是改坏赋值,那时候编译器先红。

② 钉文案

断言某个 message 等于一句写死的文案。

kotlin
assertEquals("接口返回为空", error.message)

文案不是行为:它进资源、换措辞、做本地化,测试就为一个字而碎,与功能正确性无关。

区分它和真测试:assertEquals("Does one thing.", metadata.description) 测的是解析器的输出,那是真行为。区别不在写法,在门禁那一问的答案。

③ 钉影子

断言某个已经删掉或遗留的东西不存在。

kotlin
assertNull(tool.ssh_terminal)   // 这个字段早就没了

它钉的是过去的影子。功能早已不在代码里,拦不住任何东西。

④ 钉未来 / 复读

断言"某字段以后仍然是 null"(拦一个尚不存在的变化),或整段复制另一个测试已经覆盖的契约。

kotlin
@Test fun spec_nonButtonTokensRemainUnsetForNow() { ... }

带 ForNow、NotYet、RemainsUnset、ForFuture 这类名字的测试,默认按这一类处理。

被测代码重构之后

触发条件:你重命名、拆分、合并、替换了某个类或方法的职责;移除了一个旧方法或旧字段;或者你判定这段被测代码本身就是要被替换掉的遗留代码。

这时它对应的单测必须重写或删除,只有这两种处置。三条硬规则,按顺序判断:

  1. 被测目标没了,或换了职责 → 删掉整个测试文件。不要改到能跑,也不要留一半。
  2. 被测目标还在,但内部结构变了 → 重写测试:按新结构重新组织用例,重新走一遍门禁。不要改两行让它编译通过。
  3. 重构后测试一行不改就通过 → 说明它根本没在测被重构的东西 → 删掉。

同时清理这些"为了让旧测试活着"的痕迹:

  • 为了兼容旧测试而保留的旧方法签名、旧类名、adapter、@Deprecated 转发
  • 测试里绕过新结构的写法(直接构造内部状态、反射、访问 private)
  • 旧测试里大量断言已经不存在的字段

重构不是改测试的理由,是删测试的理由。

通过门禁之后

  • 文件顶部写一行它保护什么:// 保护:<哪个行为,什么输入会打破它>。 写不出这一行,说明门禁没过,回去重判。
  • 测试名描述被保护的行为,不是被调用的方法名。rejectsBlankEndpoint 好过 testValidate1。
  • 一个 @Test 回答一个问题。一个测试里断言五个互不相关的点,红了以后没人知道是哪坏了。
  • 断言数量:一个测试 1~3 个断言。超过 5 个还在同一个 @Test 里,基本可以确定它该被拆开。

完成判据

收工前逐条过一遍,任何一条不成立就不算完成:

  • 每个新增或修改的 @Test,我都能立刻说出门禁那一问的答案
  • 答不上来的已经删掉,不是留着"反正不碍事"
  • 对照过「一票否决」表,没有一条踩中
  • 保留下来的每个测试文件顶部有 // 保护:... 那一行
  • 如果动了被测代码的结构,对应的测试是重写或删除的,不是打补丁修绿的
  • 没有为了兼容旧测试而保留的旧签名、adapter、@Deprecated 转发

Frequently asked questions

What does the Test Triage AI skill do?

Use before writing, adding, or modifying any unit test in this repo — before creating a `*Test.kt` file or a `@Test` function, and before touching an existing test after a refactor. Triages whether the test should exist at all, and what must happen to it when the code it guards changes.

Why use Test Triage on TypingMind?

Because you install it once and use it with any model. Test Triage is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Test Triage in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/niki914/zafiro/tree/main/.agents/skills/test-triage. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Test Triage?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Test Triage?

As many as you like. As long as a model supports skills, you can use Test Triage with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Test Triage AI skill free?

Yes. It is published on GitHub by niki914 under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇