mcpField
G
grade-tests

grade-tests

github.com/dotnet测试与质量5.8k3.2k 次安装1 天前更新
代码仓库

关于 grade-tests

用于评估指定测试方法并生成适合 PR 评论的简洁报告的技能。它不会自行发现测试,而是根据调用方提供的测试范围给出四种决策结果,并附带 A–F 质量等级。

主要功能

  1. 01评估调用方指定的测试范围
  2. 02输出四种决策结果
  3. 03附加 A–F 质量等级
  4. 04结合测试代码及可用的生产代码评估
  5. 05按语言和测试框架加载扩展规则

适用场景

  1. 01评估 PR 中新增或修改的测试
  2. 02审查指定测试方法是否需要跟进
  3. 03对贡献中的一组测试进行分级

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dotnet/skills --skill grade-tests

安装后在 Claude Code 中输入/grade-tests确认已加载。

详细介绍

测试评分

评估精选的测试方法列表,并生成适合在 PR 评论中使用的精简报告。主要结果为 通过、失败、不确定 或 不适用;A-F 质量等级仍作为次要诊断信息。本技能 不会自行发现测试——调用方(通常是 PR 自动化工作流,或手头有特定列表的人工审核者)需提供要评估的测试或范围有限的差异。

Step 0 确定范围后,须遵守以下评分原则:

  • 有生产代码上下文时,评分前按名称加载一次 test-gap-analysis,并使用 per-test-read-only 调用方上下文。阅读其自有的组合参考;不得根据本评分规则计算变异证据,也不得运行其独立工作流。仅当依赖项、参考资料或所需上下文确实不可用时,才报告 N/A / 未验证。
  • 任何报告的变异推断都必须使用 可能已杀死(推断) 或 可能存活的候选项(未验证),即使正文中已加以说明。
  • 只使用下方评分标准,不得采用重复测试惩罚等额外启发式规则。不得通过同组测试改变单项评估。
  • 质量等级为 B 不代表结果必须为失败;完整且聚焦的测试可能不需要采取任何后续措施。

语言专属指南:阅读调用方提供的匹配扩展文件,或运行时列出的 test-analysis-extensions 目录及其中匹配的语言文件。仅供参考的辅助工具不可调用。如果其文件缺失,请使用内嵌评分标准和固定版本的框架 API;若关键框架结构仍无法确定,应标记为“不确定”,而不是猜测或搜索安装目录。

为什么同时提供决策结果和质量详情

PR 审核者需要简单明了地判断 这个测试是否需要跟进? 四态结果用于做出该判断;现有 A-F 评分标准则说明其质量和严重程度。

适用场景

  • PR 自动化工作流需要对拉取请求中新建或修改的测试发布决策。
  • 审核者有特定的测试列表(文件、类、方法列表或差异片段),希望获得逐项跟进决策,而非测试套件报告。
  • 维护者希望筛选贡献中的 N 个测试,确定哪些值得后续改进,并为已解决的问题提供质量等级。

不适用场景

  • 调用方需要全面审查测试套件或比较指标——使用 test-anti-patterns(实用型)或 test-smell-detection(形式化),并让 test-engineer Agent 协调其内部质量专家。
  • 调用方想要编写新测试——使用 test-engineer(适用于任何语言)或 writing-mstest-tests(专用于 MSTest)。
  • 调用方想要衡量代码覆盖率或 CRAP 分数——使用 coverage-analysis 或 crap-score(仅限 .NET)。
  • 调用方想要直接修复测试代码中的问题——调用相应的编辑技能。
  • 未提供特定的测试列表。不要尝试为工作区中的每项测试评分;请向调用方索要明确的列表或范围。

输入

输入必需描述
测试方法是要评分的范围。请提供以下任一内容:(a) 明确的测试方法名称列表(完全限定名,例如 Namespace.ClassName.TestMethodName);(b) 一个或多个文件路径,并明确指示对这些文件中声明的每个测试进行评分;或 (c) 应对其变更的测试进行评分的差异代码块 / PR 标识符。建议提供文件路径;如果方法名称在工作区中没有歧义,则可不提供。对于范围不明确的请求,例如未指定范围的 “给我的测试打分”,会在前置检查中拒绝(参见步骤 0);此技能面向经过筛选的输入,不会自动对整个工作区评分。
测试主体 / 代码片段建议提供每个测试方法对应的确切源代码行。如果省略,则从所列文件中读取。
生产代码否被测代码,用于判断断言是否覆盖了所声称的行为。若无法获取,则应将变异评估标记为 N/A / 未验证,而不是猜测或扣分。
语言参考否宿主提供的、指向匹配的 test-analysis-extensions 文件的路径。直接读取该文件,而不是调用仅供参考的加载器;不得用未经验证的框架指南替代。
差异上下文否对 PR 变更进行评分时,每个测试方法对应的统一差异有助于聚焦实际变更的内容。
步骤 0:验证输入

在执行任何其他操作之前,检查调用方是否提供了以下内容之一:

  1. 明确的测试方法名称列表,或
  2. 一个或多个文件路径,并明确指示对这些文件中声明的每个测试进行评分(例如,“对 OrderTests.cs 中的每个测试评分”),或
  3. 应对其变更的测试进行评分的差异代码块或 PR 标识符。

如果请求含糊不清(例如,未指定范围的 “给我的测试评分”、“这些测试质量如何?”、“审查测试套件”),不要加载扩展、不要读取文件,也不要对任何内容评分。请简短回复,要求调用方提供明确的列表 / 文件 / 差异,并可酌情建议他们使用 test-engineer Agent 或 test-anti-patterns 技能来分析整个测试套件。到此为止。

如果有效且范围明确的请求中没有符合条件的测试,则返回 不适用,并简短说明原因;不要编造行。

工作流

步骤 1:检测语言并加载扩展

根据文件扩展名以及所提供列表中的测试方法标记,识别目标代码库使用的语言和测试框架。直接读取调用方提供的匹配扩展文件;否则,从运行时列出的 test-analysis-extensions 目录中读取匹配的 extensions/ 文件(dotnet.md、python.md、typescript.md、go.md 等)。 对于其他语言,使用该目录,不要调用技能。 如果输入包含多种语言的测试,则加载每种相关扩展,并按各自语言的惯例对每个测试评分。

步骤 2:确定测试主体

对于输入列表中的每个条目:

  1. 如果测试主体以内联形式提供,则直接使用。
  2. 否则,读取给定路径的文件,并通过完全限定名定位方法。获取完整的方法主体,包括属性 / 装饰器 / 固件,以及该测试调用的任何辅助代码。
  3. 如果找不到所请求的方法,则将其记录为 Uncertain — method not found,不提供质量评分,然后继续。绝不要编造方法主体来评分。请求的方法缺失需要人工审查;这与有效范围内没有测试不同。

**组合检查点:**对于已解析且有可用生产代码上下文的测试,在步骤 3 之前加载 test-gap-analysis;对于整个批次只加载一次,并使用 per-test-read-only 评估上下文。完成其负责的参考评估后再继续。即使测试主体层面的弱点已经显而易见,也不得跳过此加载;自行编造变异解释不属于组合。

步骤 3:评估声称的行为,并为每个已解析测试评分

评分时保持只读:不运行构建或测试,不执行变更,不编辑文件,不安装工具,不广泛发现测试套件,也不委派给 Agent。仅解析提供的测试、相关夹具/辅助函数,以及验证其声明所需的生产调用链。

使用步骤 2 检查点中的内联 test-gap-analysis 评估;不要再次加载。提供每个测试的标识符/正文、相关设置/辅助函数、声称的行为、断言语义和可用源码。其组合分发会加载自有的只读参考,而非其独立的基线/验证工作流。使用其中针对每个测试的证据;不要在此处重复其变更目录,也不要调用审计/生成 Agent。 使用宿主支持的调用方指令传达模式和输入。如果加载器只接受技能名称,则仅按名称加载 test-gap-analysis;不要臆造工具参数或特定模式的技能名称。

如果技能/参考或生产上下文不可用,记录 Pseudo-mutation: N/A / unverified — <reason>,然后继续按正常的正文级别评分。这不扣分,也不会单独导致 Uncertain 结果。不要搜索安装目录,也不要改用变更运行器。

仅评估每个测试声称的内容:不要借用其他测试的断言,也不要要求无关分支、输出或场景。可观察到的存活变更若能证明测试未验证其声称的结果,则可用于支持现有的 Assertion 强度类别;不要引入变更点、权重、上限或自动存活扣分。正常应用现有评分标准,包括其在 Assertion 和 Anti-pattern 维度中均归类的弱点;不要因相同的变更证据重复扣分。

每项测试都从 A(90–100 分段)开始,然后仅针对捕获正文中可观察到的问题严格扣分。对于假设性问题(例如“可能缺少更多否定断言”),除非生产代码明确要求且生产代码可用,否则不要扣分。

生产代码不可用时,照常评估测试正文中可观察到的问题,但不要推断缺失的行为或因此扣分。在摘要中注明一次 Production-dependent behavior coverage: Unverified,以便读者区分测试正文中的发现和需要源码验证的声明。

三个子维度

计算三个子等级(均为 A–F),共同决定总评级。

A. 断言强度

阅读已加载语言扩展的断言 API 列表,并对测试主体中的每个断言进行分类。按从高到低评分:

子等级模式
A通过结构相等性或非冗余的相关可观察结果(例如,返回值加上必需的状态/交互检查)来锁定更丰富的契约,或符合下文的异常/错误校准规则。评估谓词深度,而非断言数量。模拟调用验证和普通语言断言均按常规计分。
B一个明确且有意义的断言,用于验证被测行为。
C只有琐碎断言(单个 IsNotNull/toBeDefined/assert x is not None),或断言未检查测试所声称结果中有意义的部分。若测试只针对单个字段,无需检查无关字段。
D一个自指/同义反复断言(Assert.AreEqual(x, x)、assert dto.name == dto.name、未使用非平凡输入的往返身份验证),或宽泛的异常断言(Assert.ThrowsException<Exception>)。
F完全没有断言;所有断言都是恒为真的字面值(Assert.IsTrue(true)、assert True、expect(true).toBe(true))——这些断言无法验证任何内容,等同于没有断言;或所有断言都未等待,且未作处理(例如,没有 await/return 的 expect(promise).resolves.toBe(x),未使用 await 的异步 TUnit/xUnit Assert.ThrowsAsync,pytest-asyncio 中未等待的协程)。

异常和错误路径测试(Assert.ThrowsException<T>、受约束的 pytest.raises、expect(fn).toThrow、assertThrows、#[should_panic]、Should -Throw、EXPECT_THROW,或验证预期非空错误的 Go 代码)本身即完整。若测试针对其声明的范围检查了确切的预期错误条件,则断言强度评为 A。不因只有这一个断言而扣分;除非错误消息属于文档规定的契约,否则不要求检查错误消息。仅检查 err == nil,却丢弃有意义返回值的 Go 正常路径测试,仍评为 C,因为它没有验证成功结果。

B. 结构与聚焦
子等级模式
A清晰区分 Arrange-Act-Assert(或 Given-When-Then)阶段。只测试一种行为。主体少于约 30 行。使用框架惯例进行设置。
B存在一个轻微的结构问题(主体略长,或阶段之间缺少空行),但意图明确。
C在一个测试中混合多种行为,或 AAA 阶段交错到影响理解。
D测试中存在条件逻辑(由 if/switch 驱动断言)——符合惯例的 Go/Rust 表驱动子测试循环除外;或测试依赖之前测试的状态(顺序依赖)。
F测试超过约 60 行,且验证多个无关行为;或通过未重置的静态变量/全局变量与其他测试共享可变状态。
C. 反模式规范

根据下方目录进行检查。反模式子等级分两轮计算,并按确定性规则合并:

  1. 硬上限阶段。 每个 严重 或 高危 发现都会设定一个最高子等级(标注为 F、D 或 C)。在所有匹配的严重/高危发现中取最差的上限——这些发现不会累加(即使还有其他严重/高危发现,只要有一个 F 级发现,子等级上限就是 F)。
  2. 中等扣分阶段。 从 A 开始,每个 中等 发现扣一个子等级(A→B、B→C、C→D、D→F)。多个发现会累加扣分。

最终反模式子等级取两轮中较差的结果(即 min(硬上限, A − 中等发现数量))。低危发现不会影响评分——仅在备注中提及。

示例(严重/高危和中等发现的数量 → 反模式子等级):

  • 零个严重/高危问题,1 个中危问题 → B(A − 1)
  • 零个严重/高危问题,3 个中危问题 → D(A − 3)
  • 一个 C 级封顶问题(例如过度使用模拟),0 个中危问题 → C
  • 一个 C 级封顶问题,2 个中危问题 → C(min(C, A − 2 = C) = C;第 3 个中危问题会降至 D)
  • 一个 F 级问题(例如吞掉异常),加任意数量的中危问题 → F

严重(直接降至 F 或 D)

  • 完全没有断言 → F(断言子等级也会降至 F)
  • 吞掉异常:.NET 中的 try { … } catch { }、Python 中的裸 except: pass、 JS/TS/Java 中的 try { … } catch (e) {}、Go 中不重新触发 panic 的 defer recover()、 Ruby 中没有断言的 rescue StandardError、Kotlin/Swift 中的空 catch → F
  • 在 catch 中断言(使用 Assert.Fail(ex.Message),而不是 Assert.ThrowsException)→ D
  • 永远为真的字面量断言(Assert.IsTrue(true)、assert True、 expect(true).toBe(true))→ F(什么也没有验证;断言子等级也会降至 F)
  • 对绑定值进行自引用/恒真断言 (Assert.AreEqual(x, x)、assert dto.name == dto.name)→ D
  • 被注释掉的断言 → D

高危(降低一个或两个子等级)

  • 使用墙上时钟睡眠进行同步:Thread.Sleep、Task.Delay、 time.sleep、基于 setTimeout 的等待、Thread.sleep、time.Sleep、 sleep、std::thread::sleep、Start-Sleep、 (在单元测试中使用)std::this_thread::sleep_for → D
  • 未设定种子的随机值、未经抽象封装的墙上时钟读取 (DateTime.Now、datetime.now()、Date.now()、 System.currentTimeMillis()、time.Now()、Time.now、 Instant::now()、Get-Date、system_clock::now)→ D
  • 硬编码的、依赖环境的路径(C:\…、/tmp/…、网络主机)→ D
  • 依赖可变的静态变量/包级全局变量的排序 → D
  • 宽泛的异常断言(Assert.ThrowsException<Exception>、 pytest.raises(Exception)、没有匹配器的 expect(fn).toThrow(Error)、 没有 expected = "…" 的 #[should_panic]、没有 -ExpectedMessage 的 Should -Throw、EXPECT_ANY_THROW)→ C
  • 过度使用模拟:模拟设置代码行数多于测试逻辑,或验证精确的 调用顺序而非结果 → C
  • 与实现耦合:通过反射访问私有成员、强制转换为内部类型以访问状态 → C

中危(降低一个子等级)

  • 名称不佳:Test1、TestMethod、test,或无法说明场景或预期结果的单词名称 (根据该语言扩展的惯例判断)→ 降低一个子等级
  • 魔法值:在准备/断言中使用没有命名或注释说明的 42、"foo"、0x1234 → 降低一个子等级
  • 巨型测试(超过 30 行,覆盖单个行为)→ 降低一个子等级
  • 只重复断言文本的断言消息 → 降低一个子等级
  • 非平凡测试缺少 AAA / GWT 分段 → 降低一个子等级

低危(仅注明,不扣分)

  • 未使用的准备/清理钩子;遗留的打印调试语句(Console.WriteLine、 print、console.log、System.out.println、fmt.Println、puts、 dbg!、Write-Host、std::cout);与同类测试命名不一致; 遗留的 TODO 注释。在备注栏中注明,但不扣分。
合并子等级

将子等级转换为数值分数:A=4,B=3,C=2,D=1,F=0。

  • 总体分数区间 = 加权平均值: 0.45 × Assertion + 0.30 × Anti-pattern + 0.25 × Structure
  • 映射为字母等级:
    • ≥ 3.5 → A(区间 90–100)
    • ≥ 2.8 → B(区间 80–89)
    • ≥ 2.0 → C(区间 70–79)
    • ≥ 1.2 → D(区间 60–69)
    • < 1.2 → F(区间 0–59)
  • 总体等级不得高于最差子等级——如果任一子等级为 F,总体等级就是 F;如果最差子等级为 D,总体等级最高为 D;其他情况依此类推。测试只要在任一维度上失败,总体等级就不能高于该维度的等级。

先进行加权映射,再应用等级上限,不能用上限规则取代加权映射。例如,Assertion 为 B、Anti-pattern 为 A、Structure 为 A 时,结果为 3.55:加权后的暂定等级为 A,但最终等级为 B(80-89)。因此,针对单一标量的专注测试可以是 B / Pass,且没有可执行的改进项;不要为了证明该等级合理而虚构弱点或缺失的同类行为。

报告字母等级和分数区间(不要只报 0–100 之间的一个数字)。虚假的精确度会引发无谓争论;使用区间可以让讨论聚焦于评分标准。

第 4 步:确定决策结果

等级概括测试的强度;结果说明是否需要跟进。改进项必须是有证据支持的测试、设置或夹具变更。只能指定以下一项:

  • 通过 — 没有可执行的改进项;可以包含正面评价或仅提供背景的信息。
  • 失败 — 至少有一项可执行的改进项,无论等级如何。
  • 不确定 — 证据不足以作出决定,需要人工审核。
  • 不适用 — 有效范围内没有符合条件的测试;通常用于没有行的总体结果。

不要根据等级推导状态:完整且目标明确的测试可以是 B / Pass,而调试输出可能会让原本很优秀的测试变成 A / Failed。遇到无法解析的方法体、不受支持的结构或缺少关键约定时,使用“不确定”——不能仅仅因为缺少生产代码就使用该状态。存在明确问题时,应优先报告该问题,而不是判为不确定。

第 5 步:撰写说明

用一个句子说明最重要的原因(目标长度 ≤ 120 个字符): 未发现问题。、只检查了 IsNotNull;未验证 receipt 内容。或 无法解析方法体;需要人工审核。。不要为了证明等级或失败结果合理而虚构弱点。

将操作项单独写在 如何改进 字段中。对于每个失败的测试,指出最小且有用的输入、断言或夹具变更,并说明预期结果;依据应来自方法体、源代码或明确的约定。例如,写成 将自比较替换为 Assert.AreEqual(60m, account.Balance).,而不是 改进断言;写成 在 Deposit(25m) 后移除 Console.WriteLine。,而不是 清理代码。优先列出影响最大的不同问题;只有需要采用不同变更时,才加入其他可执行的改进项。

对于行为缺口,使用共享评估中的区分性见证,以及原始版本和变异版本的观察结果;依据未修改的源代码检查预期结果。如果缺少关键上下文,应指出需要哪些证据,而不是虚构预期值。通过项填写 None;不确定项应给出具体的证据核实步骤,而不是推测性地重写测试。仅依据评分标准扣分,不能证明存在行为缺口或可执行的改进项:专注的 B / Pass 可能无需任何变更。A / Failed 仍需给出具体操作,例如移除调试输出。

第 6 步:报告

生成两个部分。

1. 摘要

以 **结果:<通过|失败|不确定|不适用>** 开头,然后给出结果数量和优先级最高的操作项。按以下顺序汇总: 失败 → 不确定 → 通过 → 不适用。如果为“不适用”,说明范围为空,并省略表格。

2. 逐项测试表
| Test | Result | Quality | Notes | How to improve |
|------|--------|---------|-------|----------------|
| `Namespace.ClassName.Test_Method_Condition_Expected` | Pass | B (80–89) | One complete value assertion. | None |
| `Namespace.ClassName.Withdraw_SufficientFunds` | Failed | D (60–69) | Balance is compared with itself. | Replace self-comparison with `Assert.AreEqual(60m, account.Balance)` after withdrawing 40m from 100m. |
| `Namespace.ClassName.Test_Missing` | Uncertain | — | Method body could not be resolved; human review is required. | Supply the method body and its referenced fixture. |

保留这两个报告部分以及原有的 Test/Result/Quality/Notes 字段。当变异证据能够解释某项发现,或调用方要求详细信息时,在逐项测试部分内追加一个简洁的 伪变异证据 区块:变更、见证、原始代码/变异代码的观察结果、相关断言和分类。静态结果只能标为 可能已杀死(推断) 或 可能存活(未经验证),绝不能标为已执行的 Killed/Survived,也不能给出经验性杀死数/总数。对于共享限制导致的上下文缺失,只说明一次 N/A / 未验证。不要在正文中重复改进表。

数量上限与排序:

  • 如果表格超过 50 行,先显示 Failed 测试,然后是 Uncertain 测试,最后展示部分 Pass 测试。将超出部分放入折叠的 <details> 区块。
  • 同一结果内,按质量从低到高排序,然后按文件路径和方法名排序,以确保顺序确定。
  • 如果提供了 diff 上下文,在每个测试名称前加上 (new) 或 (modified) 标记。

如果包含多种语言,则每种语言分别生成一个表格,并在每个部分前标注语言名称和框架。

验证

  • 输入列表中的每个测试都出现在表格中(或记录为 Uncertain — method not found)。
  • 每个已解析的测试都标有 Pass 或 Failed,并附带 A-F 质量详情。
  • Uncertain 表示证据缺失;Not applicable 表示有效的空范围。
  • 每个等级都至少由捕获的测试主体中的一个可观察信号支持,不得推测扣分。
  • 每个 Failed 行都有具体且有证据支持的改进措施;不得给 Pass 行凭空添加缺陷,即使质量等级为 B 也如此。
  • 变异评估仅以只读方式逐项进行;不因上下文不可用而扣分,排除等效项,也不得将静态标签/计数表述为已执行的证据。
  • 已验证的可观察发现应纳入现有类别,不得重复扣分,也不得更改评分权重和上限。
  • 仅当唯一断言是简单断言时,才将其标记为简单断言(在有实际意义的断言之前进行的 null 检查不属于简单断言)。
  • 不得因断言数量少而惩罚仅检查异常的测试。
  • Mock 调用验证和简写断言形式都应计为对应类别的真实断言。
  • 对有意义属性的布尔断言(Assert.IsTrue(result.IsValid))不得归类为恒真;只有字面量 true/false 常量才属于此类。
  • 自引用断言应与普通相等断言分开标记。
  • 不得将惯用模式标记为问题:Go/Rust 的表驱动子测试、pytest 的裸 assert、Go 的 if got != want { t.Errorf(...) }、JS/TS 的 expect(mock).toHaveBeenCalledWith(...)。
  • 异步测试陷阱(未等待的 resolves/rejects/ThrowsAsync、未使用 await 的 pytest-asyncio)会将 Assertion 子等级降至 F。
  • 摘要应以影响最大的观察结论开头,而不是复述表格。

常见陷阱

陷阱解决方案
未提供列表时,对工作区中的每个测试评分请调用方提供明确列表;此技能适用于精选输入。
为了证明评分合理而夸大扣分从 A 开始;仅针对可观察到的问题扣分。
因断言数量少而惩罚异常测试异常断言本身就是完整的。
因为聚焦于 Go 错误路径的测试只检查 err != nil 就降低评分在该范围内,预期错误是否存在就是可观察的契约;除非生产契约要求特定错误标识或消息,否则保持 A。
将值断言前的 IsNotNull 视为无意义只有空值检查是唯一断言时才标记。
将任何布尔断言都视为实际上没有断言只有始终为真的字面值(Assert.IsTrue(true)、assert True)才是;有意义的 Assert.IsTrue(result.IsValid) 是真正的断言。
将 Go/Rust 表驱动循环标记为条件逻辑这是惯用写法;不要扣分。
将 pytest 裸 assert 或 Go if got != want { t.Error… } 视为缺少测试框架两者都是规范写法;应计入正确的断言类别。
生产代码不可用时惩罚测试将有关未覆盖行为的疑虑标记为 Unverified,不要扣分。
使用看似精确的分数(例如 87/100)仅使用分数区间:90–100、80–89、70–79、60–69、0–59。
在 PR 评论中展开 500 行的表格应用步骤 6 的行数上限;将多余内容折叠到 <details> 中。
在不同类别下重复报告同一个既有问题三次选择最合适的类别,只报告一次。
因同类测试的断言而给薄弱测试加分仅使用当前测试及其执行的辅助函数/固件。
将伪变异组合变成套件审计传入明确的逐测试只读模式;不运行、不编辑、不进行广泛发现,也不递归调用 Agent。
为了让说明显得“平衡”,给 A 级测试编造弱点如果测试没有问题,说明可以只写 No issues found.。
根据等级或评论映射状态仅在存在可执行的改进项时判定失败;B 可以通过,A 也可以失败。
混淆 Uncertain 和 Not applicable证据缺失属于 Uncertain;有效的空范围属于 Not applicable。

评论

支持 Markdown