Nunit: 请求:向 PropertyConstraint 添加对索引器的支持

创建于 2017-06-02  ·  46评论  ·  资料来源: nunit/nunit

目前只能测试正常属性的值,无法测试索引器值。 我需要编写一个看起来像这样的测试:

```c#
[测试夹具]
公共类测试类
{
类示例
{
私人字典字典 = 新字典();

  public string this[string key]
  {
    get { return dictionary[key]; }
    set { dictionary[key] = value; }
  }
}

[Test]
public void Test()
{
  var obj = new Example
  {
    ["TEST1"] = "1",
    ["TEST2"] = "2",
  };

  Assert.That(obj, Has.Property("Item", "TEST1").EqualTo("1").And.Property("Item", "TEST2").EqualTo("2"));
}

}
``

done help wanted enhancement normal

最有用的评论

至于薪酬等级,由于这里的每个人在 NUnit 工作的年薪约为 0 美元,我认为我们都处于相同的薪酬等级 😝

所有46条评论

我喜欢这个主意。

C# 不支持命名索引属性,但 VB.NET 支持。 我认为语法.Property("Name", index1, ...)应该保留给命名索引器,所以我不希望看到它用于默认索引器。 我希望能够通过不要求人们传入名称参数来尊重 C# 语言。

您如何看待Has.Item("TEST1").EqualTo("1")Has.Index("TEST1").EqualTo("1")

@jnm2
将“Item”或“Indexer”方法作为快捷方式很方便,但实际上可以在 C# 中更改索引器名称: https :

@Ch0senOne可以更改名称。 更清楚地说,我应该说,在 C# 中不可能创建非默认索引器。 例如,您不能声明两个索引器,也不能使用 C# 中的名称。
仔细考虑一下,恐怕Has.Item("value")Contains.Item会混淆。 那么也许Has.Index("value")

所以我赞成Has.Index("IndexValue")用于默认索引器,而Has.Property("PropertyName", indexParams)用于非默认索引器。 Has.Property最终也会为默认索引器工作,即使 C# 惯用的做法是使用Has.Index


或者如果我们想变得可爱,我们可以做Has.Index["IndexValue"]
Has.Property("PropertyName")[indexparams] ... 😁

实际上,我想知道我们是否可以使用索引器语法摆脱Has.Item["AtIndex"]以避免与Contains.Item("ItemValue")混淆......不知道我是怎么想的!


让我们看看其他@nunit/framework-team 成员的想法。

我们已经定义了使用 lambda 而不是字符串来处理属性的方向。 似乎时间可能会更好地花在这上面。 我在用手机,所以不能给你问题号。

@CharliePoole https://github.com/nunit/nunit/issues/26?

这对Has.Property有意义。 我们可以这样做:

  • Has.Property(_ => _.PropertyName[index1, ...])
  • Has.Property(_ => _.PropertyName, index1, ...)

我仍然认为在需要非默认属性之前我们不应该对Has.Property做任何事情,现在只需提供Has.Index(value1, ...)Has.Index[value1, ...]Has.Item[value1, ...]更常见的默认索引器。

就是那个。 我同意你关于命名索引器的意见,无论如何都没有被要求。

@jnm2为了您的考虑,您也可以制作一个多键索引器。 例如

this[string key1, string key2]

我想知道这个问题是否有任何更新,或者当前在不继承 IEnumerable 的类型上测试索引器的方法。 我想我可以检查是否抛出了 KeyNotFoundException。 我忽略的其他任何东西都可以让我检查索引器中是否存在键?

@crush83是的,我的index1, ...语法旨在表明我们无论做什么都应该考虑它们。

我不喜欢术语键,除非我们谈论的对象实际上是字典或键控集合。 这些是带有默认索引器的东西,但并非所有带有默认索引器的东西都有键。 (既然您正在使用这个术语,也许您会发现我们的Contains.Key语法很有帮助?我们可以改进它吗?)

除了你在这里看到的之外,没有任何进展。 我们需要一个提案。 把一些东西放在那里,我目前最喜欢的是Has.Item(params object[] indexerArgs) ,不管它的名字是什么,它都会调用默认的索引器。
到目前为止,这会完成所有要求吗?

+1

@nunit/framework-team 您是否支持以下新 API?

namespace NUnit.Framework
{
    public abstract class Has
    {
        public static ResolvableConstraintExpression Property(string name);

+       public static ResolvableConstraintExpression Item(params object[] indexerArgs);
    }
}

namespace NUnit.Framework.Constraints
{
    public class ConstraintExpression
    {
        public ResolvableConstraintExpression Property(string name);

+       public ResolvableConstraintExpression Item(params object[] indexerArgs);
    }
}

在有理由将它添加到 API 之前,我可能会将IndexerConstraint类留在内部。

听起来不错。

另一票赞成,我只是想将此功能用于控制台测试。 🙂

@jnm2这个问题还是团队想要做的吗?

我对这个问题进行了快速测验,因为我不明白到底希望支持的 API 应该是什么样子 - 是吗:

/// obj from the initial post example
Assert.That(obj, Has.Item("TEST1").EqualTo(1).And.Item("TEST2").EqualTo(2));

或者是:

Assert.That(obj, Has.Item("TEST1", 1).And.Item("TEST2", 2));

?

你的例子需要params object[]所以选择选项 2。

假设选择。 2、是否需要数组就像只提供一个对象,断言键存在,2 个参数 - 键和值? 其他组合(或多或少的预期参数),失败?

@Poimen是的,我们将不胜感激!

该数组是必需的,因为索引器可以采用多个参数。 例如:

public string this[int x] => "First indexer";

public string this[string x] => "Second indexer";

public string this[int x, int y] => "Third indexer";

您将使用Has.Item(42).EqualTo("First indexer")Has.Item("42").EqualTo("Second indexer")Has.Item(1, 2).EqualTo("Third indexer")

看,你每天都在学习新东西 - 我以为这是一个错字,哎呀:see_no_evil:我以前没有使用过多键索引器的乐趣......

好的,很酷,这周我会考虑把一些东西放在一起。

@jnm2我为此问题创建了一个 PR。

我担心的一个问题是代码产生的错误消息:

Has.Item(42)

错误消息有点“hacky”,但我不确定解决这个问题的最干净的方法是什么。 “黑客”是:
https://github.com/nunit/nunit/pull/3609/commits/96154ca6402c692cec5e0ae6947f9922e72799fe#diff -ceeee4b8399633f181b6bccd5a39eb32R72

@Poimen抱歉,不确定您的意思。 当前的消息是什么,您希望它是什么? 或者您正在寻找建议?

@jnm2出现的错误信息是:

  Expected: indexer [System.Int32]
  But was:  "not found"

错误消息的形成方式StringifyArguments函数有点“hacky”。

因此,如果有一种更简洁的方式来生成所需的消息输出,那么我表达得不好的问题更多。

@jnm2我想我已经解决了评论中的评论(感谢他们)。 标签上写着awaiting:contributor

我不确定我是否需要做更多的事情来让您知道我已经“完成”了:+1:

我认为我们应该尽可能接近Has.Property找不到属性时显示的消息。 那么我们就应该更换propertydefault indexer ,取而代之的属性名accepting arguments [42]或类似的东西。 我倾向于认为我们应该使用现有的格式助手之一,如MsgUtils.FormatCollection来显示参数值而不是列出参数类型。 例如,其中一个参数可能为 null 并且适用于一系列类型。

不,那太好了! 像 PR 线程本身那样的评论将是理想的。

鉴于断言:
```c#
Assert.That(tester, Has.Item("wow"));


It producers the message:

预期:默认索引器接受参数 <>
但是是:“未找到”


Given the assertion:
```c#
Assert.That(tester, Has.Item("wow").EqualTo(1));

它产生消息:

Default indexer accepting arguments < "wow" > was not found on NUnit.Framework.Constraints.IndexerConstraintTests+NamedIndexTester.

这是使用MsgUtils.FormatCollection函数生成的。

因此,也更紧密地匹配属性消息,并添加了详细信息。

我非常喜欢第二种情况。 第一种情况对我来说在语义上仍然是错误的,因为它显示了参数类型而不是参数值。 我们只有参数值,每个参数值可以匹配索引器中的一系列参数类型。 让它看起来好像必须有一个带有System.String参数的索引器是一种误导,因为IEnumerable<char>参数也可以正常工作。

IEnumerable<char>是一个古怪的用例......但当然,我明白了。

所以,回到 - 给定:
```c#
Assert.That(tester, Has.Item("wow"));


producers the message:

预期:默认索引器接受参数<“哇”>
但是是:“未找到”


For numbers it goes into the usual suffix - given:
```c#
Assert.That(tester, Has.Item(42d));

生产者:

  Expected: Default indexer accepting arguments < 42.0d >
  But was:  "not found"

对我来说,“预期”消息看起来很棒。 But was: "not found"消息看起来有点像是在谈论内容not found的字符串实例,但是如果 PropertyConstraint 的行为方式相同,那么保持它们相同对我来说更可取。 默认情况下,更改 PropertyConstraint 将超出此 PR 的范围。 如果您愿意,我们仍然可以进行更改,并且我和框架团队中的其他人同意更改。

是的,我试图让它不这样做,但是我找不到带有错误消息的解决方案。 我希望它只是:

But was: not found

但是ConstraintResult的返回是将字符串引号放在那里。

参考属性版本:
```c#
Assert.That(tester, Has.Property("NoItDoesNotHaveThis"));

produces:

预期:属性 NoItDoesNotHaveThis
但是是:
``
所以没有引号 - 但它返回类型 - 而不是值。

我不确定返回ConstraintResult的重载是否是一个解决方案?

我认为我们应该坚持 PropertyConstraint 为保持一致性所做的事情。 当消息说失败的是找到属性时,显示actual值的默认表示并不是最糟糕的事情。

返回从 ConstraintResult 派生的新类是其他约束如何自定义它,是的。

好的,太好了 - 听起来像 - 给出:
```c#
Assert.That(tester, Has.Item(42d));


producers:

预期:默认索引器接受参数 <42.0d>
但是是:
``

这与属性约束消息匹配。

@nunit/framework-team 和所有人,我希望能迅速解决这个问题,因为@Poimen的优秀公关 (https://github.com/nunit/nunit/pull/3609) 已准备好合并。

@Dreamescaper引起了我的注意,我在这个我忘记了的关注Assert.That(collection, Has.Item("x"));认为Has.Item意味着Contains.Item .
我们可以考虑在失败消息中添加一些内容,但这无助于在阅读源代码中的Has.Item时正确理解它的问题:

  Expected: Default indexer accepting arguments < 42.0d > (Did you mean to use Contains.Item?)

另一方面,我不喜欢Has.Index(42)Has.Index(42).EqualTo(43) ,这是我建议的而不是Item 。 该实例索引一个项目。 你不会说实例一个索引并且索引本身等于 43。索引是你传入的东西,42。

一些选项

  • 使用Has.Item 。 放弃阅读源代码时可能导致的问题。 也许添加一些缓解措施以在编写源代码时提供帮助,例如 XML 文档和失败消息中的提示。
  • 使用Has.Index尽管术语混杂很尴尬。
  • 请改用Has.ItemAtHas.ItemAt(42).EqualTo(43)

等等,我刚刚爱上了最后一个建议,因为它与 LINQ ElementAt 方法有一个相似之处,该方法被赋予一个索引并返回一个项目。

每个人都对Has.ItemAt满意吗? 欢迎提出建议,但我想再次尊重@Poimen已经投入的时间。

+1 为Has.ItemAt :)

我同意你的看法, Has.Item可能会令人困惑。 我喜欢Has.ItemAt但既然你喜欢 LINQ 语法,为什么不使用Has.ElementAt呢?

我不确定 ElementAt,因为它不是直接的等价物。
例如,对于Dictionary<string,string> LINQ 的 ElementAt(0) 将返回第一对,而 Has.ItemTo(0).EqualTo("...") 将失败,因为没有索引器接受整数。

@CharliePoole这真是个好主意。 看起来它不会像查找那样直观,但我不知道为什么我会这么想。 我想我更喜欢Item因为我们将在大概某种集合/字典/查找上调用索引器,而ElementAt旨在用于查询和其他不一定是集合的枚举. 集合元素通常称为项。

@Dreamescaper你认为Has.ItemAt(4)能让人们认为它像ElementAt ,枚举而不是调用索引器吗?

@jnm2
它可能可以,但我不知道更好的选择(而且我认为它仍然是比 Has.Item 更好的选择)。

@jnm2我实际上有点困惑。 这个问题一开始是关于属性的,现在仍然如此。 Has.Property 和 Has.ItemAt 在用户眼中将如何相互关联、交互或比较?

[我意识到属性和集合项在某种抽象级别上可以被视为等效,但我认为这不是我们大多数人通常操作的抽象级别。 :smiling_imp: ]

作为 NUnit 4.0 的东西,了解一下术语在 API 中的使用方式并稍微整理一下可能会很好。 “成员”一词通常等同于“项目”,但也可能表示属性、方法或变量。

我觉得ItemAtElementAt更舒服,部分原因是它看起来像是我们委托给Enumerable.ElementAt或采用与ElementAt(int index)给我留下深刻印象的不是它如何工作的细节,而是 BCL 中有At后缀的先例,尤其是与名为index的参数相关联

如果将其与 ILookup 一起使用,则 ILookup 的每个索引都会返回多个 TElement,而不是单个元素/项目。 Has.EntryAt可能适用于集合、字典和多元素索引查找。 我最初的反应是Has.ItemAt IEnumerable<TElement>从 ILookup 返回

大家怎么看? 当您看到不止一个事情在起作用时,这很难,但是这里的多数偏好也可能有助于表明哪个名称最容易被人们找到。 除了@nunit/framework-team 之外的人,我并不是有意排斥你。 如果您正在观看并且有强烈的感觉,那么这是一个总是有用的数据点!

@CharliePoole Has.Property("X")断言该实例具有名为X的非参数化属性。 Has.ItemAt("X")会断言该实例具有可以接受字符串参数的默认索引器(具有不相关的、众所周知的名称的参数化属性)。

Has.Property("X").EqualTo(3)断言该实例具有名为X的非参数化属性,如果您调用其非参数化get访问器,该属性将返回3Has.ItemAt("X").EqualTo(3)将断言实例都有一个默认的索引(用不相关的,众所周知的名称的参数属性)返回3如果您通过特定的字符串"X"get存取器。

使默认索引器的名称无关紧要的是,C#、VB 和 F# 语法都可以在不指定名称的情况下调用默认索引器。 (因此术语“默认”。)没有默认索引器概念但具有命名索引器概念的语言必须指定名称。 默认索引器的名称通常是Item ,但是默认索引器可以任意命名并且仍然是默认索引器,因为这些语言仍然可以在不指定名称的情况下调用它。 在 C# 中,您可以使用[IndexerName("ArbitraryName")]属性来完成此操作。

@jnm2我实际上了解有关索引器的所有 C# 等语言内容......但是......

我认为财产之间存在混淆的可能性

  • __being__ 实际对象的索引器
  • __returning__ 具有索引器的对象
  • __returning__ 一个包含项目的集合

我强调 __you__ 不会感到困惑,我可能只会在与您交谈时假装如此(苏格拉底式的方法,你知道 :wink: )但我怀疑许多人可能会感到困惑,而您会向人们解释它非常多。

我不反对添加,但我认为需要在文档中详细说明上述区别。

PS:我完全没有被排斥的感觉。 :笑脸:

@CharliePooleHas.ItemAt / Has.ElementAt / Has.EntryAt /etc 之间的选择? 或者我是否遗漏了一点,并且您支持基于这种潜在混淆的特定命名选择?

到目前为止,我倾向于ItemAt@Dreamescaper ,你还在那里吗? @CharliePoole我不确定您是表达对ElementAt的偏好还是只是帮助我们思考整个过程。

参与Has.ItemAt / Has.ElementAt / Has.EntryAt /other 的人越多,我们对人们会发现的直观内容就越有信心。 我想在第二天左右将其称为领先者,因为如果我们花费更长的时间,我们将错过 3.13 版本。

@jnm2只是想通过自己思考并鼓励同样的想法。

WRT 单词的选择,我想知道某些特定的措辞是否最适合我列出的三种表达方式,以及是否还有其他组合需要担心。 有了我提到的三个选项,你就会有

  • Has.ItemAt("foo").EqualTo("bar")
  • `Has.Property("Bing").With.ItemAt("foo").EqualTo("bar")
  • `Has.Property("Bing").With.One.EqualTo("bar")

所以我想你是对的,措辞对任何选项都不重要

从长远来看,我宁愿使用带方括号的实际索引。 但这需要实际编译约束表达式,而不是建模为一组类。

我知道这高于我的工资等级 :smile:, 但我想我会在这里插话......

我喜欢ItemAt

正如@CharliePoole 所建议的那样,我花了一些时间思考一些用例。 我开始认为ElementAtIEnumerable情况下使用得更多,并且语言索引器可以处理的不仅仅是IEnumerable 。 我在ElementAt栅栏上坐了一会儿,然后考虑:
```c#
Assert.That(..., Has.ElementAt("one", "two"));

against:
```c#
Assert.That(..., Has.ItemAt("one", "two"));

ItemAt表达多个值索引器感觉更自然。

我还认为这张罚单最初是用于财产限制的短路,而ItemAt符合该叙述。 (然而,这并不排除它变成更多/更好/等的东西,只是一个观点)。

我也更喜欢ItemAt ,所以让我们称其为多数并支持这一点😺

为防止混淆,让我们确保在完成后迅速记录并包含在发行说明中。

@Poimen Re:你的工资等级,不要低估我们在这里所做的事情的程度。 感谢您分享您的想法!

至于薪酬等级,由于这里的每个人在 NUnit 工作的年薪约为 0 美元,我认为我们都处于相同的薪酬等级 😝

@Dreamescaper在 PR 中提出了另一个好问题。 无论是正数还是负数,这就像检查计数而不是检查索引器的存在:

Assert.That(new[] { 1, 2, 2 }, Has.ItemAt(3));
Assert.That(new[] { 1, 2, 2 }, Has.No.ItemAt(3));

在我们有更多时间考虑之前,移除 Exists 约束似乎是最安全的。 我更愿意为Has.Indexer(typeof(Type1), typeof(Type2), ...)为索引器存在性检查打开一个单独的问题,并Has.ItemAt(arg1, arg2, ...)实际运行索引器并断言有关结果值的内容。

有人会反对最初将Has.ItemAt(3)运送为未解析为约束而将Has.ItemAt(3).EqualTo(4)运送为解析吗?

Has.Indexer对我来说很有意义。 我假设您还会支持Has.Indexer<T>()Has.Indexer<T1, T2>()等,以与其他约束保持一致。

感谢您的反馈! 我批准了拉取请求以使 Has.ItemAt(...) 不再是自我解决的,并且我提交了https://github.com/nunit/nunit/issues/3690以跟踪Has.Indexer来提供这种能力。

此页面是否有帮助?
0 / 5 - 0 等级